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 .
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.
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.
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).
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!
我来评几句
登录后评论已发表评论数(0)