pixelwright UI Development & Design

Creating an embeddable widget in Rails 4

If you wish to allow some of your content to be embedded into other websites, you can create a JS script that will enable that quite easily using Rails. Sounds good? Let’s go!

First, let’s start by adding an endpoint in our routes file:

#config/routes.rb

resources :embeds, only: :show, path: 'embed' # -> domain.com/embed/:id

This will allow the client website to use our JS embed code to connect to our app via domain.com/embed/:id

In order for this to happen smoothly, we will need to allow connections from remote sites by managing our CORS (Cross Origin Resource Sharing) policy, which would block any XHR requests from non-origin domains by default, preventing attacks to our server by anonymous requests.

We will do so by creating a private allow_iframe method in our ApplicationController which we can reuse if needed:

#app/controllers/application_controller.rb

private

def allow_iframe
  response.headers.delete 'X-Frame-Options'
end

This will delete the X-Frame-Options to which Rails 4 adds a default value of SAMEORIGIN, as the absence of this header will make the browser “allow all” origins (only where the method is called, of course) and is more consistent to different browsers and caching issues than simply replacing its value.

Then, we will call it in the corresponding controller:

#app/controllers/embeds_controller.rb

class EmbedsController < ApplicationController
  after_filter :allow_iframe, only: [:show]
  protect_from_forgery except: [:show]

  layout false

  def show
    @content = Event.find(params[:id])
  end
end

And in the show view:

<!-- app/views/embeds/show.html.erb -->

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">

    <!--[if lt IE 9]>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv.js"></script>
    <![endif]-->
    <%= yield :stylesheets %>
  </head>

  <body>
  <!-- THE CONTENT CAN BE IN A PARTIAL: -->
    <%= render partial: 'shared/embed', locals: { content: @content } %>
  <!-- OR JUST BY ITSELF: -->
    <%= @content.title %>
    <%= @content.description %>
    […]
  </body>

</html>

Now, lets create a simple embed.js script in our public folder which will be accesed by the client website to load our embedded content:

// public/embed.js

window.onload = function() {
  var scriptParam = document.getElementById('load_widget');
  var id = scriptParam.getAttribute('data-content');

  var iframe = document.createElement('iframe');

  // Set width and height as required

  iframe.setAttribute('width', '390');
  iframe.setAttribute('height', '340');

  // Remove those horrible borders...

  iframe.setAttribute('frameborder', '0');
  iframe.style.border = '0';

  // Replace the iframe's src url to contain our request content

  iframe.src = scriptParam.getAttribute('src').replace(/\.js/g, '/') + id;

  document.body.appendChild(iframe);

};

Now all we have to do is instruct the client website to insert this simple script, where the data-content attribute is the :id of the desired content:

<script id="load_widget" src="https://domain.com/embed.js" data-content="1"></script>

Enjoy!


Optional AJAX implementation

In case you’d like to allow AJAX functionality to this widget, great gem for managing this in Rails is the RACK-CORS gem which we can add to our Gemfile:

#Gemfile

gem 'rack-cors', require: 'rack/cors'

And configure:

#config/application.rb

config.middleware.use Rack::Cors do
  allow do
    origins '*'
    resource '/embed.js', headers: :any, methods: [:get, :post, :options]
  end
end

Questions?

Have a question about this post, a project or anything else?
Let's have a conversation on Twitter.