Skip to content
apotonick edited this page Sep 14, 2010 · 7 revisions

Unobstrusive JavaScript (UJS)

That’s separating markup and HTML, e.g. not putting JS into an onclick attribute but in a detached observer.

Apotomo does not implement UJS with any magic but provides some tools for having clean separation. There are basically two approaches
- inline the JavaScript in the widget view
- separate JavaScript to a global file and use data-event-url in the view for configuration

I personally start liking the second approach as it really cleans up your shit.

Example

Assuming we got the following mouse widget, which will initially display a button. When clicked, it re-renders the button showing the time the button was clicked.

class MouseWidget < Apotomo::StatefulWidget
  def display
    respond_to_event :squeak, :with => :squeak
    render
  end

  def squeak
    @time = Time.now
    render :view => :display  # reuse the display view.
  end
end

Inline Javascript

The widget view provides the method widget_javascript that accepts a script block. Basically that just generates a <script...> container.

The view mouse_widget/display.html.haml might look like this.

= h1 "Squeaked at #{@time}!"
= submit_tag "Squeak!", :id => 'click-me'

- widget_javascript do
  $('click-me').on('click', function(event) {
    = "new Xhr('#{url_for_event :click}').send();"
  });

which renders to

<div id="mouse">
  <h1>Squeaked at !</h1>
  <input id="click-me" name="commit" type="submit" value="Squeak!" />

  <script type="text/javascript">
  $('click-me').on('click', function(event) {
    new Xhr('/dashboard/render_event_response?source=click_me&type=click').send();
  });
  </script>
</div>

The first time the widget is rendered, it will inject the script into the page. However, when clicked, it will re-inject the script again since the squeak method reuses that view.

Suppressing JavaScript output

One way would be to suppress script rendering in the squeak state. The state method would be changed as follows.

  def squeak
    @time = Time.now
    render :view => :display, :suppress_js => true
  end

The output of squeak would be just

<div id="mouse">
<h1>Squeaked at 02:40:33!</h1>
<input id="click-me" name="commit" type="submit" value="Squeak!" />
</div>

Global JavaScript file

Another way is to put the scripts in a separate file, which could look like that.

 ".widget".on('click', function(event) {
    new Xhr(
      $(this).get('data-event-url')
    ).send();
  });

The JavaScript code is extracted to its own file whereas the widget view only contains the markup and some configuration.

= h1 "Squeaked at #{@time}!"
= submit_tag "Squeak!", :class => 'widget', 'data-event-url' => url_for_event(:click)

Credits

Thanks to Nikolay V. Nemshilov [MadRabbit], the author of RightJS who spent hours explaining me all the bits and bytes of different JavaScript frameworks, UJS and inline JavaScript.