Intro on making a Javascript widget (example using Rails)

15th October 2013 – 1352 words

For a side-project that I consult every once in a while, we wanted to provide a widget generator which allows people to include events that match specific criteria on their websites. After some internet search, we found some scripts here and there, but no comprehensive introduction. Here is my attempt.

Iframe vs. Javascript

First, there are two ways to build a widget:

  • let the user embed an Iframe with your site inside
  • let the user load a Javascript from your server that builds up the required html and inject that into the website

Using an Iframe has the advantage that it will still work when the visitor has Javascript disabled. Besides, it is isolated from the client’s website, meaning you can’t access his document and he can’t access yours.

Thus, if you want to provide the option that your widget inherits font settings and stuff from the website and you want the customers to adjust the widget’s style to match their website’s, then Javascript is the way to go. For that reason, we decided to go with Javascript this time.

Widget snippet for client

This is a snippet that the client can embed into their website:

<script type='text/javascript'>
MySiteWidget = {};
MySiteWidget.limit = 5;
MySiteWidget.query = "javascript";
</script>
<script src='//www.mysite.com/widget.js' type='text/javascript'></script>
<div class='mysite-widget'></div>

First, we set some configuration variables (that can be set by a widget generator). These will be transmitted to our controller function later.

Bootstrapping Javascript

The Javascript that the customer includes on their websites just loads the next bootstrapping widget.js. This can be a static Javascript or a dynamically generated Javascript response. We used latter for the moment because it gives us easy access to our asset paths and host names (Because we used Rails, we embed Erb tags (<% … %>), but this, of course, can be any server side programming language).

We found variants of this script on different internet sites:

(function() {
var jQuery;
if (window.jQuery === undefined || window.jQuery.fn.jquery !== '1.4.2') {
    var script_tag = document.createElement('script');
    script_tag.setAttribute("type","text/javascript");
    script_tag.setAttribute("src", "//ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js");
    if (script_tag.readyState) {
      script_tag.onreadystatechange = function () { // For old versions of IE
          if (this.readyState == 'complete' || this.readyState == 'loaded') {
              scriptLoadHandler();
          }
      };
    } else {
      script_tag.onload = scriptLoadHandler;
    }
    (document.getElementsByTagName("head")[0] || document.documentElement).appendChild(script_tag);
} else {
    jQuery = window.jQuery;
    main();
}

function scriptLoadHandler() {
    jQuery = window.jQuery.noConflict(true);
    main();
}
function main() {
    jQuery(document).ready(function($) {
        var css_link = $("<link>", {
            rel: "stylesheet",
            type: "text/css",
            href: "<%= URI.join(root_url, path_to_stylesheet("widget.css")).to_s %>"
        });
        css_link.appendTo('head');

        var jsonp_url = <%= raw widgets_url(format: "json").to_json %>;
        $.ajax({
          url:       jsonp_url,
          data:      MySiteWidget,
          dataType:  "jsonp",
          success:   function(data) {
            // modify this part
            $.each(data, function(i,d) {
              $('.mysite-widget').append(d.template)
            })
          }
        });
    });
}
})();

This script will load jQuery as a private variable into the client’s website and will then continue to load our widget.css. Afterwards, a JSONP url on our server is queried handing over the before defined parameters (MySiteWidget).

The controller action under that url will generate the dynamic content of the widget based upon the parameters. In our example, on Eventchart, it is a list of upcomming events which suffice the given search query and location parameters.

In this example, our server is expected to respond with a Json encoded array of objects that each have a template method, which contains just the completly, ready rendered template of that item. Of course, this part could have different kind of data structures thus different handling of the data in the success handler.

Example controller response

If you are not using Ruby on Rails then you can skip this section or use it as inspiration for your own implementation. Because Rails can handle multiple formats per given URL, we used the same URL action for handling both the response of the bootstrapping Javascript above, as well as the dynamic response of the widget content:


class WidgetsController < ApplicationController
  def index
    respond_to do |format|
      # shows the widget generator form for the client
      format.html
      # deliver the bootstrapping Javascript
      format.js { render "bootstrap", formats: [:js] }
      # deliver the rendered events as JSONP response to the widget
      format.json {
        search = Search.new(q: params[:q], per: params[:limit])
        render json: search.events, callback: params[:callback]
      }
    end
  end

We respond to:

  • /widget.html - a form generator for the client
  • widget.js - which just render the bootstrap Javascript filled in with the correct asset paths. This could be also just a static precompiled Javascript file
  • widget.json - which renders our search results and respond with a jsonp-compliant response (the callback parameter)

The whole process:

Remarks

JSONP is a way to make cross-domain ajax requests, meaning allow your widget on the clients site to request dynamic data from your server, which is normally on a totally different host.

When your widget seems not to load on your client’s site, check if there is https required. Your browser will then block every non-https request. This is why you should always use https://www.... or leave the protocol out //www..., e.g. the stylesheet, images etc. that are loaded from the widget.

For performance reason, you can embed the widget.css directly into the widget.js. If you do not need too many parameters for the widget you may also skip the jsonp response as well and embed the widget content directly into the first widget.js response (Just note that some IE have problems with query parameters when loading Javascripts…).

Be nice with your stylings and Javascript. You are guest on the client’s website, so behave nicely:

  • prefix all your css class-names with a unique name (like, your site name)
  • Don’t do any intrusive stuff in your Javascript, like changing the whole site