-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathslides_javascript_and_ajax.html
More file actions
523 lines (508 loc) · 35.9 KB
/
slides_javascript_and_ajax.html
File metadata and controls
523 lines (508 loc) · 35.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Slides for
JavaScript and Rails — Ruby on Rails Guides
</title>
<link rel="stylesheet" type="text/css" href="stylesheets/style.css" data-turbo-track="reload">
<link rel="stylesheet" type="text/css" href="stylesheets/print.css" media="print">
<link rel="stylesheet" type="text/css" href="stylesheets/highlight.css" data-turbo-track="reload">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="stylesheets/reset.css">
<link rel="stylesheet" href="stylesheets/reveal.css">
<link rel="stylesheet" href="stylesheets/myslide.css" id="theme">
<link rel="stylesheet" href="stylesheets/code.css">
<script src="javascripts/clipboard.js" data-turbo-track="reload"></script>
<script src="javascripts/slides.js" data-turbo-track="reload"></script>
</head>
<body>
<div class="reveal">
<!-- Any section element inside of this container is displayed as a slide -->
<div class="slides">
<section>
<h1>JavaScript and Rails</h1><p>This guide covers the built-in JavaScript functionality of Rails;
it will enable you to create rich and dynamic web applications with
ease!</p><p>After reading this guide, you will know:</p>
<ul>
<li>unobtrusive JavaScript,</li>
<li>how to use ES6 in your Rails app,</li>
<li>how Rails avoids loading full HTML pages,</li>
<li>how built-in helpers assist you,</li>
<li>the Turbolinks gem.</li>
</ul>
<p>This guide is based on the original
<a href="https://guides.rubyonrails.org/working_with_javascript_in_rails.html">Rails Guide Working with JavaScript</a>.
The original guide uses coffeescript, this guide uses ES6 and
refers to an <a href="https://github.com/backend-development/rails-example-recipes-js">example app</a></p><div class="interstitial repo"><p>Fork the <a href="https://github.com/backend-development/rails-example-recipes-js">example app 'recipes'</a> and try out what you learn here.</p></div>
<p><small>Slides - use arrow keys to navigate, esc to return to page view, f for fullscreen</small></p>
</section>
<section><a class='slide_break' href='javascript_and_ajax.html#slide-0'>▻</a>
<h2 id="unobtrusive-javascript"><a class="anchorlink" href="#unobtrusive-javascript"><span>1</span> Unobtrusive JavaScript</a></h2><p>Rails uses a technique called "Unobtrusive JavaScript" to handle attaching
JavaScript to the DOM. The goal is to separate javascript from html as
much as possible. So instead of writing inline JavaScript:</p><div class="interstitial code">
<pre><code class="highlight html"><span class="nt"><a</span> <span class="na">href=</span><span class="s">"#"</span> <span class="na">onclick=</span><span class="s">"this.style.backgroundColor='#990000'"</span><span class="nt">></span>Paint it red<span class="nt"></a></span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="<a href="#" onclick="this.style.backgroundColor='#990000'">Paint it red</a>
">Copy</button>
</div>
<p>We extract the code into a JavaScript funktion, and only attach the event
handler to the a-tag after the page has loaded. To encourage reuse of JavaScript
funktions we try to add all the data needed in html data-attributes:</p><p>Not very DRY, eh? We can fix this by using events instead. We'll add a <code>data-*</code>
attribute to our link, and then bind a handler to the click event of every link
that has that attribute:</p><div class="interstitial code">
<pre><code class="highlight js"> <span class="nf">$</span><span class="p">(</span><span class="kd">function</span><span class="p">(){</span>
<span class="nf">$</span><span class="p">(</span><span class="dl">"</span><span class="s2">a[data-background-color]</span><span class="dl">"</span><span class="p">).</span><span class="nf">click</span><span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">e</span><span class="p">.</span><span class="nf">preventDefault</span><span class="p">();</span>
<span class="kd">var</span> <span class="nx">backgroundColor</span> <span class="o">=</span> <span class="nf">$</span><span class="p">(</span><span class="k">this</span><span class="p">).</span><span class="nf">data</span><span class="p">(</span><span class="dl">"</span><span class="s2">background-color</span><span class="dl">"</span><span class="p">)</span> <span class="o">||</span><span class="err"> </span><span class="k">this</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">backgroundColor</span><span class="p">;</span>
<span class="kd">var</span> <span class="nx">textColor</span> <span class="o">=</span> <span class="nf">$</span><span class="p">(</span><span class="k">this</span><span class="p">).</span><span class="nf">data</span><span class="p">(</span><span class="dl">"</span><span class="s2">text-color</span><span class="dl">"</span><span class="p">)</span> <span class="o">||</span> <span class="k">this</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">color</span><span class="p">;</span>
<span class="k">this</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">backgroundColor</span> <span class="o">=</span> <span class="nx">backgroundColor</span><span class="p">;</span>
<span class="k">this</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">color</span> <span class="o">=</span> <span class="nx">textColor</span><span class="p">;</span>
<span class="p">});</span>
<span class="p">});</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text=" $(function(){
$("a[data-background-color]").click(function(e) {
e.preventDefault();
var backgroundColor = $(this).data("background-color") || this.style.backgroundColor;
var textColor = $(this).data("text-color") || this.style.color;
this.style.backgroundColor = backgroundColor;
this.style.color = textColor;
});
});
">Copy</button>
</div>
<div class="interstitial code">
<pre><code class="highlight html"><span class="nt"><a</span> <span class="na">href=</span><span class="s">"#"</span> <span class="na">data-background-color=</span><span class="s">"#990000"</span><span class="nt">></span>Paint it red<span class="nt"></a></span>
<span class="nt"><a</span> <span class="na">href=</span><span class="s">"#"</span> <span class="na">data-background-color=</span><span class="s">"#009900"</span> <span class="na">data-text-color=</span><span class="s">"#FFFFFF"</span><span class="nt">></span>Paint it green<span class="nt"></a></span>
<span class="nt"><a</span> <span class="na">href=</span><span class="s">"#"</span> <span class="na">data-background-color=</span><span class="s">"#000099"</span> <span class="na">data-text-color=</span><span class="s">"#FFFFFF"</span><span class="nt">></span>Paint it blue<span class="nt"></a></span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="<a href="#" data-background-color="#990000">Paint it red</a>
<a href="#" data-background-color="#009900" data-text-color="#FFFFFF">Paint it green</a>
<a href="#" data-background-color="#000099" data-text-color="#FFFFFF">Paint it blue</a>
">Copy</button>
</div>
<p>This is called 'unobtrusive' JavaScript because we're no longer mixing
JavaScript into the HTML. We've properly separated our concerns, making future
change easy. </p><p>Take a look at the standard "destroy" links created by the scaffold:
they use <code>data-confirm</code> to unobstrusively add a confirmation dialog.</p><p>The Rails team strongly encourages you to write your JavaScript in this style,
and you can expect that many libraries will also
follow this pattern.</p></section>
<section><a class='slide_break' href='javascript_and_ajax.html#slide-1'>▻</a>
<h2 id="using-es6-in-rails"><a class="anchorlink" href="#using-es6-in-rails"><span>2</span> Using ES6 in Rails</a></h2><p>Rails comes with CoffeeScript by default. It is transpiled to JavaScript
by the asset pipeline. CoffeeScript gives you a simpler, more ruby-like syntax,
including arrow functions and a secure for in loop. This was very valuable
three or five years ago. But in 2017 ES6 also gives you arrow functions
and a for of loop, so you might not need CoffeeScript after all.</p><p>To disable CoffeeScript just comment out the gem in the Gemfile
and remove any *.coffee files the scaffold might have created from <code>/app/assets/javascript</code></p><p>To use ES6 instead follow the instructions for
the <a href="https://github.com/TannerRogalsky/sprockets-es6">sprockets-es6 gem</a>.
With this gem
any file in <code>/app/assets/javascript/</code> with extension <code>.js.es6</code> will be
transpiled to JavaScript first, and later be minified and combined by the asset pipeline.
You do not need to write modules or import them, because all the JavaScript
code will be combined into one file.</p></section>
<section><a class='slide_break' href='javascript_and_ajax.html#slide-2'>▻</a>
<h2 id="javascript-and-rails"><a class="anchorlink" href="#javascript-and-rails"><span>3</span> Javascript and Rails</a></h2></section>
<section><a class='slide_break' href='javascript_and_ajax.html#slide-3'>▻</a>
<h3 id="what-changes-questionmark"><a class="anchorlink" href="#what-changes-questionmark"><span>3.1</span> What changes?</a></h3><p>In order to understand how Javascript is used here, you must first understand what a web browser does normally.</p><p>When you type <code>http://localhost:3000</code> into your browser's address bar and hit
'Go,' the browser (your 'client') makes a request to the server. It parses the
response, then fetches all associated assets, like JavaScript files,
stylesheets and images. It then assembles the page. </p><p>If you click a link, the same process starts again: fetch the page, fetch the assets, put it all together,
show you the results. This is called the 'request response cycle.'</p><p>JavaScript can also make requests to the server, and parse the response. It
also has the ability to update information on the page. Combining these two
powers, you can write JavaScript that updates just parts of a webpage,
without needing to get the full page from the server. This is a
powerful technique. It used to be called <a href="https://en.wikipedia.org/wiki/Ajax_(programming)">Ajax</a>, but nowadays it's just the normal way Javascript is used.</p><p>Rails provides quite a bit of built-in support for building web pages with this
technique. </p></section>
<section><a class='slide_break' href='javascript_and_ajax.html#slide-4'>▻</a>
<h3 id="javascript-example"><a class="anchorlink" href="#javascript-example"><span>3.2</span> Javascript Example</a></h3><p>You can clone the source code for the example from
<a href="https://github.com/backend-development/rails-example-recipes-js">github</a></p><p>Let's have a look at the list of ingredients, and how an ingredient
can be edited with the normal CRUD operations created by the scaffold.
There are four HTTP requests and three full page loads in this process:</p><p><img src="images/rails-js-1.jpg" alt=""></p><p>We want to replace this with a version that uses Rails typical Javascript. When
the 'edit' link is clicked the server returns JavaScript that places
the form into the existing page.
When the form is submitted - also by Javascript - the server returns
JavaScript that enters the name of the new ingredient into
the existing list.</p><p>This still needs four HTTP requests, but a lot less data is transferred
and - more importantly - there is no new full HTML page for the browser
to parse and display. The URLs stay almost the same: </p><p><img src="images/rails-js-2.jpg" alt=""></p><p>As a first step we change the <code>link_to</code> to <code>remote</code>:</p><div class="interstitial code">
<pre><code class="highlight plaintext">link_to 'Edit', edit_ingredient_path(ingredient), remote: true, class: 'edit_ingredient'
</code></pre>
<button class="clipboard-button" data-clipboard-text="link_to 'Edit', edit_ingredient_path(ingredient), remote: true, class: 'edit_ingredient'
">Copy</button>
</div>
<p>Now clicking the link fails silently. In your
browsers network view you can
see that a GET request is sent, and HTML code
is returned (just like before):</p><p><img src="images/rails-js-network-html.png" alt=""> </p><p>But our HTTP request does not handle the HTML.
In fact, our HTTP requests expects the response to
contain JavaScript code! If you look in the Rails log
you can see that:</p><p><img src="images/rails-js-log.png" alt=""> </p><p>Rails is already prepared to respond to both requests
that expect HTML and those that expect JavaScript.
In fact we only have to create a view <code>edit.js.erb</code>
and Rails will render that.</p><p>Try it out with a simple alert:</p><div class="interstitial code">
<pre><code class="highlight plaintext">alert("from the server, for ingredient <%= @ingredient.id %>");
</code></pre>
<button class="clipboard-button" data-clipboard-text="alert("from the server, for ingredient <%= @ingredient.id %>");
">Copy</button>
</div>
<p>The result should be an alert in your browser:</p><p><img src="images/rails-js-alert.png" alt=""> </p><p>If this works we can start building the
behaviour we actually want: We want to replace
the existing display of the ingredient with the
edit form. Let's find a good place in the
DOM to do that:</p><p><img src="images/rails-js-dom.png" alt=""> </p><p>The paragraph has an id that identifies the ingredient.
That is a good place to start. Then we find the first
span inside that and replace the existing content:</p><div class="interstitial code">
<pre><code class="highlight plaintext">console.log("now running for <%= @ingredient.id %>");
$("#ingredient_<%= @ingredient.id %> span").html('put the form here!');
</code></pre>
<button class="clipboard-button" data-clipboard-text="console.log("now running for <%= @ingredient.id %>");
$("#ingredient_<%= @ingredient.id %> span").html('put the form here!');
">Copy</button>
</div>
<p>For the creation of the form we can use the existing form partial.
We need to escape the resulting code
in the proper way for using it in javascript:</p><div class="interstitial code">
<pre><code class="highlight plaintext">....html('<%= escape_javascript(render 'form') %>');
</code></pre>
<button class="clipboard-button" data-clipboard-text="....html('<%= escape_javascript(render 'form') %>');
">Copy</button>
</div>
<p>There is a short version for <code>escape_javascript</code>: just the letter <code>j</code>.
And we can leave the braces off:</p><div class="interstitial code">
<pre><code class="highlight plaintext">....html('<%= j render 'form' %>');
</code></pre>
<button class="clipboard-button" data-clipboard-text="....html('<%= j render 'form' %>');
">Copy</button>
</div>
<p>At this stage the app is fully functional again: If you send
in the form via normal PATCH request a new page is rendered,
and everything works.</p><p>But we will not stop here. We will turn this form into a "remote form".
We can do this by adding the <code>data-</code> attribute to it.
The form has a unique id we can use to identify it:</p><div class="interstitial code">
<pre><code class="highlight plaintext">$("#edit_ingredient_<%= @ingredient.id %>").data('remote', true);
</code></pre>
<button class="clipboard-button" data-clipboard-text="$("#edit_ingredient_<%= @ingredient.id %>").data('remote', true);
">Copy</button>
</div>
<p>The form sends a PATCH request (via Javascript), which will be handled
by the <code>update</code> action. This action already specifies
two formats it can handle: html and json:</p><div class="interstitial code">
<pre><code class="highlight plaintext"># PATCH/PUT /ingredients/1
# PATCH/PUT /ingredients/1.json
def update
respond_to do |format|
if @ingredient.update(ingredient_params)
format.html { redirect_to ingredients_path, notice: 'Ingredient was successfully updated.' }
format.json { render :show, status: :ok, location: @ingredient }
else
format.html { render :edit }
format.json { render json: @ingredient.errors, status: :unprocessable_entity }
end
end
end
</code></pre>
<button class="clipboard-button" data-clipboard-text="# PATCH/PUT /ingredients/1
# PATCH/PUT /ingredients/1.json
def update
respond_to do |format|
if @ingredient.update(ingredient_params)
format.html { redirect_to ingredients_path, notice: 'Ingredient was successfully updated.' }
format.json { render :show, status: :ok, location: @ingredient }
else
format.html { render :edit }
format.json { render json: @ingredient.errors, status: :unprocessable_entity }
end
end
end
">Copy</button>
</div>
<p>This limits the allowed format, we have to add <code>.js</code> explicitly:</p><div class="interstitial code">
<pre><code class="highlight plaintext"># PATCH/PUT /ingredients/1
# PATCH/PUT /ingredients/1.json
# PATCH/PUT /ingredients/1.js
def update
respond_to do |format|
if @ingredient.update(ingredient_params)
format.html {
redirect_to ingredients_path, notice: 'Successfully updated.'
}
format.json { render :show, status: :ok, location: @ingredient }
format.js { }
else
format.html { render :edit }
format.json {
render json: @ingredient.errors, status: :unprocessable_entity
}
format.js { }
end
end
end
</code></pre>
<button class="clipboard-button" data-clipboard-text="# PATCH/PUT /ingredients/1
# PATCH/PUT /ingredients/1.json
# PATCH/PUT /ingredients/1.js
def update
respond_to do |format|
if @ingredient.update(ingredient_params)
format.html {
redirect_to ingredients_path, notice: 'Successfully updated.'
}
format.json { render :show, status: :ok, location: @ingredient }
format.js { }
else
format.html { render :edit }
format.json {
render json: @ingredient.errors, status: :unprocessable_entity
}
format.js { }
end
end
end
">Copy</button>
</div>
<p>This will render the view update.js.erb.</p><p>Now we have to make this view handle both cases:
saving <code>@ingredient</code> caused errors or went through
successfully. </p><p>There is a length validation on the ingredient name,
so typing in just one letter should cause an error. Try it out with this code:</p><div class="interstitial code">
<pre><code class="highlight plaintext"><% if @ingredient.errors.any? %>
alert("error: <%= @ingredient.errors.full_messages.first %>");
<% else %>
alert("saved successfully");
<% end %>
</code></pre>
<button class="clipboard-button" data-clipboard-text="<% if @ingredient.errors.any? %>
alert("error: <%= @ingredient.errors.full_messages.first %>");
<% else %>
alert("saved successfully");
<% end %>
">Copy</button>
</div>
<p>In case of success we want to remove the form and replace it with
the new name of the ingredient. This is left as an exercise for the reader.</p></section>
<section><a class='slide_break' href='javascript_and_ajax.html#slide-5'>▻</a>
<h3 id="the-rails-javascript-style"><a class="anchorlink" href="#the-rails-javascript-style"><span>3.3</span> The Rails Javascript Style</a></h3><p>Sending JavaScript from the Server to the client is a very
strange concept at first. But as you have seen you can achieve
a lot with a few lines of jQuery and some clever reuse of existing
templates.</p><p>You may have noticed that the HTML code for the example
app included some additional tags and ids that turned
out to be very helpful in the example.</p><p>This style of using Javascript is very specific to Rails. Other
frameworks use other approaches.</p></section>
<section><a class='slide_break' href='javascript_and_ajax.html#slide-6'>▻</a>
<h3 id="helper-methods"><a class="anchorlink" href="#helper-methods"><span>3.4</span> Helper Methods</a></h3><p>The "Unobtrusive JavaScript" Helpers, actually consist of two
parts: the JavaScript half and the Ruby half.</p><p><a href="https://github.com/rails/jquery-ujs/blob/master/src/rails.js">rails.js</a>
provides the JavaScript half, and the regular Ruby view helpers add
tags and data-attributes to the DOM. The JavaScript in rails.js then listens for these
attributes, and attaches appropriate handlers.</p></section>
<section><a class='slide_break' href='javascript_and_ajax.html#slide-7'>▻</a>
<h4 id="form-for-and-form-tag"><a class="anchorlink" href="#form-for-and-form-tag"><span>3.4.1</span> form_for and form_tag</a></h4><p><a href="https://edgeapi.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#method-i-form_for"><code>form_for</code></a>
and <a href="https://edgeapi.rubyonrails.org/classes/ActionView/Helpers/FormTagHelper.html#method-i-form_tag"><code>form_tag</code></a>
are helpers that assists with writing forms. Both take a <code>:remote</code> option. It works like this:</p><div class="interstitial code">
<pre><code class="highlight erb"><span class="cp"><%=</span> <span class="n">form_for</span><span class="p">(</span><span class="vi">@article</span><span class="p">,</span> <span class="ss">remote: </span><span class="kp">true</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">f</span><span class="o">|</span> <span class="cp">%></span>
...
<span class="cp"><%</span> <span class="k">end</span> <span class="cp">%></span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="<%= form_for(@article, remote: true) do |f| %>
...
<% end %>
">Copy</button>
</div>
<p>This will generate the following HTML:</p><div class="interstitial code">
<pre><code class="highlight html"><span class="nt"><form</span>
<span class="na">accept-charset=</span><span class="s">"UTF-8"</span>
<span class="na">action=</span><span class="s">"/articles"</span>
<span class="na">class=</span><span class="s">"new_article"</span>
<span class="na">data-remote=</span><span class="s">"true"</span>
<span class="na">id=</span><span class="s">"new_article"</span>
<span class="na">method=</span><span class="s">"post"</span><span class="nt">></span>
...
<span class="nt"></form></span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="<form
accept-charset="UTF-8"
action="/articles"
class="new_article"
data-remote="true"
id="new_article"
method="post">
...
</form>
">Copy</button>
</div>
<p>rails.js will remove the <code>data-remote="true"</code> attribute and
add a handler that turns the form into an form submitted by Javascript.</p></section>
<section><a class='slide_break' href='javascript_and_ajax.html#slide-8'>▻</a>
<h4 id="error-handling"><a class="anchorlink" href="#error-handling"><span>3.4.2</span> error handling</a></h4><p>You can handle errors (for example: no answer from the server)
on the client side by listening to the "ajax:error" event:</p><div class="interstitial code">
<pre><code class="highlight js"><span class="nf">$</span><span class="p">(</span><span class="dl">"</span><span class="s2">#new_article</span><span class="dl">"</span><span class="p">).</span><span class="nf">on</span><span class="p">(</span><span class="dl">"</span><span class="s2">ajax:error</span><span class="dl">"</span><span class="p">,</span> <span class="kd">function</span><span class="p">(</span><span class="nx">e</span><span class="p">,</span> <span class="nx">xhr</span><span class="p">,</span> <span class="nx">status</span><span class="p">,</span> <span class="nx">error</span><span class="p">)</span> <span class="p">{</span>
<span class="nf">$</span><span class="p">(</span><span class="dl">"</span><span class="s2">#new_article</span><span class="dl">"</span><span class="p">).</span><span class="nf">append</span><span class="p">(</span><span class="dl">"</span><span class="s2"><p>ERROR </span><span class="dl">"</span> <span class="o">+</span> <span class="nx">error</span> <span class="o">+</span> <span class="dl">"</span><span class="s2"></p></span><span class="dl">"</span><span class="p">);</span>
<span class="p">});</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="$("#new_article").on("ajax:error", function(e, xhr, status, error) {
$("#new_article").append("<p>ERROR " + error + "</p>");
});
">Copy</button>
</div>
<p>These events work for all the helpers discussed here.
You can learn more about the available
events <a href="https://github.com/rails/jquery-ujs/wiki/ajax">in the jquery-ujs wiki</a>.</p></section>
<section><a class='slide_break' href='javascript_and_ajax.html#slide-9'>▻</a>
<h4 id="link-to"><a class="anchorlink" href="#link-to"><span>3.4.3</span> link_to</a></h4><p><a href="https://edgeapi.rubyonrails.org/classes/ActionView/Helpers/UrlHelper.html#method-i-link_to"><code>link_to</code></a>
and its cousin <code>link_to_unless_current</code>
are helper that generate links. Again they have a <code>:remote</code> option:</p><div class="interstitial code">
<pre><code class="highlight erb"><span class="cp"><%=</span> <span class="n">link_to</span> <span class="s2">"an article"</span><span class="p">,</span> <span class="vi">@article</span><span class="p">,</span> <span class="ss">remote: </span><span class="kp">true</span> <span class="cp">%></span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="<%= link_to "an article", @article, remote: true %>
">Copy</button>
</div>
<p>which generates</p><div class="interstitial code">
<pre><code class="highlight html"><span class="nt"><a</span> <span class="na">href=</span><span class="s">"/articles/1"</span> <span class="na">data-remote=</span><span class="s">"true"</span><span class="nt">></span>an article<span class="nt"></a></span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="<a href="/articles/1" data-remote="true">an article</a>
">Copy</button>
</div>
<p>Again rails.js removes the data-attribute and attaches a
hander that deactivates the normal link and uses Javascript
to send the HTTP request instead.</p></section>
<section><a class='slide_break' href='javascript_and_ajax.html#slide-10'>▻</a>
<h4 id="button-to"><a class="anchorlink" href="#button-to"><span>3.4.4</span> button_to</a></h4><p><a href="https://edgeapi.rubyonrails.org/classes/ActionView/Helpers/UrlHelper.html#method-i-button_to"><code>button_to</code></a>
is a shorthand for creating a form that consists of just one button. This
help in using consistent REST requests.</p><div class="interstitial code">
<pre><code class="highlight erb"><span class="cp"><%=</span> <span class="n">button_to</span> <span class="s2">"Delete Image"</span><span class="p">,</span>
<span class="p">{</span> <span class="ss">action: </span><span class="s2">"delete"</span><span class="p">,</span> <span class="ss">id: </span><span class="vi">@image</span><span class="p">.</span><span class="nf">id</span> <span class="p">},</span>
<span class="ss">method: :delete</span><span class="p">,</span>
<span class="ss">data: </span><span class="p">{</span> <span class="ss">confirm: </span><span class="s2">"Are you sure?"</span> <span class="p">}</span> <span class="cp">%></span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="<%= button_to "Delete Image",
{ action: "delete", id: @image.id },
method: :delete,
data: { confirm: "Are you sure?" } %>
">Copy</button>
</div>
<p>this generates</p><div class="interstitial code">
<pre><code class="highlight html"><span class="nt"><form</span> <span class="na">method=</span><span class="s">"post"</span> <span class="na">action=</span><span class="s">"/images/delete/1"</span> <span class="na">class=</span><span class="s">"button_to"</span><span class="nt">></span>
<span class="nt"><input</span> <span class="na">type=</span><span class="s">"hidden"</span> <span class="na">name=</span><span class="s">"_method"</span> <span class="na">value=</span><span class="s">"delete"</span> <span class="nt">/></span>
<span class="nt"><input</span> <span class="na">data-confirm=</span><span class="s">'Are you sure?'</span> <span class="na">value=</span><span class="s">"Delete Image"</span> <span class="na">type=</span><span class="s">"submit"</span> <span class="nt">/></span>
<span class="nt"><input</span> <span class="na">name=</span><span class="s">"authenticity_token"</span> <span class="na">type=</span><span class="s">"hidden"</span> <span class="na">value=</span><span class="s">"10f2...05a6"</span><span class="nt">/></span>
<span class="nt"></form></span>"
</code></pre>
<button class="clipboard-button" data-clipboard-text="<form method="post" action="/images/delete/1" class="button_to">
<input type="hidden" name="_method" value="delete" />
<input data-confirm='Are you sure?' value="Delete Image" type="submit" />
<input name="authenticity_token" type="hidden" value="10f2...05a6"/>
</form>"
">Copy</button>
</div>
</section>
<section><a class='slide_break' href='javascript_and_ajax.html#slide-11'>▻</a>
<h2 id="turbolinks"><a class="anchorlink" href="#turbolinks"><span>4</span> Turbolinks</a></h2><p>Rails ships with the <a href="https://github.com/turbolinks/turbolinks">Turbolinks</a> gem.
Turbolinks are a method of speeding up user interaction with the webpage
without making changes in the backend.</p></section>
<section><a class='slide_break' href='javascript_and_ajax.html#slide-12'>▻</a>
<h3 id="how-turbolinks-works"><a class="anchorlink" href="#how-turbolinks-works"><span>4.1</span> How Turbolinks Works</a></h3><p>Turbolinks attaches a click handler to all <code><a></code> Tags on the page. When
the link is clicked, Turbolinks will make a HTTP request for the page,
parse the response, and replace the entire <code><body></code> of the page with the
<code><body></code> of the response. It will then use the <a href="https://caniuse.com/#search=pushState">HTML History API</a>
to change the URL to the correct one. In unsupported browsers
, Turbolinks gracefully degrades to standard navigation.</p><p>Turbolinks are enabeld by default in new rails applications.</p><p>If you want to disable Turbolinks for certain links, add a <code>data-no-turbolink</code>
attribute to the tag:</p><div class="interstitial code">
<pre><code class="highlight html"><span class="nt"><a</span> <span class="na">href=</span><span class="s">"..."</span> <span class="na">data-no-turbolink</span><span class="nt">></span>No turbolinks here<span class="nt"></a></span>.
</code></pre>
<button class="clipboard-button" data-clipboard-text="<a href="..." data-no-turbolink>No turbolinks here</a>.
">Copy</button>
</div>
<p>If you want to keep an element when the page changes, add the <code>data-turbolinks-permanent</code> attribute,
for example in the layout:</p><div class="interstitial code">
<pre><code class="highlight plaintext"><%= yield %>
<div class="media-player" data-turbolinks-permanent id="music-player">
[audio player should not be reloaded, audio can contiue to play]
</div>
</code></pre>
<button class="clipboard-button" data-clipboard-text="<%= yield %>
<div class="media-player" data-turbolinks-permanent id="music-player">
[audio player should not be reloaded, audio can contiue to play]
</div>
">Copy</button>
</div>
</section>
<section><a class='slide_break' href='javascript_and_ajax.html#slide-13'>▻</a>
<h3 id="page-change-events"><a class="anchorlink" href="#page-change-events"><span>4.2</span> Page Change Events</a></h3><p>When writing JavaScript, you'll often want to do some sort of processing upon
page load. With jQuery, you'd write something like this:</p><div class="interstitial code">
<pre><code class="highlight js"><span class="nf">$</span><span class="p">(</span><span class="kd">function</span><span class="p">(){</span>
<span class="nf">alert</span><span class="p">(</span><span class="dl">"</span><span class="s2">page has loaded!</span><span class="dl">"</span><span class="p">);</span>
<span class="p">});</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="$(function(){
alert("page has loaded!");
});
">Copy</button>
</div>
<p>However, because Turbolinks overrides the normal page loading process, the
event that this relies on will not be fired. If you have code that looks like
this, you must change it to:</p><div class="interstitial code">
<pre><code class="highlight js"><span class="nf">$</span><span class="p">(</span><span class="nb">document</span><span class="p">).</span><span class="nf">on</span><span class="p">(</span><span class="dl">"</span><span class="s2">turbolinks:load</span><span class="dl">"</span><span class="p">,</span> <span class="kd">function</span><span class="p">(){</span>
<span class="nf">alert</span><span class="p">(</span><span class="dl">"</span><span class="s2">page has loaded!</span><span class="dl">"</span><span class="p">);</span>
<span class="p">});</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="$(document).on("turbolinks:load", function(){
alert("page has loaded!");
});
">Copy</button>
</div>
<p>For more details, including other events you can bind to, check out <a href="https://github.com/turbolinks/turbolinks/blob/master/README.md">the
Turbolinks
README</a>.</p></section>
<section><a class='slide_break' href='javascript_and_ajax.html#slide-14'>▻</a>
<h2 id="other-resources"><a class="anchorlink" href="#other-resources"><span>5</span> Other Resources</a></h2><p>Here are some helpful links to help you learn even more:</p>
<ul>
<li><a href="https://guides.rubyonrails.org/working_with_javascript_in_rails.html">Original Rails Guide Working with JavaScript</a>.</li>
<li><a href="https://github.com/rails/jquery-ujs/wiki">jquery-ujs wiki</a></li>
<li><a href="https://github.com/rails/jquery-ujs/wiki/ajax">Events created by Rails AJAX</a></li>
<li><a href="http://railscasts.com/episodes/205-unobtrusive-javascript">Railscasts: Unobtrusive JavaScript</a></li>
<li><a href="https://www.youtube.com/watch?v=SWEts0rlezA">RailsConf 2016 - Turbolinks 5</a> video of the talk on using turbolinks on ios and android</li>
</ul>
</div></section>
</div>
</div>
<!-- End slides. -->
<!-- Required JS files. -->
<script src="javascripts/reveal.js"></script>
<script src="javascripts/search.js"></script>
<script src="javascripts/markdown.js"></script>
<script>
// Also available as an ES module, see:
// https://revealjs.com/initialization/
Reveal.initialize({
controls: false,
progress: true,
center: false,
hash: true,
// The "normal" size of the presentation, aspect ratio will
// be preserved when the presentation is scaled to fit different
// resolutions. Can be specified using percentage units.
width: 1000,
height: 600,
disableLayout: false,
// Factor of the display size that should remain empty around
// the content
margin: 0.05,
// Bounds for smallest/largest possible scale to apply to content
minScale: 0.2,
maxScale: 10.0,
keyboard: {
27: () => {
// do something custom when ESC is pressed
var new_url = window.location.pathname.replace('slides_', '') + window.location.hash.replace('/','slide-');
window.location = new_url;
},
191: 'toggleHelp',
13: 'next', // go to the next slide when the ENTER key is pressed
},
// Learn about plugins: https://revealjs.com/plugins/
plugins: [ RevealSearch, RevealMarkdown ]
});
</script>
</body>
</html>