-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathrails-javascript-include-external.html
More file actions
756 lines (756 loc) · 69.9 KB
/
rails-javascript-include-external.html
File metadata and controls
756 lines (756 loc) · 69.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
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
<!DOCTYPE html>
<!--[if IE 9]><html class="lt-ie10" lang="en" > <![endif]-->
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>Unholy Rails: Adding JavaScript to Rails · RailsApps</title>
<meta name="viewport" content="width=device-width">
<link href="https://plus.google.com/117374718581973393536" rel="publisher">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/foundation/5.2.2/css/normalize.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/foundation/5.2.2/css/foundation.min.css">
<link rel="stylesheet" href="http://railsapps.github.io/css/railsapps.css" />
<link rel="stylesheet" href="http://railsapps.github.io/css/syntax.css" />
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/modernizr/2.6.2/modernizr.min.js"></script>
<script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0];if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src="http://platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs");</script>
<script type="text/javascript">
window.analytics=window.analytics||[],window.analytics.methods=["identify","group","track","page","pageview","alias","ready","on","once","off","trackLink","trackForm","trackClick","trackSubmit"],window.analytics.factory=function(t){return function(){var a=Array.prototype.slice.call(arguments);return a.unshift(t),window.analytics.push(a),window.analytics}};for(var i=0;i<window.analytics.methods.length;i++){var key=window.analytics.methods[i];window.analytics[key]=window.analytics.factory(key)}window.analytics.load=function(t){if(!document.getElementById("analytics-js")){var a=document.createElement("script");a.type="text/javascript",a.id="analytics-js",a.async=!0,a.src=("https:"===document.location.protocol?"https://":"http://")+"cdn.segment.io/analytics.js/v1/"+t+"/analytics.min.js";var n=document.getElementsByTagName("script")[0];n.parentNode.insertBefore(a,n)}},window.analytics.SNIPPET_VERSION="2.0.9",
window.analytics.load("l1stqfqqbf");
window.analytics.page();
window.analytics.ready(function () {
ga('require', 'linker');
ga('linker:autoLink', ['railscomposer.com','learn-rails.com','blog.railsapps.org','tutorials.railsapps.org']);
});
</script>
</head>
<body>
<div class="fixed">
<nav class="top-bar" data-topbar>
<ul class="title-area">
<li class="name">
<a href="http://railsapps.github.io/" class="brand">RailsApps Project</a>
</li>
<li class="toggle-topbar menu-icon"><a href="#"><span>Menu</span></a></li>
</ul>
<section class="top-bar-section">
<ul class="right">
<li><a href="https://tutorials.railsapps.org/" class="google">Tutorials</a></li>
<li><a href="http://twitter.com/rails_apps" class="twitter">Twitter</a></li>
<li><a href="http://blog.railsapps.org/" class="twitter">Blog</a></li>
<li><a href="https://github.com/RailsApps" class="github">GitHub Repository</a></li>
</ul>
</section>
</nav>
</div>
<div class="row">
<div class="large-12 columns">
<div class="content wikistyle gollum textile">
<h1>Unholy Rails: Adding JavaScript to Rails</h1>
<h4>by Daniel Kehoe</h4>
<p><em>Last updated 31 December 2012</em></p>
<p>Rails and JavaScript topics: How to include external JavaScript files and jQuery plugins in Rails 3.1 or newer. Using page-specific JavaScript in Rails. Organizing JavaScript files. Passing parameters to JavaScript.</p>
<p>What is the best way to add a JavaScript library to a Rails application? Use the Rails asset pipeline? Or include directly in a view template? This article explains what to do when your application is not wholly Rails.</p>
<h4>If You Are New to Rails</h4>
<p>If you’re new to Rails, see <a href="http://railsapps.github.io/what-is-ruby-rails.html">What is Ruby on Rails?</a>, the book <a href="http://learn-rails.com/learn-ruby-on-rails.html">Learn Ruby on Rails</a>, and recommendations for a <a href="https://tutorials.railsapps.org/rails-tutorial">Rails tutorial</a>.</p>
<h4><a href="http://railsapps.github.io/"><img src="http://railsapps.github.io/images/join/join-railsapps.png" title="Join RailsApps" alt="Join RailsApps"></a></h4>
<h4>What is the RailsApps Project?</h4>
<p>This is an article from the RailsApps project. The <a href="http://railsapps.github.io/">RailsApps project</a> provides example applications that developers use as starter apps. Hundreds of developers use the apps, report problems as they arise, and propose solutions. Rails changes frequently; each application is known to work and serves as your personal “reference implementation.” Each application is accompanied by a tutorial so there is no mystery code. Support for the project comes from subscribers. Please accept our invitation to <a href="http://railsapps.github.io/">join the RailsApps project</a>.</p>
<h2 id="contents">Contents</h2>
<ul>
<li><a href="#summary">Rules of Thumb</a></li>
<li><a href="#principles">Principles for Performance</a></li>
<li><a href="#missing">JavaScript’s Missing Directive</a></li>
<li><a href="#pipeline">Rails and the Asset Pipeline</a></li>
<li><a href="#locations">Where to Stick Your JavaScript</a></li>
<li><a href="#external">External Scripts</a></li>
<li><a href="#specific">Page-Specific JavaScript</a></li>
<li><a href="#edgecases">Edge Cases</a></li>
<li><a href="#parameters">Passing Parameters to JavaScript</a></li>
<li><a href="#comment">Comments</a></li>
</ul>
<h2 id="summary">Rules of Thumb</h2>
<p>In summary, here are rules of thumb to guide your use of JavaScript in Rails:</p>
<ul>
<li>Logically organize your site-wide scripts in the <strong>app/assets/javascripts/</strong> folder.</li>
<li>Copy external JavaScript libraries (such as jQuery plugins) to the <strong>vendor/assets/javascripts</strong> folder.</li>
<li>Let the Rails asset pipeline combine them all in one minimized <strong>application.js</strong> file.</li>
<li>List scripts in the <strong>app/assets/javascripts/application.js</strong> manifest.</li>
</ul>
<p>In almost all applications, there is no need to add external JavaScript libraries directly in a view template. Use the Rails asset pipeline, even for JavaScript used on just one page (page-specific JavaScript). Copy external scripts to your application and you’ll gain the performance benefits of the Rails asset pipeline and avoid complexity.</p>
<p>The Rails asset pipeline will become even more important in Rails 4.0 with the new <a href="https://github.com/rails/turbolinks">Turbolinks</a> feature. Turbolinks improves performance by keeping the current page instance alive and replacing only the page <span class="caps">BODY</span> (plus the title in the <span class="caps">HEAD</span>). As long as the <span class="caps">HEAD</span> element is identical between pages, the Turbolinks mechanism can deliver its “turbo” speed boost. This adds to the importance of avoiding any extra script tags on specific pages.</p>
<h2 id="principles">Principles for Performance</h2>
<p>It’s difficult to sort out all the advice and folklore around Rails and JavaScript. Here are basic principles to improve website performance.</p>
<p>JavaScript is single-threaded, meaning that only one operation can be performed at a time. The browser can only be executing JavaScript or rendering the UI at any moment.</p>
<p>Downloading files takes much longer than parsing and executing browser code. Modern web browsers can download files (scripts, <span class="caps">CSS</span> files, or images) in parallel. Modern web browsers cache files to minimize download requests, both within a site and across sites (in the case of popular JavaScript libraries such as Google Analytics or jQuery). But even with parallel downloads and caching, multiple files can be slower than single files.</p>
<p>Content delivery networks (CDNs) are faster at delivering popular JavaScript libraries than your own web server. However, once a file is cached (after the first download), CDNs offer no advantages. CDNs make sense for landing pages (the first page that a visitor encounters) but not so much for every page in a large site (where caching is at work). CDNs for popular JavaScript libraries offer no advantages if a visitor has a library cached from a visit to another site.</p>
<p>You can easily and cheaply set up a <span class="caps">CDN</span> for your own application with a service such as <a href="http://en.wikipedia.org/wiki/Amazon_CloudFront">CloudFront</a>. This gives you the advantage of a <span class="caps">CDN</span> for files you’ve added to the Rails asset pipeline, allowing you to combine popular JavaScript libraries with your own code for first-page delivery faster than your own server. But again, the only advantage is for first-page delivery.</p>
<p>Inline JavaScript (mixed in your <span class="caps">HTML</span> code) blocks loading and rendering the page. Plus it is messy to mix JavaScript, Ruby, and <span class="caps">HTML</span> in a view template. Keep JavaScript (or CoffeeScript) in its own files in the Rails assets directories.</p>
<p>The fewer <code><script></code> tags you use, the faster your pages will load. Modern web browsers download scripts in parallel but each script tag has to be parsed and evaluated to determine if a file is cached and current. Dynamic loading (from within another script) is faster than using an additional script tag.</p>
<p>Scripts that are concatenated into a single file (such as <em>application.js</em> with the Rails asset pipeline) minimize download time and can be cached for site-wide use.</p>
<p>External JavaScript libraries can be copied and concatenated into a single file to minimize download time. Make your own copy of an external library when your application requires a specific version. When you want to rely on a third party to update and maintain the library, don’t make a copy; use dynamic loading.</p>
<p>External JavaScript libraries that are likely to be cached from visits to other websites can be dynamically loaded from within your local JavaScript code. Dynamically loading scripts allows use of cached files, allows loading scripts asnychronously, and eliminates the overhead of parsing and evaluating multiple script tags.</p>
<p>Certain external JavaScript libraries that introduce security vulnerabilities, such as code that handles credit cards, should not be copied into your application asset directories. Instead, include the external script in an <em>application.js</em> script through dynamic loading.</p>
<p>When a single <em>application.js</em> script combines JavaScript used site-wide with JavaScript intended for use on individual pages, conditional execution of page-specific JavaScript tied to elements on an individual page reduces execution overhead.</p>
<p>In most cases, downloading a single script that combines site-wide and page-specific JavaScript (for a first page) and reading from a cache (for subsequent pages) will take less time than downloading multiple scripts on individual pages. The exception to this rule could be a very lengthy script that is used on only a single page that is not visited by most of the site’s visitors (for example, an administrative page). This exceptional case merits adding an additional script to an individual page using a second script tag, rather than including page-specific “big code” in the <em>application.js</em> script. Only performance testing can tell you whether this optimization is warranted.</p>
<p>Finally, a word about persistent folklore. You may have encountered the frequently repeated advice to “always place JavaScript at the bottom of the page just before the closing <code></body></code> tag”. This was once true because web browsers loaded scripts sequentially and blocked loading and rendering until each script was complete. This is no longer true; modern browsers do “preload scanning” and begin loading all scripts in parallel, whether listed in the head element or at the bottom of the page. External JavaScript often is loaded asynchronously and is written so it won’t execute until the page is loaded and the <span class="caps">DOM</span> is ready. Loading a script in the head element is no longer a bad practice.</p>
<p>For a deeper and more detailed look at recommended practices for using JavaScript in a web application, look to advice from web performance optimization experts such as <a href="http://stevesouders.com/">Steve Souders</a> and <a href="http://www.nczonline.net/blog/">Nicholas C. Zakas</a>.</p>
<p>Now that we’ve considered principles to guide our evaluation, let’s look at the specifics of using JavaScript in Rails. But first, step back and consider why this is so complicated.</p>
<h2 id="missing">JavaScript’s Missing Directive</h2>
<p>The C language has <code>#include</code>, Java has <code>import</code>, Perl has <code>use</code> or <code>require</code>, <span class="caps">PHP</span> has <code>include</code> or <code>require</code>, and Ruby has <code>require</code>. These directives add the contents of one file into another. Often these directives are used to incorporate code libraries provided by other developers. Some languages also have a package manager that provides a standard format for distributing programs and libraries (Ruby has RubyGems). What’s the equivalent in JavaScript? Nothing. JavaScript doesn’t have a native package manager or import directive.</p>
<p>Instead, you’re expected to include all the JavaScript files you require for a web page in a series of <span class="caps">HTML</span> <code><script></code> tags typically placed between the <code><head></code> tags at the top of an <span class="caps">HTML</span> file. The order of placement is important. The web browser compiles the JavaScript code sequentially. If your code requires an external library, the external script must be listed first. Each <code><script></code> tag requires a separate download and introduces a delay. JavaScript’s “missing include” leaves framework developers looking for ways to improve performance.</p>
<p>That’s where the Rails asset pipeline comes in.</p>
<h2 id="pipeline">Rails and the Asset Pipeline</h2>
<p>Rails 3.1 introduced the asset pipeline in August 2011.</p>
<p>Before 3.1, Rails did little to manage JavaScript. Developers used the <code>javascript_include_tag</code> helper to construct a <code><script></code> tag and add scripts directly to a view template or application layout. Before 3.1, developers used the helper to add every script required for an application.</p>
<p>The Rails asset pipeline improves website performance by concatenating multiple JavaScript files into a single script, allowing the developer to segregate code in separate files for development efficiency, but eliminating the performance penalty of multiple <code><script></code> tags.</p>
<p>The Rails asset pipeline adds some of the functionality of a package manager for project-specific JavaScript code. You can organize multiple JavaScript files in the <strong>app/assets/javascripts</strong> folder. The default <strong>app/assets/javascripts/application.js</strong> file serves as a manifest file, specifying which files you require. By default, the file’s <code>//= require_tree .</code> recursively includes all JavaScript files in the <strong>app/assets/javascripts</strong> directory. Sprockets, the mechanism that powers the Rails asset pipeline, will concatenate and minify all the specified JavaScript files into a single <em>application.js</em> script which you can include in your application layout with the <code><%= javascript_include_tag "application" %></code> statement. Sprockets also performs preprocessing so you can write JavaScript as CoffeeScript or include Ruby code as an <span class="caps">ERB</span> file. Order of execution is still important; a manifest file must list each JavaScript file in dependent order.</p>
<p>For more about the Rails asset pipeline, see:</p>
<ul>
<li>Rails Guide <a href="http://guides.rubyonrails.org/asset_pipeline.html">Asset Pipeline</a>
</li>
<li>Railscasts <a href="http://railscasts.com/episodes/279-understanding-the-asset-pipeline">Understanding the Asset Pipeline</a>
</li>
<li>Eric Berry’s <a href="http://coderberry.me/blog/2012/04/24/asset-pipeline-for-dummies/">Asset Pipeline for Dummies</a>
</li>
</ul>
<p>The Rails asset pipeline is innovative and useful. For the simplest use case, where a developer intends to use multiple scripts on every page of an application, the Rails asset pipeline is a no-brainer. But documentation for the Rails asset pipeline offers no guidance for two common implementation requirements: JavaScript libraries obtained from third parties (such as jQuery plugins) and scripts that are only used on a single page (page-specific JavaScript).</p>
<p>This article addresses these concerns.</p>
<h2 id="locations">Where to Stick Your JavaScript</h2>
<p>Whether you use the Rails asset pipeline or add a <code><script></code> tag directly to a view, you have to make a choice about where to put any local JavaScript file.</p>
<p>We have a choice of three locations for a local JavaScript file:</p>
<ul>
<li>the <strong>app/assets/javascripts</strong> folder</li>
<li>the <strong>lib/assets/javascripts</strong> folder</li>
<li>the <strong>vendor/assets/javascripts</strong> folder</li>
</ul>
<p>Here are guidelines for selecting a location for your scripts:</p>
<ul>
<li>Use <strong>app/assets/javascripts</strong> for JavaScript you create for your application.</li>
<li>Use <strong>lib/assets/javascripts</strong> for scripts that are shared by many applications (but use a gem if you can).</li>
<li>Use <strong>vendor/assets/javascripts</strong> for copies of jQuery plugins, etc., from other developers.</li>
</ul>
<p>In the simplest case, when all your JavaScript files are in the <strong>app/assets/javascripts</strong> folder, there’s nothing more you need to do.</p>
<p>Add JavaScript files anywhere else and you will need to understand how to modify a manifest file.</p>
<h3 id="manifest">Mysterious Manifests</h3>
<p>There are two kinds of files in a JavaScript assets folder:</p>
<ul>
<li>ordinary JavaScript files</li>
<li>manifest files</li>
</ul>
<p>You can also have CoffeeScript files and <span class="caps">ERB</span> files which are variations on ordinary JavaScript files.</p>
<p>Manifest files have the same <em>.js</em> file extension as ordinary JavaScript files. Manifest files and ordinary JavaScript files can be combined in a single file. This makes manifest files mysterious, or at least non-obvious.</p>
<p>The default <strong>app/assets/javascripts/application.js</strong> file is a manifest file. It’s a manifest file because it contains directives:</p>
<pre>
//= require jquery
//= require jquery_ujs
//= require_tree .
</pre>
<p>Directives tell Sprockets which files should be combined to build a single JavaScript script. Each file that contains manifest directives becomes a single JavaScript script with the same name as the original manifest file. Thus the <strong>app/assets/javascripts/application.js</strong> manifest file becomes the <em>application.js</em> script.</p>
<p>All scripts in the <strong>app/assets/javascripts</strong> folder are automatically added to the default <em>application.js</em> script when the manifest file includes the default <code>//= require_tree .</code> directive. See below for suggestions why you might want to change this default.</p>
<p>If you add a script to the <em>vendor/…</em> folder and you wish to have it combined with your project code in the <em>application.js</em> script for use throughout your application, you must specify it with a directive in the manifest (details below). The same is true for the <em>lib/…</em> folder.</p>
<h3 id="organizing">Organizing Your Scripts</h3>
<p>Rails is all about following conventions to save effort and simplify teamwork. But there is no well-known and accepted practice for organizing your JavaScript files. Here’s advice I’ve found about organizing your scripts:</p>
<ul>
<li>Jerod Santo’s <a href="http://blog.jerodsanto.net/2012/02/a-simple-pattern-to-namespace-and-selectively-execute-certain-bits-of-javascript-depending-on-which-rails-controller-and-action-are-active/">A Simple Pattern to Namespace…</a>
</li>
<li>Ken Collins’s <a href="https://speakerdeck.com/u/metaskills/p/secrets-of-the-asset-pipeline">Secrets Of The Asset Pipeline</a>
</li>
</ul>
<p>Leave a <a href="#comment">comment</a> below if you’d like to suggest ways to organize your JavaScript files.</p>
<h4>Using the Paloma Gem</h4>
<p>The <a href="https://github.com/kbparagua/paloma">Paloma gem</a> offers an easy way to organize JavaScript files using the Rails asset pipeline. It also provides a capability to execute page-specific JavaScript.</p>
<p>Read on for my advice about organizing your JavaScript files.</p>
<h3 id="default">Default Locations</h3>
<p>In a simple application, you can collect all the JavaScript files in the <strong>app/assets/javascripts</strong> folder and rely on the default <code>//= require_tree .</code> directive to combine the scripts into a single <em>application.js</em> script. Here we add <strong>google-analytics.js</strong> and <strong>admin.js</strong> files to the default directory.</p>
<pre>
+-javascripts/
| +-application.js (manifest)
| +-google-analytics.js
| +-admin.js
+-stylesheets/
</pre>
<p>There’s nothing to configure and it works as long as you don’t have any requirements to load the scripts in a particular order. Here’s the default <strong>app/assets/javascripts/application.js</strong> manifest file:</p>
<pre>
//= require jquery
//= require jquery_ujs
//= require bootstrap
//= require_tree .
</pre>
<p>The jQuery and Twitter Bootstrap scripts are included from gems and all scripts in the default directory are included.</p>
<p>In a complex application, use subdirectories to organize your scripts. Here are suggestions.</p>
<h3 id="sitewide">Site-wide Scripts</h3>
<p>You can create a folder <strong>app/assets/javascripts/sitewide</strong> for scripts that are used on all (or many) pages of the application. Here we place the <strong>google-analytics.js</strong> file in a directory we use for site-wide scripts:</p>
<pre>
+-javascripts/
| +-application.js (manifest)
| +-sitewide/
| | +-google_analytics.js
+-stylesheets/
</pre>
<p>In the manifest file <strong>app/assets/javascripts/application.js</strong>, remove the <code>//= require_tree .</code> directive and replace it with <code>//= require_tree ./sitewide</code> to automatically include all scripts in the <em>sitewide</em> directory.</p>
<pre>
//= require jquery
//= require jquery_ujs
//= require bootstrap
//= require_tree ./sitewide
</pre>
<p>The jQuery and Twitter Bootstrap scripts are included from gems and any scripts in a <em>sitewide</em> directory are included.</p>
<p>There’s nothing more you need to do for site-wide scripts.</p>
<h3 id="specific">Page-Specific Scripts</h3>
<p>Following the principles described above, you will frequently include page-specific JavaScript in the <em>application.js</em> script to be combined with site-wide scripts. This can be confusing, so think about it for a minute. We call the scripts “page-specific” because we intend to use them on only one (or a few) pages. We want to segregate them in the assets directory and give them a name that corresponds to the controller, view template, or feature where the scripts will be used. Segregating the scripts serves us in development by organizing our files. However, they actually become available “site wide” because they are concatenated into the <em>application.js</em> script. This gives us the performance benefits of the asset pipeline and a single script. It’s up to us as developers to write the JavaScript code to only execute on a specific page where it is needed, at which point it again becomes page-specific.</p>
<p>If you have only a few page-specific scripts, place them in the top-level <strong>app/assets/javascripts</strong> folder. For example, you might have an <em>admin.js</em> script you use on only a few administrative pages. Add it as an <strong>app/assets/javascripts/admin.js</strong> file:</p>
<pre>
+-javascripts/
| +-application.js (manifest)
| +-admin.js
| +-sitewide/
| | +-google_analytics.js
+-stylesheets/
</pre>
<p>You’ll need to explicitly specify this script in the <strong>app/assets/javascripts/application.js</strong> manifest file if you’ve removed the <code>//= require_tree .</code> directive as described above. Note that we drop the file extension when we specify the filename in the manifest file. Set up the <strong>app/assets/javascripts/application.js</strong> manifest file like this:</p>
<pre>
//= require jquery
//= require jquery_ujs
//= require bootstrap
//= require_tree ./sitewide
//= require admin
</pre>
<p>The jQuery and Twitter Bootstrap scripts are included from gems; any scripts in a <em>sitewide</em> directory are included; and the <em>admin.js</em> script is included.</p>
<h3 id="namespace">Namespaces for Complex Applications</h3>
<p>In a complex application, you can use the assets directory structure to organize scripts for a “namespacing” effect. Choose an organizational structure that suits your application.</p>
<p>In this example, we have a single <em>admin.js</em> script that is used with all the views rendered by an AdminController. We have <em>articles.js</em> and <em>comments.js</em> scripts that correspond to view templates or partials that are used with a ContentController. You might consider another organizational scheme; the folder and file names can be anything that makes sense to you.</p>
<pre>
+-javascripts/
| +-application.js (manifest)
| +-admin/
| | +-admin.js
| +-content/
| | +-articles.js
| | +-comments.js
| +-sitewide/
| | +-google_analytics.js
+-stylesheets/
</pre>
<p>You’ll need to explicitly specify each script in the <strong>app/assets/javascripts/application.js</strong> manifest file. Set up the <strong>app/assets/javascripts/application.js</strong> manifest file like this:</p>
<pre>
//= require jquery
//= require jquery_ujs
//= require bootstrap
//= require_tree ./sitewide
//= require ./admin/admin
//= require ./content/articles
//= require ./content/comments
</pre>
<p>The jQuery and Twitter Bootstrap scripts are included from gems; any scripts in a <em>sitewide</em> directory are included; and the scripts in the <em>admin</em> and <em>content</em> directories are explicitly included.</p>
<h3>Using the Paloma Gem</h3>
<p>If you use the <a href="https://github.com/kbparagua/paloma">Paloma gem</a>, you’ll organize JavaScript files like this:</p>
<pre>
+-javascripts/
| +-application.js (manifest)
| +-paloma/
| | +-users/ (assuming you have a UsersController)
| | | +-new.js (assuming you have JavaScript you want to execute only for the "new" action)
| | +-foobars/ (assuming you have a FoobarsController)
| | | +-show.js (assuming you have JavaScript you want to execute only for the "show" action)
+-stylesheets/
</pre>
<p>This structure is ideal for executing page-specific (or controller-specific) JavaScript.</p>
<h3>Create Your Own Gems When You Can</h3>
<p>By convention, <strong>lib/assets/javascripts</strong> is intended for scripts used on more than one project. Consider putting these in a gem to gain the benefits of RubyGems as a package manager if you use these scripts across different applications. You get easy version management and you can <code>bundle update <gem></code> when you need the latest version of a JavaScript library.</p>
<p>The Rails asset pipeline will recognize scripts from any gem that contains files in these locations. If you wish to have a script from a gem combined with your project code in the <em>application.js</em> script for use throughout your application, you must specify it with a directive in the manifest. The examples above show how jQuery and Twitter Bootstrap scripts are included from gems.</p>
<p>Here’s where to place scripts in gems:</p>
<ul>
<li>
<strong>app/assets/javascripts</strong> will probably not be used for scripts in a gem</li>
<li>
<strong>lib/assets/javascripts</strong> for scripts you share across your own projects</li>
<li>
<strong>vendor/assets/javascripts</strong> for gemified jQuery plugins, etc., from open source projects</li>
</ul>
<p>Here’s an article that shows how to create a gem containing JavaScript code:</p>
<ul>
<li>Stephen Ball’s <a href="http://rakeroutes.com/blog/write-a-gem-for-the-rails-asset-pipeline/">How to Write (and Test) a Gem to Serve Static Files on the Rails Asset Pipeline</a>
</li>
</ul>
<h3>Use Open Source Gems</h3>
<p>It is ideal to use gemified versions of JavaScript code from open source projects such as jQuery because this gives you the advantage of RubyGems as a package manager. The <a href="https://github.com/joliss/jquery-ui-rails">jquery-ui-rails</a> gem from Jo Liss is an excellent example.</p>
<p>Unfortunately, few JavaScript projects are intended solely for Rails so there seldom is a gemified version of the JavaScript. Instead, the files are offered for downloading or from a content delivery network as external scripts.</p>
<p>External scripts are where JavaScript in Rails gets complicated.</p>
<h2 id="external">External Scripts</h2>
<p>The Rails asset pipeline is a powerful tool for managing project-specific JavaScript code; however, it doesn’t offer a facility for managing JavaScript files that are obtained outside of your own application. External scripts, those that are downloaded by a web browser from a remote web server, can be handled in three ways:</p>
<ul>
<li>copied locally and managed with the asset pipeline</li>
<li>dynamically loaded from within another JavaScript file using a little-known Ajax technique</li>
<li>added to an application layout or view template with a <code>script</code> tag</li>
</ul>
<p>The organizational and performance benefits of the Rails asset pipeline eliminate many reasons to include scripts from external servers. In most cases, you’ll want to copy the external script locally and manage it with the asset pipeline. I’ll show you how to do that below.</p>
<p>Consider the benefits of the Rails asset pipeline. When there is a only a single <em>application.js</em> script to download, the browser will cache it on first encounter and after that will load it from the browser cache. Each script you add directly to a view template using the <code>script</code> tag or the <code>javascript_include_tag</code> helper will require an additional server hit.</p>
<p>For small applications with only a few pages, place the JavaScript code in its own file. Then let Sprockets concatenate and minify all your JavaScript files into a single <em>application.js</em> script. The default directive <code>//= require_tree .</code> in the <strong>app/assets/javascripts/application.js</strong> manifest file will recursively include all JavaScript files in the <strong>app/assets/javascripts</strong> directory. Or remove the <code>//= require_tree .</code> directive and list each file individually. The script will be available throughout the application but you’ll only use it on a few pages. I’ll show you how to limit execution to a single page below.</p>
<p>For large applications, it may seem the browser will be more efficient if each page only gets the script it needs. In fact, the Rails asset pipeline will be faster delivering a single <em>application.js</em> file in almost all cases.</p>
<p>You’ll only know if there’s a performance benefit to including an external script separately if you actually profile performance. For rudimentary analysis, use the <em>Network</em> tab in the WebKit developer tool (in Chrome or Safari) or Firebug (in Firefox). You can use the <a href="http://developer.yahoo.com/yslow/">Yslow</a> tool for a detailed analysis of web page performance factors (see an article from New Relic on <a href="http://blog.newrelic.com/2012/09/04/improving-site-performance-with-yslow/">Improving Site Performance with YSlow</a>). By far the best tool for analysis of web page performance is the free <a href="http://www.webpagetest.org/">WebPagetest.org</a>.</p>
<h3 id="copy">Copy External Scripts Locally</h3>
<p>It’s easiest to simply copy an external script to your own application. By convention, the preferred location is in the <strong>vendor/assets/javascripts</strong> folder. It will work in the <strong>app/assets/javascripts</strong> folder but that’s not where it belongs.</p>
<p>Potential headaches with shifting versions can be minimized by using Michael Grosser’s <a href="https://github.com/grosser/vendorer">vendorer</a> gem to install and update external scripts.</p>
<p>To make a script available as part of the site-wide <em>application.js</em> script, you must specify it in your <strong>app/assets/javascripts/application.js</strong> manifest. Here we add an <strong>vendor/assets/javascripts/jquery.validate.min.js</strong> file:</p>
<pre>
//= require jquery
//= require jquery_ujs
//= require jquery.validate.min
</pre>
<p>In most cases, it is best to copy an external script locally and let the Rails asset pipeline combine it with your own project code. You’ll avoid complexity, gain the benefit of managing all your JavaScript in one place, and (in most cases) gain performance benefits.</p>
<p>Now let’s consider the edge cases where it makes sense to include an an external script from an external server.</p>
<h3>Using External Scripts</h3>
<p>If a script is delivered by a content delivery network, likely to be cached in a web browser by visits to other sites, and used throughout your application, you might include an external script from an external server. The Google Analytics tracking script is an example.</p>
<p>You’d also want to use an external script when copying the script locally would introduce a security vulnerability. Scripts that process credit card data are an example.</p>
<p>Here we’ll look closely at how to use external scripts. First, we’ll consider how an external script interacts wih local scripts (dependencies). Then we’ll look at options for including external scripts in the asset pipeline (the preferred approach). Finally we’ll look at the how to include an external script directly in a view template as page-specific JavaScript (only for edge cases where performance optimization requires it).</p>
<h3 id="none">No Dependencies</h3>
<p>Some external scripts work independently of your project-specific JavaScript code. For example, you might add the HTML5Shiv script to support HTML5 tags in old versions of Internet Explorer. Your own JavaScript would not be dependent on the file. You don’t have to worry about the order in which it is loaded or executed.</p>
<h3 id="simple">Simple Dependencies</h3>
<p>Some external scripts have simple dependency chains.</p>
<p>Suppose you are going to use the Google Maps <span class="caps">API</span> throughout your application. You could add this to your application layout file:</p>
<pre>
<%= javascript_include_tag 'http://maps.googleapis.com/maps/api/js?sensor=false' %>
<%= javascript_include_tag 'application' %>
</pre>
<p>If you are not going to copy the external file to the Rails asset pipeline, you would load the Google Maps <span class="caps">API</span> before your <em>application.js</em> script with a <code><%= javascript_include_tag %></code> helper tag. Then you could write JavaScript code that uses methods from the Google Maps <span class="caps">API</span>. This is a crude but functional approach; it adds an additional <code><script></code> tag to your web pages but the first-page performance drawback of the additional <code><script></code> tag is minimized by the possibility that the Google Maps JavaScript library may have been cached in the browser by a visit to another website. On subsequent pages, the script will be available in the cache but the <code><script></code> tag still needs to be evaluated. To improve performance, consider dynamic loading (described below).</p>
<h3 id="complex">Complex Dependencies</h3>
<p>Now consider the problem of external scripts that are dependent on jQuery. For example, you might wish to use a jQuery plugin. It has to be loaded after the <em>application.js</em> script which loads jQuery.</p>
<p>You could set up your application layout like this:</p>
<pre>
<%= javascript_include_tag 'application' %>
<%= javascript_include_tag 'http://ajax.aspnetcdn.com/ajax/jquery.validate/1.9/jquery.validate.min.js' %>
<%= javascript_include_tag 'code_that_uses_the_validation_plugin' %>
</pre>
<p>This is the kind of complexity that the asset pipeline is intended to eliminate. Instead of segregating your code and loading it as a separate file, use the asset pipeline. There are two ways to load the jQuery plugin in the middle of the <em>application.js</em> script. You can copy the external library to your own application as described above. Or you can include an external JavaScript file from within a local JavaScript file, which apparently cannot be done.</p>
<h3 id="including">Dynamic Loading</h3>
<p>I said that <em>apparently</em> one cannot insert an external script in the middle of the asset pipeline. In fact it can be done, despite JavaScript’s lack of an import directive. You <em>can</em> include a JavaScript file from within a JavaScript file.</p>
<p>The technique is used on millions of web pages and you may have used it without realizing it:</p>
<pre>
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-XXXXXXX-XX']);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</pre>
<p>Does that look familar? It’s the Google Analytics tracking code. It creates a tiny script using <code>document.createElement</code> and inserts it into the page where it dynamically and asynchronously downloads the full Google Analytics script.</p>
<p>The Google Analytics tracking code is delivered by a content delivery network, likely to be cached in a web browser by visits to other sites, and used throughout an application. In this case, the Rails asset pipeline doesn’t offer any performance advantages so you might want to access the Google Analytics tracking code from Google’s servers. Consider that Google may update and revise the tracking script and you have one more good reason to use Google’s servers.</p>
<p>You could add it directly to the application layout. Instead, you can use the asset pipeline and include it from within another JavaScript file. Using the asset pipeline gives you two benefits. You avoid the performance hit of evaluating a second <code><script></code> tag. And your application layout is less cluttered when all your JavaScript is consolidated in the <em>application.js</em> script.</p>
<p>Google’s technique uses the JavaScript <code>document.createElement</code> method. With Rails, you can use the <a href="http://api.jquery.com/jQuery.getScript/">jQuery getScript method</a>. It will load any JavaScript file from within a JavaScript file. The <code>getScript</code> method has one big limitation: It doesn’t retrieve scripts from the browser cache. To overcome this limitation, we can define a similar method that looks for a cached script before attempting a download of an external script.</p>
<h3 id="implementing-loading">Implementing Dynamic Loading</h3>
<p>Here’s how we can download (or load from the cache) a JavaScript file from a remote server from within the Rails asset pipeline.</p>
<p>Create a file <strong>app/assets/javascripts/jquery.externalscript.js</strong>:</p>
<pre>
jQuery.externalScript = function(url, options) {
// allow user to set any option except for dataType, cache, and url
options = $.extend(options || {}, {
dataType: "script",
cache: true,
url: url
});
// Use $.ajax() since it is more flexible than $.getScript
// Return the jqXHR object so we can chain callbacks
return jQuery.ajax(options);
};
</pre>
<p>If you haven’t changed the default manifest file, the <em>jquery.externalscript.js</em> script will be automatically loaded by the <code>//= require_tree .</code> directive. If you’ve removed the <code>//= require_tree .</code> directive, specify the script in the manifest:</p>
<pre>
//= require jquery
//= require jquery_ujs
//= require jquery.externalscript
</pre>
<p>Alternatively, you could place the <code>externalScript</code> code in the <strong>app/assets/javascripts/application.js</strong> file and add <code>//= require_self</code> to the manifest.</p>
<p>Let’s test it by downloading the Google Analytics tracking script.</p>
<p>Create a file <strong>app/assets/javascripts/google_analytics.js.erb</strong>:</p>
<pre>
<% if Rails.env == 'production' %>
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-XXXXXXX-XX']);
_gaq.push(['_trackPageview']);
ga_src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
$.externalScript(ga_src).done(function(script, textStatus) {
console.log('Script loading: ' + textStatus );
if (typeof _gat != 'undefined') {
console.log('Okay. GA file loaded.');
}
else
{
console.log('Problem. GA file not loaded.');
}
});
<% end %>
</pre>
<p>We use the <code>externalScript</code> function to obtain the Google Analytics script. Then we test for the presence of a variable set by the Google Analytics tracking script and</p>
<p>Notice that we only load the Google Analytics script for tracking traffic in production mode. To do so, we use the <em>.erb</em> file extension so it will be preprocessed by Tilt.</p>
<p>With this technique, you’ve added an external script to the site-wide <em>application.js</em> script without copying it locally.</p>
<p>Now let’s consider cases where you want to use JavaScript on a specific page, not throughout the entire application. If external scripts are complicated, page-specific JavaScript in Rails gets even more complex.</p>
<h2 id="specific">Page-Specific JavaScript</h2>
<p>First we’ll consider how to execute JavaScript on a specific page. Then we’ll look at ways to use a JavaScript library from an external script as page-specific JavaScript.</p>
<h3 id="conditional">Conditional Execution of Page-Specific JavaScript</h3>
<p>Often JavaScript is written to interact with specific elements on a page; if so, the browser will evaluate the JavaScript on every page but only activate it if the appropriate page element is present. The JavaScript you need for that page can be part of the site-wide <em>application.js</em> script or it can be included on the page with a <code>javascript_include_tag</code> statement in the view template or application layout.</p>
<p>JavaScript execution can be determined by</p>
<ul>
<li>the presence of unique elements on the page, or</li>
<li>attributes in the <span class="caps">HTML</span> <code>body</code> tag.</li>
</ul>
<p>You can simply test for an element’s unique <span class="caps">HTML</span> element ID:</p>
<pre>
$('document').ready(function() {
if($('#main').length){
console.log("element exists");
}
});
</pre>
<p>A more organized approach is to test for attributes in the <span class="caps">HTML</span> <code>body</code> tag:</p>
<ul>
<li>Set <code>class</code> or <code>id</code> attributes on your page’s body element.</li>
</ul>
<ul>
<li>Use functions in your JavaScript that parse those classes or ids and call the appropriate functions.</li>
</ul>
<p>First you must modify the application layout. Replace the <code><body></code> statement in the <strong>app/views/layouts/application.html.erb</strong> file:</p>
<pre>
<body class="<%= params[:controller] %>">
</pre>
<p>Assuming a page is generated by a Projects controller, the rendered page will include:</p>
<pre>
<body class="projects">
</pre>
<p>Use this condition in your JavaScript code:</p>
<pre>
$('document').ready(function() {
if ($('body.projects').length) {
console.log("Page generated by the projects controller.");
}
});
</pre>
<p>This provides a simple technique to execute JavaScript conditionally on a page.</p>
<h3 id="garber-irish">The Garber-Irish Technique</h3>
<p>For a sophisticated approach to namespacing assets and executing JavaScript based on controller name and action, investigate the <a href="http://viget.com/inspire/extending-paul-irishs-comprehensive-dom-ready-execution">Garber-Irish</a> technique.</p>
<h3>Using the Paloma Gem</h3>
<p>The <a href="https://github.com/kbparagua/paloma">Paloma gem</a> offers an easy way to organize JavaScript files using the Rails asset pipeline. It also provides a capability to execute page-specific JavaScript based on controller name and action.</p>
<p>The Paloma gem simplifies implementation of page-specific JavaScript. I recommend it.</p>
<p>Read on if you’d like to understand how to implement page-specific JavaScript yourself.</p>
<h3 id="implementing-conditional">Implementing Conditional Execution of Page-Specific JavaScript</h3>
<p>We can use a simplified version of the Garber-Irish technique to execute JavaScript conditionally on a page. We use John Firebaugh’s <a href="https://github.com/Verba/jquery-readyselector">jquery-readyselector</a> script to extend the jQuery <code>.ready()</code> to simplify the conditional test.</p>
<p>Set the controller name and action in the <span class="caps">HTML</span> <code>body</code> tag:</p>
<pre>
<body class="<%= controller_name %> <%= action_name %>">
</pre>
<p>You have a choice of syntax: use either <code><%= params[:controller] %></code> or <code><%= controller_name %></code>.</p>
<p>Create a file <strong>app/assets/javascripts/jquery.readyselector.js</strong>:</p>
<pre>
(function ($) {
var ready = $.fn.ready;
$.fn.ready = function (fn) {
if (this.context === undefined) {
// The $().ready(fn) case.
ready(fn);
} else if (this.selector) {
ready($.proxy(function(){
$(this.selector, this.context).each(fn);
}, this));
} else {
ready($.proxy(function(){
$(this).each(fn);
}, this));
}
}
})(jQuery);
</pre>
<p>If you haven’t changed the default manifest file, the <em>jquery.readyselector.js</em> script will be automatically loaded by the <code>//= require_tree .</code> directive. If you’ve removed the <code>//= require_tree .</code> directive, specify the script in the manifest:</p>
<pre>
//= require jquery
//= require jquery_ujs
//= require jquery.readyselector
</pre>
<p>Alternatively, you could place the code in the <strong>app/assets/javascripts/application.js</strong> file and add <code>//= require_self</code> to the manifest.</p>
<p>Here’s how you can test the <em>jquery.readyselector.js</em> script for a home/index page:</p>
<pre>
$('.home.index').ready(function() {
console.log("Page-specific JavaScript on the home/index page.");
});
</pre>
<p>You’ll see execution of JavaScript is restricted to the specified page.</p>
<h3 id="recap">Summary</h3>
<p>To recap, I’ve shown an approach where the Rails asset pipeline is used for all JavaScript files. I’ve shown the benefits of delivering all JavaScript in a single <em>application.js</em> script, how to organize JavaScript files for ease of development, and how to dynamically load scripts from external servers where appropriate. Finally, I’ve shown how to restrict execution of JavaScript to a specific page even when a script is available site-wide.</p>
<p>For completeness, I want to present the edge cases where it is not optimal to include all JavaScript in a single <em>application.js</em> script. Keep in mind that you’ll only know if you have an edge case if you conduct performance testing to determine that a single <em>application.js</em> script is not optimal. In most cases, a single <em>application.js</em> script offers the best performance.</p>
<h2 id="edgecases">Edge Cases</h2>
<p>A neat, uncluttered application layout is one of the advantages of using a single <em>application.js</em> script for delivering all JavaScript code.</p>
<p>I’m going to show you how to add clutter to your application layout by adding multiple <code><script></code> tags using the <code>javascript_include_tag</code> and <code><% content_for %></code> helpers.</p>
<p>We’ll be stepping back in time to use techniques developed before Rails 3.1 was released.</p>
<p>But why? We’ve looked at how we can eliminate the need for multiple <code><script></code> tags, even with scripts loaded from external servers. We’ve seen how we can restrict execution of JavaScript to a specific page. These were reasons developers used multiple <code><script></code> tags before the benefits of the asset pipeline were well understood.</p>
<p>Here’s an edge case. You might want to use multiple <code><script></code> tags on a single page that is not visited by most users, especially if the page requires a script that contains many, many lines of code. (How many? Only performance testing can tell you with certainty.)</p>
<p>Think about it. Suppose one page requires a JavaScript file that is twice as large as your site-wide <em>application.js</em> script. If it is included in the <em>application.js</em> script, every visitor to your site will wait twice as long for the entire <em>application.js</em> script to download on a first visit. On subsequent visits, the file size is of little importance because reading from the local cache is instantaneous relative to the time required for network downloads. So it only matters for a user’s first visit. But what if the “big code” JavaScript file is only used on an administrative page used by one out of ten thousand visitors? Looking at the sum total of all visits, it makes sense to optimize the site’s performance for the first visit of ten thousand users by keeping the administrative JavaScript file out of the <em>application.js</em> script.</p>
<h3>Implementation</h3>
<p>Here are five steps to add JavaScript code to a page using multiple <code><script></code> tags:</p>
<ul>
<li>Add a script to the <strong>app/assets/javascripts</strong> folder.</li>
<li>Remove the <code>//= require_tree .</code> directive from the <strong>app/assets/javascripts/application.js</strong> manifest file.</li>
<li>Use <code><%= yield(:head) %></code> in the application layout.</li>
<li>Use <code><% content_for :head ... %></code> in the view.</li>
<li>Modify <strong>config/environments/production.rb</strong> to add <code>config.assets.precompile</code> for your script.</li>
</ul>
<h3>Example</h3>
<p>Let’s imagine we’re using the MegaAdmin JavaScript library on an administrative page.</p>
<p>There will be a performance cost to download and cache the MegaAdmin JavaScript file. We know that only a small number of visitors to the site will be visiting the admin page so loading it only on one page reduces the performance hit for other users.</p>
<p>First, let’s write a local script that uses the the MegaAdmin JavaScript library. Here is our example <strong>app/assets/javascripts/admin.js</strong> file:</p>
<pre>
$(function() {
if (typeof MegaAdmin != 'undefined') {
console.log('MegaAdmin JavaScript file loaded.');
}
else
{
console.log('Problem: MegaAdmin JavaScript file not loaded.');
}
});
</pre>
<p>We can use it to test if the external JavaScript file is loaded.</p>
<p>Make sure you’ve removed the <code>//= require_tree .</code> directive from the <strong>app/assets/javascripts/application.js</strong> manifest file so our admin script doesn’t get concatenated into the <em>application.js</em> script.</p>
<p>Now we’ll consider how to add page-specific JavaScript directly to the view that renders the page.</p>
<h3 id="layout">Application Layout</h3>
<p>For page-specific JavaScript, you’ll need to add a <code>javascript_include_tag</code> helper to the <code>head</code> section of your page view. One approach is to modify your controller to use a custom layout for the view (see a range of approaches in the RailsGuide <a href="http://guides.rubyonrails.org/layouts_and_rendering.html#using-render">Layouts and Rendering in Rails</a>). I suggest you leave your controller alone. Such a minor customization defeats the purpose of the site-wide application layout; fortunately, Rails offers a better option.</p>
<p>Set up your application layout with a <code><%= yield(:head) %></code> statement so you can inject additional tags in the <code>head</code> section of the view. Here’s an example of the <code>head</code> section in an <strong>app/views/layouts/application.html.erb</strong> file:</p>
<pre>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><%= content_for?(:title) ? yield(:title) : "App_Name" %></title>
<meta name="description" content="<%= content_for?(:description) ? yield(:description) : "App_Name" %>">
<%= stylesheet_link_tag "application", :media => "all" %>
<%= javascript_include_tag "application" %>
<%= csrf_meta_tags %>
<%= yield(:head) %>
</head>
</pre>
<p>The <code><%= yield(:head) %></code> statement follows the <code><%= javascript_include_tag "application" %></code> so you can add additional scripts and still use jQuery in any page-specific JavaScript code you add.</p>
<h3 id="contentfor">Using <em>content_for</em>
</h3>
<p>We want both the external MegaAdmin script and our local <em>admin.js</em> script available on the admin page. We’ll use the <code><% content_for %></code> helper to include the two scripts.</p>
<p>Here’s our imaginary admin view:</p>
<pre>
<% content_for :head do %>
<%= javascript_include_tag 'https://example.com/MegaAdmin/v1/' %>
<%= javascript_include_tag 'admin' %>
<% end %>
<h2>admin Page</h2>
.
.
.
</pre>
<p>The <code><% content_for :head ... %></code> block allows us to add page-specific JavaScript files to the view.</p>
<p>The Rails asset pipeline will find our <em>admin.js</em> script in the <strong>app/assets/javascripts</strong> folder and make it available so it appears with a path of <code>/assets/admin.js</code>.</p>
<p>If we view <span class="caps">HTML</span> source, we will see generated <span class="caps">HTML</span> that looks like this:</p>
<pre>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>App_Name</title>
<meta name="description" content="App_Name"/>
<link href="/assets/application.css" media="all" rel="stylesheet" type="text/css" />
<script src="/assets/application.js" type="text/javascript"></script>
<meta content="authenticity_token" name="csrf-param" />
<meta content="..." name="csrf-token" />
<script src="https://example.com/MegaAdmin/v1/" type="text/javascript"></script>
<script src="/assets/admin.js" type="text/javascript"></script>
</head>
</pre>
<p>You see we have three <code><script></code> tags in the <span class="caps">HTML</span> source. Our site-wide <em>application.js</em> script executes first, followed by the MegaAdmin JavaScript library from an external server, and then our <em>admin.js</em> script. It is not a good idea to use multiple <code><script></code> tags on every page of our application, but in this case, just for some admin pages, it makes sense.</p>
<h3 id="precompiling">Precompiling in Production</h3>
<p>In development mode, nothing more is required to use our new <em>admin.js</em> script on any page where it is needed. The asset pipeline “live compiles” all the JavaScript files it finds and makes them available for use.</p>
<p>For production, we must make an important configuration change so our new script is precompiled and available on deployment.</p>
<p>Add this to the file <strong>config/environments/production.rb</strong>:</p>
<pre>
config.assets.precompile += %w( admin.js )
</pre>
<p>If we’ve created a CoffeeScript or <span class="caps">ERB</span> file, we don’t need to include the <strong>.coffee</strong> or <strong>.erb</strong> file extension.</p>
<p>When you precompile assets in production mode, the Rails asset pipeline will automatically process the <strong>app/assets/javascripts/application.js</strong> file and any additional files listed in its manifest to produce a concatenated and minified site-wide <em>application.js</em> script.</p>
<p>Any other scripts that you wish to use on a page in addition to the site-wide <em>application.js</em> script must be specified by the <code>config.assets.precompile</code> statement or else they will not be precompiled and made available in production mode.</p>
<p>Sprockets will look for files designated in the <code>config.assets.precompile</code> statement and create JavaScript files with the same names. If the file contains manifest directives, it will combine other files to make a single script.</p>
<p>If you don’t make this configuration change, you won’t see the error until your application is deployed in production.</p>
<h3 id="testing">Testing in Production Mode</h3>
<p>How can you tell if you’ve configured your application to serve the scripts needed in production?</p>
<p>Test it.</p>
<p>To test, you must enable your Rails web server to deliver static assets. Modify the <strong>config/environments/production.rb</strong> file:</p>
<pre>
# Disable Rails's static asset server (Apache or nginx will already do this)
config.serve_static_assets = true
</pre>
<p>Be sure to switch this back after trying out your application locally in production mode.</p>
<p>Then try running your server locally in production mode:</p>
<pre>
$ rake db:migrate RAILS_ENV=production
$ rake assets:precompile
$ rails server -e production
</pre>
<p>Visit the web pages that use your scripts and check functionality. In our MegaAdmin example, we’ll see an error “admin.js isn’t precompiled” unless we set <code>config.assets.precompile</code> to include it.</p>
<p>Use <code>rake assets:clean</code> to remove the precompiled assets when you return to development mode.</p>
<h2 id="parameters">Passing Parameters to JavaScript</h2>
<p>It is often necessary to pass parameters from a Rails controller to JavaScript. We’ll look at several approaches and consider the issues.</p>
<h3>Using the Paloma Gem</h3>
<p>The <a href="https://github.com/kbparagua/paloma">Paloma gem</a> offers an easy way to organize JavaScript files using the Rails asset pipeline. It provides a capability to execute page-specific JavaScript and it provides an easy way to pass parameters to JavaScript.</p>
<p>For example, you might have a UsersController with a <code>destroy</code> action:</p>
<pre>
def destroy
user = User.find params[:id]
user.destroy
js_callback :params => {:user_id => params[:id]}
end
</pre>
<p>The <code>js_callback</code> directive makes the parameter available in a corresponding JavaScript file named <strong>assets/javascripts/paloma/users/destroy.js</strong>:</p>
<pre>
Paloma.callbacks['users/destroy'] = function(params){
var id = params['user_id'];
alert('User ' + id + ' deleted.');
};
</pre>
<p>If you use the Paloma gem, there’s nothing more you need to do.</p>
<h3>Use HTML5 Data Attributes</h3>
<p>HTML5 offers a convenient way to store data strings in an <span class="caps">HTML</span> page that are not rendered but are available to JavaScript through the <span class="caps">DOM</span> (the <a href="http://en.wikipedia.org/wiki/Document_Object_Model">document object model</a>). The HTML5 specification describes <a href="http://www.w3.org/TR/2010/WD-html5-20101019/elements.html#custom-data-attribute">custom data attributes</a>. Custom data attributes are the best way to set parameters that will be used in JavaScript.</p>
<p>Here’s an example. Suppose you have a form that will be manipulated with JavaScript before it is submitted to your application. Using the <a href="https://github.com/plataformatec/simple_form">SimpleForm gem</a>, you might use a form helper and set an HTML5 data attribute. In our example, we obtain the user’s IP address from the <code>request</code> object and set it as a <code>data-ip_address</code> data attribute.</p>
<pre>
<%= simple_form_for @user, :html => {:class => 'form-vertical', 'data-ip_address' => request.remote_ip}) do |f| %>
.
.
.
<% end %>
</pre>
<p>The view will render <span class="caps">HTML</span> in the browser:</p>
<pre>
<form accept-charset="UTF-8" action="/users" class="form-vertical" data-ip_address="..." id="new_user" method="post">
</pre>
<p>Using a jQuery selector, we can obtain the value of the IP address in our JavaScript with:</p>
<pre>
console.log('IP address: ' + $('#new_user').data('ip_address'));
</pre>
<p>In general, this is the best way to pass a parameter to JavaScript.</p>
<h3>Use Metatags</h3>
<p>You may see this technique used in some Rails applications.</p>
<p>Add a <code><%= yield(:head) %></code> directive to the <code>head</code> section of the <strong>app/views/layouts/application.html.erb</strong> application layout file:</p>
<pre>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><%= content_for?(:title) ? yield(:title) : "App_Name" %></title>
<meta name="description" content="<%= content_for?(:description) ? yield(:description) : "App_Name" %>">
<%= stylesheet_link_tag "application", :media => "all" %>
<%= javascript_include_tag "application" %>
<%= csrf_meta_tags %>
<%= yield(:head) %>
</head>
</pre>
<p>In a view file, add a <code>content_for?</code> view helper:</p>
<pre>
<% content_for :head do %>
<%= tag :meta, :name => "ip_address", :content => request.remote_ip %>
<% end %>
</pre>
<p>The <code><% content_for :head do %></code> block injects the IP address in the application layout as a metatag named “ip_address.” Any JavaScript used on the page will have access to the IP address through a jQuery selector:</p>
<pre>
console.log('IP address: ' + $('meta[name="ip_address"]').attr('content'));
</pre>
<p>The metatag technique is an alternative to using HTML5 data attributes.</p>
<h3>Use a “.js.erb” File</h3>
<p>Any Ruby variable can be included in JavaScript code by giving the JavaScript file an <strong>.erb</strong> extension.</p>
<p>For example, you may have a file <strong>app/assets/javascripts/myscript.js.erb</strong> that contains:</p>
<pre>
console.log('IP address: ' + <%= request.remote_ip %>);
</pre>
<p>This technique of passing a parameter to JavaScript is simple, but it is best to avoid mixing Ruby variables into JavaScript, so try to use HTML5 data attributes instead.</p>
<h2 id="comment">Credits</h2>
<p>Daniel Kehoe wrote this article for the RailsApps project.</p>
<p>Thank you to Peter Cooper (<a href="http://twitter.com/peterc">@peterc</a>), Pat Shaughnessy (<a href="http://twitter.com/pat_shaughnessy">@pat_shaughnessy</a>), Eric Berry (<a href="http://twitter.com/cavneb">@cavneb</a>), Ken Collins (<a href="http://twitter.com/metaskills">@metaskills</a>), Jo Liss (<a href="http://twitter.com/jo_liss">@jo_liss</a>), Stephen Ball (<a href="http://twitter.com/StephenBallNC">@StephenBallNC</a>), and Andrey Koleshko (<a href="http://twitter.com/ka8725">@ka8725</a>) for technical review and advice.</p>
<h3><a href="http://learn-rails.com/learn-ruby-on-rails.html"><img src="http://railsapps.github.io/images/learn-rails-cover-130x161.jpg" title="Learn Ruby on Rails" alt="Learn Ruby on Rails"></a></h3>
</div>
<div class="comments" id="comments">
<div class="content wikistyle gollum">
<h2>Comments</h2>
</div>
<p>Is this helpful? Your encouragement fuels the project. Please tweet or add a comment. Couldn't get something to work? For the example apps and tutorials, it's best to open an issue on GitHub so we can help you.</p>
<div id="disqus_thread"></div>
<script type="text/javascript">
/* * * CONFIGURATION VARIABLES: EDIT BEFORE PASTING INTO YOUR WEBPAGE * * */
var disqus_shortname = 'railsapps'; // required: replace example with your forum shortname
/* * * DON'T EDIT BELOW THIS LINE * * */
(function() {
var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;
dsq.src = 'http://' + disqus_shortname + '.disqus.com/embed.js';
(document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
})();
</script>
<noscript>Please enable JavaScript to view the <a href="http://disqus.com/?ref_noscript">comments powered by Disqus.</a></noscript>
<a href="http://disqus.com" class="dsq-brlink">comments powered by <span class="logo-disqus">Disqus</span></a>
</div><!-- class="comments" -->
</div><!-- class="columns" -->
</div><!-- class="row" -->
<footer class="row">
<div class="large-12 columns">
<div class="row">
<div class="medium-4 large-4 columns">
<dl class="footer_nav">
<dt>RailsApps · Getting Started</dt>
<dd><a href="http://railsapps.github.io/ruby-and-rails.html">Ruby on Rails</a></dd>
<dd><a href="http://railsapps.github.io/what-is-ruby-rails.html">What is Ruby on Rails?</a></dd>
<dd><a href="http://learn-rails.com/learn-ruby-on-rails.html">Learn Ruby on Rails</a></dd>
<dd><a href="https://tutorials.railsapps.org/rails-tutorial">Rails Tutorial</a></dd>
<dd><a href="http://learn-rails.com/ruby-on-rails-tutorial-for-beginners">Ruby on Rails Tutorial for Beginners</a></dd>
<dd><a href="http://railsapps.github.io/installing-rails.html">Install Ruby on Rails</a></dd>
<dd><a href="http://railsapps.github.io/installrubyonrails-mac.html">Install Ruby on Rails - Mac OS X</a></dd>
<dd><a href="http://railsapps.github.io/installrubyonrails-ubuntu.html">Install Ruby on Rails - Ubuntu</a></dd>
<dd><a href="http://railsapps.github.io/rubyonrails-nitrous-io.html">Ruby on Rails - Nitrous.io</a></dd>
<dd><a href="http://railsapps.github.io/updating-rails.html">Update Rails</a></dd>
<dd><a href="http://railsapps.github.io/rails-composer/">Rails Composer</a></dd>
<dd><a href="http://railsapps.github.io/">Rails Examples</a></dd>
<dd><a href="http://railsapps.github.io/rails-examples-tutorials.html">Rails Starter Apps</a></dd>
</dl>
</div>
<div class="medium-4 large-4 columns">
<dl class="footer_nav">
<dt>RailsApps · Articles</dt>
<dd><a href="http://railsapps.github.io/rails-authorization.html">Rails Authorization</a></dd>
<dd><a href="http://railsapps.github.io/rails-google-analytics.html">Analytics for Rails</a></dd>
<dd><a href="http://railsapps.github.io/rails-heroku-tutorial.html">Heroku and Rails</a></dd>
<dd><a href="http://railsapps.github.io/rails-javascript-include-external.html">JavaScript and Rails</a></dd>
<dd><a href="http://railsapps.github.io/rails-environment-variables.html">Rails Environment Variables</a></dd>
<dd><a href="http://railsapps.github.io/rails-git.html">Git and Rails</a></dd>
<dd><a href="http://railsapps.github.io/rails-github.html">Rails GitHub</a></dd>
<dd><a href="http://railsapps.github.io/rails-send-email.html">Send Email with Rails</a></dd>
<dd><a href="http://railsapps.github.io/rails-haml.html">Haml and Rails</a></dd>
<dd><a href="http://railsapps.github.io/rails-default-application-layout.html">Rails Application Layout</a></dd>
<dd><a href="http://railsapps.github.io/rails-html5-boilerplate.html">HTML5 Boilerplate for Rails</a></dd>
<dd><a href="http://railsapps.github.io/rails-3-2-example-gemfile.html">Example Gemfiles for Rails</a></dd>
<dd><a href="http://railsapps.github.io/rails-application-templates.html">Rails Application Templates</a></dd>
<dd><a href="http://railsapps.github.io/rails-product-planning.html">Rails Product Planning</a></dd>
<dd><a href="http://railsapps.github.io/rails-project-management.html">Rails Project Management</a></dd>
</dl>
</div>
<div class="medium-4 large-4 columns">
<dl class="footer_nav">
<dt>RailsApps · Tutorials</dt>
<dd><a href="http://railsapps.github.io/twitter-bootstrap-rails.html">Rails Bootstrap</a></dd>
<dd><a href="http://railsapps.github.io/rails-foundation.html">Rails Foundation</a></dd>
<dd><a href="http://railsapps.github.io/rails-omniauth/">OmniAuth Tutorial</a></dd>
<dd><a href="http://railsapps.github.io/tutorial-rails-devise.html">Rails Devise Tutorial</a></dd>
<dd><a href="http://railsapps.github.io/tutorial-rails-devise-rspec-cucumber.html">Devise RSpec</a></dd>
<dd><a href="http://railsapps.github.io/tutorial-rails-bootstrap-devise-cancan.html">Devise Bootstrap</a></dd>
<dd><a href="http://railsapps.github.io/rails-devise-roles">Role-Based Authorization</a></dd>
<dd><a href="http://railsapps.github.io/rails-devise-pundit">Rails Authorization with Pundit</a></dd>
<dd><a href="https://tutorials.railsapps.org/rails-stripe-membership-saas">Rails Membership Site with Stripe</a></dd>
<dd><a href="https://tutorials.railsapps.org/rails-recurly-subscription-saas">Rails Subscription Site with Recurly</a></dd>
<dd><a href="https://tutorials.railsapps.org/rails-prelaunch-signup">Startup Prelaunch Signup Application</a></dd>
</dl>
<dl class="footer_nav">
<dt>RailsApps Profile</dt>
<dd><a href="https://plus.google.com/108039160165742774777?rel=author">Google</a></dd>
<dd><a href="https://plus.google.com/117374718581973393536" rel="publisher">Find us on Google+</a></dd>
</dl>
</div>
</div>
</div>
</footer>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/foundation/5.2.2/js/foundation.min.js"></script>
<script>
$(document).foundation();
</script>
</body>
</html>