Building an embeddable Javascript widget (third-party javascript)

Tips building an embeddable Javascript widget (like Disqus, or social buttons) using RequireJS.

RequireJS makes building embeddable application a breeze, it also bundled with an optimizer tool (r.js) that take care of everything (dependency handling, uglifying...).

You must remember that you don't own the DOM, you can't do anything like updating global style, or altering the page. You should also avoid conflict with current script, and global variables.

RequireJS crash course

Assuming you already have RequireJS and Bower installed.

If you don't use Bower yet to manage dependency, you should take a look at it.

$ bower install jquery

We need to create few files to get started, create config.js:

var requirejs = {
    paths: {
      jquery: "bower_components/jquery/dist/jquery"
    }
};

Create embed.build.js:

({
  baseUrl: ".",
  mainConfigFile: "config.js",
  name: "bower_components/almond/almond", // assumes a production build using almond
  include: ['embed'],
  out: "embed.min.js"
})

Create embed.js:

require(["jquery"], function($) {

    "use strict";

    $(function() {

        console.log('ok');

    });
});

Now we can try to build embed.min.js to make sure everything works as expected.

$ r.js -o embed.build.js

Handling CSS

Cleanslate seems to be a good solution to prevent overlapping with current style.

The RequireJS optimizer will automatically inline CSS files referenced by @import.

Also, don't forget to prefix your CSS class so you don't mess up with the actual CSS of the page and to use !important.

$ wget https://raw.github.com/premasagar/cleanslate/master/cleanslate.css
@import cleanslate.css

#my-widget a {
  color:orange !important;
}

Embedding CSS

To embed/bundle text (template) inside the final JS file, take a look at requirejs-text.

$ bower install requirejs-text

Ànd in your config.js:

var requirejs = {
    paths: {
      jquery: "bower_components/jquery/dist/jquery",
      text : "components/requirejs-text/text"
    }
};

To optimize CSS, you need to run this additional command:

$ r.js -o cssIn=my-widget.css out=my-widget_embed.css

Now, you can require your CSS stylesheet within your app:

require(["text!module_embed.css"],
  function(css) {
    var $style = $("<style></style>", {type: "text/css"});
    $style.text(css);
    $("head").append($style);
  }
);

Handling configuration

Configuration can easily be managed using data-* attributes and retrieve it using $.data:

var configTheme = $('#my-widget').data('my-widget-theme');
if (configTheme == undefined) {
  doSomething();
};

Communicating with external server

Two evident ways: CORS or JSON-P, but since CORS is only supported by IE8+, you may want to stick with JSON-P. Hopefully jQuery ($.ajax and deferred) handles everything for us.

$.ajax({
  url: "http://date.jsontest.com/",
  dataType: "jsonp"
}).then(function(resp) {
  console.log(resp);
}, function(resp) {
  console.log("Something bad happened:");
  console.log(resp);
});

Loading the code

I found this article very interesting, here is the final snippet of this article (you should read it):

(function (window, document) {
  var loader = function () {
    var script = document.createElement("script"), tag = document.getElementsByTagName("script")[0];
    script.src = "http://errorception.com/projects/" + _errs[0] + "/beacon.js";
    tag.parentNode.insertBefore(script, tag);
  };

  // Wait until window.onload before downloading any more code.
  window.addEventListener ? window.addEventListener("load", loader, false) : window.attachEvent("onload", loader);
})(window, document);

A basic widget

I built a demo widget using Ractive.js, I think it's a good fit for building a widget, it comes with templating, data binding, event handling, and it integrate with RequireJS.

I won't make a tutorial for the widget, just check the source code on GitHub.

<div id="myWidget"></div>
<script>
(function (window, document) {
  var loader = function () {
    var script = document.createElement("script"), tag = document.getElementsByTagName("script")[0];
    script.src = "http://thomassileo.demo.s3.amazonaws.com/embed.min.js";
    tag.parentNode.insertBefore(script, tag);
  };
  window.addEventListener ? window.addEventListener("load", loader, false) : window.attachEvent("onload", loader);
})(window, document);
</script>

Here is the result:

Interesting links/projects

You should follow me on Twitter

Share this article

Tip with Bitcoin

Tip me with Bitcoin and vote for this post!

1FKdaZ75Ck8Bfc3LgQ8cKA8W7B86fzZBe2

Leave a comment

© Thomas Sileo. Powered by Pelican and hosted by DigitalOcean.