Unobtrusive JavaScript in Rails 3

Rails 2 nutzt Prototype per Default. Die JavaScript-Helper generieren Inline-Code, der mit dem HTML verwoben ist. In diesem Beitrag möchte ich beschreiben, wie man in Rails 3 stattdessen jQuery nutzen kann und zudem noch eine saubere Trennung von JavaScript und HTML erreicht. Als erstes wird hierzu eine neue Rails-Anwendung angelegt. Bei mir ist momentan Version 3.0.0.beta2 installiert.

rails jsdemo
cd jsdemo

Als nächstes wird jQuery heruntergeladen:

curl http://code.jquery.com/jquery-1.4.2.min.js > public/javascripts/jquery.js

Im JavaScript-Verzeichnis befindet sich die Datei rails.js. Dabei handelt es sich quasi um einen Adapter für die genutzte JavaScript-Bibliothek. Momentan bezieht sich dieses noch auf Prototype. Aus diesem Grund wird es nun durch eines für jQuery ersetzt:

curl http://github.com/rails/jquery-ujs/raw/master/src/rails.js > public/javascripts/rails.js

Als nächstes müssen wir Rails beibringen, dass durch die Helper auch die richtigen Dateien eingebunden werden. Dafür wird die Datei config/initializers/javascript_default_sources.rb angelegt und mit folgendem Code gefüllt:

module ActionView::Helpers::AssetTagHelper
  remove_const :JAVASCRIPT_DEFAULT_SOURCES
  JAVASCRIPT_DEFAULT_SOURCES = %w(jquery.js rails.js)
  reset_javascript_include_default
end

Jetzt brauchen wir noch ein Layout, das eben diese Dateien einbindet. Es wird bewusst HTML5 verwendet. Dazu gleich mehr. Nach einem touch app/views/layouts/application.html.erb füllen wir die neue Datei mit folgendem Inhalt:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>jsdemo</title>
  <%= javascript_include_tag :defaults %>
  <%= csrf_meta_tag %>
</head>
<body>
<%= yield %>
</body>
</html>

Jetzt wird noch ein Controller mit einer Action benötigt. An dieser Stelle verzichte ich der Einfachheit halber auf REST bzw. Ressourcen.

rails generate controller demo index

Nun noch mit rails server den Server starten und die Seite über http://localhost:3000/demo/index aufrufen.

OK, bisher war es noch nicht sonderlich interessant. Das soll sich nun ändern. Dazu wird das index-Template gefüllt und ein weiteres angelegt, der Controller erweitert und eine entsprechende Route ergänzt.

index.html.erb:

<p id="result">result</p>
<p><%= link_to 'eins', eins_path, :remote => true %></p>

routes.rb:

match 'eins' => 'demo#eins'

demo_controller.rb:

class DemoController < ApplicationController
  respond_to :html, :js
 
  def index
  end
 
  def eins
    respond_with(:eins)
  end
end

neu: app/views/demo/eins.js.erb:

$('#result').css('color', 'red');

Wenn man sich nun den Quelltext im Browser ansieht, fällt sofort das eigenartige Attribut im Link auf. Dabei handel es sich um ein neues Feature von HTML5. Mit dem data-Attribut lassen sich eigene Schlüssel-Wert-Paare definieren, auf die man per JavaScript zugreifen kann. “remote” ist der Schlüssel und “true” der entsprechende Wert:

<a href="/eins" data-remote="true">eins</a>

Nach einem Klick auf den Link wird per Ajax die eins-Action aufgerufen und das gerenderte eins.js.erb zurückgegeben. Der darin enthaltene JavaScript-Schnipsel wird ausgeführt und bewirkt, dass die Schrift in div#result rot wird. Es lassen sich allerdings auch andere Formate verwenden, zum Beispiel HTML:

index.html.erb:

...
<p><%= link_to 'zwei', zwei_path, :remote => true, 'data-type' => 'html', :id => 'zwei' %></p>
<script>
$('#zwei').bind('ajax:success', function(type, data, status, xhr) {
  $('#result').append(data);
 
  console.log(type);
  console.log(data);
  console.log(status);
  console.log(xhr);
});
</script>

routes.rb:

match 'zwei' => 'demo#zwei'

demo_controller.rb:

  def zwei
    render :layout => false
  end

neu: app/views/demo/zwei.html.erb:

<div>Output von <em>zwei.html.erb</em></div>

Das Ereignis wird per bind abgefangen. In der FireBug-Console sieht man das Ergebnis. Desweiteren wird der HTML-Schnipsel aus dem gerenderten zwei.html.erb-Template dem div#result angehängt.

Anmerkung: Das script-Element gehört natürlich nicht ins Template, sondern in eine eigene JavaScript-Datei. Der Einfachheit halber habe ich aber darauf verzichtet, obwohl dies alles andere als unobtrusive ist.

Welche Vorteile bring nun das Ganze? Als erstes wird deutlich, dass sich jQuery sehr einfach einbinden lässt. Ähnliches dürfte auch für andere Bibliotheken gelten. Es muss halt jeweils eine angepasste rails.js geschrieben oder selbige erweitert werden. Diese muss dabei HTML-Elemente mit data-remote-Attributen überwachen und entsprechende Ajax-Calls abfeuern. Desweiteren kann man auf diese Weise sehr schön HTML und JavaScript voneinander trennen – nie mehr Inline-Skripte! Außerdem lässt sich Ruby-Code in *.js.erb-Templates verwenden – man hat somit Zugriff auf die Objekte und kann individuelles JavaScript dynamisch erzeugen.

Wie sieht’s mit Nachteilen aus? Nun ja, wenn man nicht aufpasst, verlagert man den Spaghetti-Code aus dem gerenderten HTML in die Views. Nämlich dann, wenn dort ein Gemisch aus JavaScript, HTML und Ruby zu finden ist. Es lassen sich nämlich auch “HTML”-Parials in *.js.erb-Templates verwenden. Beispiel:

$('#result').append("<%= escape_javascript(render @some_partial) %>");

Fazit: Diese Neuerungen stellen ganz klar einen Fortschritt gegenüber Rails 2 dar. Eine klare Trennung von HTML und JavaScript war schon lange nötig. Sicherlich ließ sich das auch schon vorher erreichen, wenn man nämlich weitestgehen auf Rails’ JavaScript-Helper verzichtet hat. Aber wozu braucht man dann ein Framework, wenn doch alles selbst geschrieben wird? Desweiteren sei noch erwähnt, dass die oben beschriebene Technik Geschmackssache ist: JavaScript-Templates, dessen Output direkt ausgeführt wird ist vielleicht gewöhnigungsbedürftig.

Hier noch ein paar Quellen:

Dieser Beitrag wurde unter Ruby/Rails abgelegt und mit , , verschlagwortet. Setze ein Lesezeichen auf den Permalink.

Eine Antwort auf Unobtrusive JavaScript in Rails 3

  1. Informnet sagt:

    Vielen Dank für das griffige Beispiel. Ich habe es mit einer simplen Anwendung, die neue Philosophie Javascript von HTML zu trennen, nachvollzogen. Bisher hatte ich gerne RJS-Template verwendet, um einfache Aktionen auf DOM-Element anzuwenden. Mit Rails 3 funktioniert das nicht mehr so ohne weiteres. Aber mit etwas mehr Javascript-Programmierung geht es eigentlich fast genauso einfach und das neue Konzept von Rails 3 wird erfüllt.