Gabriel Hilal
4 Jul, 2015 • 5 min read

Widgets API in Rails

You might have already found yourself in a situation where you would like to distribute parts of layouts among different web applications. Let’s say you manage dozens of websites, and for some reason you need a contact form to send you an email when someone fill it in. You don’t want to implement the HTML forms for all these applications, right?

If you do a quick search on Google, you will find a lot of tutorials explaining how to create iframes, as well as some discussions about inline JavaScript. However, there are important differences between these techniques. This post explains how to create a flexible Widgets API that can be used by both approaches. It also discusses the main difference between these methods, so I hope it helps you to decide when to use each one.

Route, Controller and Views

We can start by creating a route to receive incoming requests for widgets. In the routes.rb add the following line:

get '/widgets/:template', to: 'widgets#show'

In this way, the request /widgets/contact_form for example, would be dispatched to the widgets controller’s show action with { template: 'contact_form' } in the params.

So, let’s create our widgets controller:

class WidgetsController < ApplicationController
  def show
    respond_to do |format|
      format.html { render params[:template], layout: 'widgets' }
      format.js   { render js: js_constructor }
    end
  end

  private
  def js_constructor
    content = render_to_string(params[:template], layout: false)
    "document.write(#{content.to_json})"
  end
end

The show action can respond_to HTML and JS formats. In both cases, params[:template] should match a view in the app/views/widgets folder. In our example, the expected file is contact_form, but you can create any other piece of view. So, let’s create the contact_form.html.erb file:

<%= form_tag send_email_action_url do %>
  <%= text_field_tag :name %>
  <%= text_field_tag :email %>
  <%= text_area_tag :message %>
  <%= submit_tag :submit %>
<% end %>

Note that send_email_action_url must be replaced by a valid url pointing to the action that deals with this POST. In case you are using url helpers, make sure you use _url instead of _path, as it provides the host, port and path prefix.

Iframe

One way of rendering our contact form, would be creating an iframe that requests the HTML format. The action show renders the appropriated view using the layout: 'widgets'. Inside the app/views/layouts folder, we must create the widgets.html.erb file.

<!DOCTYPE html>
<html>
  <head><%= stylesheet_link_tag 'widgets', media: 'all' %></head>
  <body>
    <%= yield %>
  <body>
</html>

This layout is minimal, including only the stylesheet file and the view’s content that is rendered by the <%= yield %>. You have to create the widgets.css file inside the apps/assets/stylesheets/ folder, and then define the style for all your widgets.

After the above setup, you can render our contact form in different domains, using the <iframe> tag. Be aware that the iframe will use the style defined in the widgets.css.

<iframe src="http://mydomain.com/widgets/contact_form" width="100%" height="100%" frameborder="0"></iframe>

PS: Depending on your Rails version, you might have to configure the X-Frame-Options.

Some important notes about iframes:

Inline JavaScript

Another way of rendering our contact form, would be running the JavaScript code provided by the JS format. The js_constructor method wraps the view’s HTML code inside a document.write. Note the layout: false, which means the widgets layout will not be included.

We can render our contact form in different domains using the <script> tag. The script will construct the widget by writing the HTML elements to the DOM. In this way, the widget becomes part of the layout, and the existing style is also applied to it.

<script type="text/javascript" src="http://mydomain.com/widgets/contact_form.js"></script>

Some important notes about inline JavaScript:

Deciding When to Use Each Approach

Although we have discussed security issues, our Widgets API does not provide any unsafe script. While the Inline JavaScript should be used to construct the widget as part of the client’s HTML, the Iframe can render the widget as defined in the API server (widgets.css).

Back to our example, we can assume that our contact form has a black background and a white font. So, let’s say we want to include this widget in a clean website with white background and black font.

If your aim is to highlight the contact form in this clean website, you can use the iframe method. However, if you would like to integrate this widget with the website, the Inline JavaScript would be the best choice. So, at the end we can say that both approaches can be used, having situations where each one of them can be more suitable.

Post by: Gabriel Hilal