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