XDM Bookmarklet Example

Drag the link below into your bookmark bar.

Fvb Xdm

Initial Setup

XDM and IFrame bookmarklets allow you to insert an iframe from your app into the 'host' page which can communicate seamlessly with the bookmarklet code running in the 'host' page.

To enable cross domain communictaion between your site and other pages on the 'net we need to be able to generate a fully qualified URL to your bookmarklet code. To set the correct host you should add this to each of your confing/environment/*.rb files. Adjust the :host accordingly. :port is optional.

  config.action_controller.default_url_options = {:host => 'localhost', :port => 3000}

XDM Basics

To enable cross-domain communication Easymarklet uses easyXDM to insert an iframe into the remote page which points back to your app. The code running in the context of the remote page is called the consumer. The code running on the page that has been iframed in is called the producer.

Function calls can be passed from the consumer to the producer, and the then the producer can interact with your app and then push a message back to the consumer. You could also use polling or websockets to allow your app to 'push' data to the remote page.

Generating this bookmarklet

  rails g easymarklet:xdm fvb_xdm

You'll get a bunch of files.


$ rails g easymarklet:xdm fvb_xdm
      create  app/assets/javascripts/fvb_xdm_bookmarklet.js
      create  app/assets/javascripts/fvb_xdm_consumer.js
      create  app/assets/javascripts/fvb_xdm_producer.js
      create  app/controllers/fvb_xdm_producer_controller.rb
      create  app/views/fvb_xdm_producer/index.html.erb
      create  app/views/layouts/fvb_xdm_producer.html.erb
       route  match 'fvb_xdm_producer' => 'fvb_xdm_producer#index'

You can link to your new bookmarklet with this :

<%= link_to 'Fvb Xdm', easymarklet_js('fvb_xdm_consumer.js') %>

File Overview

*_bookmarklet.js
This is where your code goes. It's the central interface between your consumer (the code that runs on other pages), and your producer (the pages on your app). You don't link directly to this.
*_consumer.js
A manifest that includes a consumer helper from Easymarklet and your bookmarklet code. You link to this with the easymarklet_js helper. It will be loaded into the page after someone clicks your bookmarklet.
*_producer.js
A manifest that includes a producer helper from Easymarklet and your bookmarklet code. This is included on page on your app that provides the service.
*_producer_controller.rb
An empty controller to handle serveing the producer page. Customize this if you need to make data available to the producer at load time.
index.html.erb
A basic template for the producer page.
layouts/*_producer.html.erb
A simple layout that includes the *_producer.js manifest.

Update the code

The only JS file that you'll need to modify is app/assets/javascripts/fvb_xdm_bookmarklet.js

(function(){
  
  var FvbXdmBookmarklet = {

    visible : false,
    consumer : {
      css : ['/assets/fvb_simple_bookmarklet.css'], // could be an array or a string
      init : function(full_host,remote){
        full_host = full_host == undefined ? '' : full_host
        // Start building up a UI container.
        // We take the 'long way' here because we may not have
        // any convenience libraries loaded in the consumer page.
        var content = document.createElement('div')
        content.id = 'fvb_simple_content'
        
        var vote = document.createElement('p')
        vote.appendChild(document.createTextNode('Vote on the Foo V Baz status of this page.'))
        
        // Set up a couple of links which call the fvbPost function
        // that is declared in our producer
        var foo = document.createElement('a')
        foo.appendChild(document.createTextNode('Vote foo!'))
        foo.onclick = function(){
          remote.fvbPost('Foo',document.location.href,handleResults);
        }

        var baz = document.createElement('a')
        baz.appendChild(document.createTextNode('Vote baz!'))
        baz.onclick = function(){
          remote.fvbPost('Baz',document.location.href,handleResults);
        }

        // Give the user a way to opt out mid stream
        var no = document.createElement('a')
        no.appendChild(document.createTextNode('Nevermind, voting is for suckers.'))
        no.onclick = function(){
          document.body.removeChild(document.getElementById('fvb_simple_insert'));
        }
        // Chain everything together and add it to the document
        content.appendChild(vote)
        content.appendChild(foo)
        content.appendChild(baz)
        content.appendChild(no)
        document.body.appendChild(FooVsBaz.wrap(content,'fvb_simple_insert'));

        // A handler to display the current voting results
        function handleResults(data){
          console.log(data);
          var response = document.createElement('div')
          response.appendChild(document.createTextNode('Congratulations!  Your vote has been counted.'))

          var foo = document.createElement('div')
          foo.appendChild(document.createTextNode('Foo : ' + data.foo_count ))
          response.appendChild(foo)

          var baz = document.createElement('div')
          baz.appendChild(document.createTextNode('Baz : ' + data.baz_count ))
          response.appendChild(baz)


          var no = document.createElement('a')
          no.appendChild(document.createTextNode('Thank you, come again!'))
          no.onclick = function(){
            document.body.removeChild(document.getElementById('fvb_simple_insert'));
          }
          response.appendChild(no)

          content.innerHTML = "";
          content.appendChild(response);
        }
      },

      methods : { // The methods that the producer can call
        // We don't have anything for this demo!
     }
    },
    producer : {
      path : "/fvb_xdm_producer", // The path on your app that provides your data service
      methods : { // The methods that the consumer can call
        // The RPC method used by our consumer.
        fvbPost : function(vote,url,callback){
          // Since this code will be executed inside of our producer page
          // and not on the client, we can make use of JQuery
          $.post( '/pages', { page : { url : url, vote : vote } }, callback, 'json')
        }
      }
    }
  }
  
  window.FvbXdmBookmarklet = FvbXdmBookmarklet;

})();

You'll also need to add some css to style the UI elements that the bookmarklet adds to the page.

app/assets/stylesheets/fvb_xdm_bookmarklet.css

/*
 *= require application/logo_bar
 */

#fvb_simple_insert{
  position:fixed;
  top:10px;
  left:10px;
  background:black;
  border:3px double white;
  color:black;
  padding:5px;
  -webkit-box-shadow: 7px 7px 5px rgba(50, 50, 50, 0.75);
  -moz-box-shadow:    7px 7px 5px rgba(50, 50, 50, 0.75);
  box-shadow:         7px 7px 5px rgba(50, 50, 50, 0.75);
  border-radius: 8px;
  -moz-border-radius: 8px;
  -webkit-border-radius: 8px;
  z-index:10000;

}

#fvb_simple_frame{
  background:#eee;
  padding: 0px;
  border-radius: 8px;
  -moz-border-radius: 8px;
  -webkit-border-radius: 8px;
}

#fvb_simple_content{
  padding:5px;
}

#fvb_simple_insert a{
  display:block;
  padding:5px;
}