3 Fun Tricks with Opal and jQuery

Everyone loves jQuery! Well, almost everyone. It makes interacting with the DOM simple and fast, handling browser events is a breeze, and there are numerous jQuery plugins available that are highly useful and easy to get started using.

As crazy as this might sound, jQuery isn't always a simple pleasure, but that's not jQuery's fault. It's the language you have to use...Javascript. Fortunately, with Opal, that's no longer an issue. Write Ruby code that runs in the browser and directly interfaces with jQuery? Yep, no problem.

Here are three fun tricks that you can perform with jQuery using Opal. For a more basic overview of how the opal-jquery gem works, see this documentation page on the Opal website .

Trick 1: Method-based Event Handling

One of the easy ways you can get tripped up with jQuery is the tendency to write immediate callback functions for all your event handlers. Not only does this lead to "callback hell" and code soup, but it's also a pain when you run into weird errors and realize you accidentally tried to interact with your parent object using this instead of something like _this or self or whatever.

Not only are scope problems like that not an issue with Opal, but you can avoid writing anonymous callback functions entirely and just use standard object methods. Here's an example of how to do it:

class MyWidget  
  def initialize
    @widget_type = "button"
    Element['#clickme'].on(:click) {|event| somemethod(event)}
  end

  def somemethod(event)
    event.current_target.html = "Clicked the #{@widget_type}!"
  end
end  
Document.ready? do  
  my_widget = MyWidget.new
end

# HTML:
# <div id="clickme">Click me!</div>

Well, that does indeed work, but the block syntax in the initializer seems a little funky. If all we're doing is calling an object method, can't we just pass that in directly somehow?

Well, with a little "metaprogramming", we can. We're going to define a special handle method in a module we include, and using the neat little stabby lambda syntax ->(...) coupled with the & block invocation, we can easily pass along an object method as a block for any event handler (and to prove it, the access of the object's instance variable @widget_type still works!).

module EventHandler  
  def handle(method_sym)
    ->(event) { send(method_sym, event) }
  end
end

class MyWidget  
  include EventHandler

  def initialize
    @widget_type = "button"
    Element['#clickme'].on :click, &handle(:somemethod)
  end

  def somemethod(event)
    event.current_target.html = "Clicked the #{@widget_type}!"
  end
end  

Of course, given that this is Ruby programming we're talking about here, there's always more than one way to do things. :)

module EventHandler  
  def handle(event_type, selector, method_sym)
    Element[selector].on(event_type) {|event| send(method_sym, event) }
  end
end

class MyWidget  
  include EventHandler

  def initialize
    @widget_type = "div tag"
    handle :click, '#clickme', :somemethod
  end

  def somemethod(event)
    event.current_target.html = "Clicked the #{@widget_type}!"
  end
end  

OK, now I'm getting carried away. The point is, think through how you want to structure your objects and the the way you'd like to define event handling independent of jQuery per-se, and then bend jQuery to your will with some nice syntactic sugar. I've even built experimental DSLs that let me set up event handling at the class-level similar to ActiveRecord's validate or has_many type class methods in Rails. The sky's the limit.

Trick 2: HTTP Promises

Promises are becoming a big thing in the Javascript world, and thankfully they are also a first-class citizen in Opal. The wrapper around jQuery's ajax methods makes good use of them, so that you can easily handle good requests, error conditions, and run code that works no matter what the response. Here's an example:

HTTP.get('/some/json').then do |response|  
  process_json_data(response.json)
end.fail do |response|  
  handle_error(response.json[:error])
end.always do  
  hide_spinner!
end  

Here we're just passing along JSON to some methods in the current object context (aka self ) based on the nature of the server's response. In the always block, we turn off a spinner on the page in either case. ( Note: always isn't working as expected in the current release of Opal as of 4/28/2015, but a bugfix is already underway.)

Since a promise is its own type of object, you can structure your code in a more modular fashion. For example, this is another way to do things:

def fetch_json  
  process_request HTTP.get('/some/json') do |response|
    # do something with the json
  end
end

def process_request(promise)  
  promise.then do |response|
    yield response
  end.fail do |response|
    handle_error(response.json[:error])
  end.always do
    hide_spinner!
  end
end  

This way you could execute a number of different requests throughout your object, all the while maintaining common error handling and cleanup routine.

Trick 3: Easy Templates

Wish you could render a Ruby ERB file client-side? And then add it into an HTML element? Here's a super-simple way to do it:

# somewhere in your codebase:
require 'ostruct'  
class Element  
  def render_template(template_path, assigns)
    template_assigns = OpenStruct.new(assigns)
    template_output = Template[template_path].render(template_assigns)
    self.html = template_output
  end
end

# app/assets/templates/freetobe.opalerb
I am <strong><%= free %></strong> to be <em><%= me %></em>!

# elsewhere...
Element['#mydiv'].render_template('freetobe', {free: 'Free', me: 'Me'})  

Basically, we're adding a render_template method to the Element class (which is defined by the opal-jquery gem). That method simply creates an OpenStruct object loading in the hash as the available object methods, then passes that along to the Template object that is available to render the template file (loaded via Rails' Asset Pipeline). You'll then get the HTML inserted for <div id="mydiv"></div> :

I am Free to be Me !

This is by no means an example of the "right" way to render templates client-side, but it's certainly not a bad way to get your feet wet! Your next step might be to add some event handling via a callback once the template is rendered, or maybe jettison the OpenStruct and define your own View class which could be used as the context for the template (e.g., it could provide helper methods and other useful actions and data to the template).

What comes next

This only scratches the surface of what you can accomplish writing Ruby for the browser with Opal and jQuery. I highly suggest you review the code in the opal-jquery gem directly as there are a lot of interesting little nuggets of functionality there. And feel free to head over to the Opal Gitter chat room if you have questions!

相关推刊
  • 《匿名收藏》 82514
我来评几句
登录后评论

已发表评论数(0)

相关站点

热门文章
站长统计