IFrame Bookmarklet Example

Drag the link below into your bookmark bar.

Fvb Iframe

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:iframe fvb_iframe

You'll get a bunch of files.


$ rails g easymarklet:iframe fvb_iframe
      create  app/assets/javascripts/fvb_iframe_bookmarklet.js
      create  app/assets/javascripts/fvb_iframe_consumer.js
      create  app/assets/javascripts/fvb_iframe_producer.js
      create  app/controllers/fvb_iframe_producer_controller.rb
      create  app/views/fvb_iframe_producer/index.html.erb
      create  app/views/layouts/fvb_iframe_producer.html.erb
       route  match 'fvb_iframe_producer' => 'fvb_iframe_producer#index'

You can link to your new bookmarklet with this :

<%= link_to 'Fvb Iframe', easymarklet_js('fvb_iframe_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

In the fvb_iframe_bookmarklet.js file we just need to add a link to a stylesheet to use for styling the iframe, and alter the init function to set up our AJAX interaction with the server.

app/assets/javascripts/fvb_iframe_bookmarklet.js

(function(){
  
  var FvbIframeBookmarklet = {

    visible : true,
    consumer : {
      css : ['/assets/fvb_iframe_bookmarklet.css'], // could be an array or a string
      methods : { // The methods that the producer can call
      }
    },
    producer : {
      path : "/fvb_iframe_producer", // The path on your app that provides your data service
      init : function(consumer_url,consumer){
        // Set up some click handlers
        $('#vote_foo').click(function(){
          doPost('Foo')
          return false;
        });

        $('#vote_baz').click(function(){
          doPost('Baz')
          return false;
        });
        $('.fvb_close').click(function(){
          consumer.closeFrame();
          return false;
        });
        // Send the vote to the server via AJAX and pass the rusults off to the handleResults function
        function doPost(vote){
          url = consumer_url;
          $.post( '/pages', { page : { url : url, vote : vote } }, handleResults, 'json')
        }
        // A handler to display the current voting results
        function handleResults(data){
          $("#foo_count").html(data.foo_count);
          $("#baz_count").html(data.baz_count);
          $('#vote').hide();
          $('#results').show();
        }

      },
      methods : { // The methods that the consumer can call
        // We only need one way communication for this one.
      }
    }
  }
  
  window.FvbIframeBookmarklet = FvbIframeBookmarklet;

})();

Now we add just a touch of CSS that will control the appearance of our floating iframe. Since the UI for the bookmarklet will run inside the iframe (on our site, instead of directly in another page) we don't have to include CSS for any of our actual UI elements.

app/assets/stylesheets/fvb_iframe_bookmarklet.css

#easymarklet_div{
  position:fixed;
  top:10px;
  left:10px;
  background:black;
  border:3px double white;
  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;
}

Next we update the index.html.erb template to include our voting options.

app/views/fvb_iframe_producer/index.html.erb

<div id="vote">
  <p>Cast your vote now!</p>
  <%= link_to "Vote Foo", "", :class => 'btn', :id => 'vote_foo' %>
  <%= link_to "Vote Baz", "", :class => 'btn', :id => 'vote_baz' %>
  <%= link_to "Nevermind", "", :class => 'fvb_close' %>
</div>
<div id="results" style="display:none;">
  <p>Congratulations! Your vote has been counted.</p>
  <ul>
    <li>Foo : <span id="foo_count"></span></li>
    <li>Baz : <span id="baz_count"></span></li>
  </ul>
  <%= link_to "Thank you, come again!", "", :class=>"fvb_close" %>
</div>

We also tweak the laoyout to include our logo bar.

app/views/layouts/fvb_iframe_producer.html.erb

<!DOCTYPE html>
<html>
<head>
<title>Fvb Iframe Producer</title>
  <%= stylesheet_link_tag    "application", :media => "all" %>
  <%= javascript_include_tag "application" %>
  <%= csrf_meta_tags %>
</head>
<body>

<%= render 'layouts/fvb_logo_bar' %>
<div id="main_content" class="well">
  <%= yield %>
</div>


<%= javascript_include_tag "fvb_iframe_producer" %>
<script type="text/javascript" charset="utf-8">
  var easymarklet_consumer_url = "<%= params['url'] %>";
</script>
</body>
</html>

Going to production

Before deploying you need to make sure that a few things are available as individual files through the asset pipeline. Add this to the bottom of the config block in config/application.rb.

config.assets.precompile += %w( fvb_iframe_consumer.js )
config.assets.precompile += %w( fvb_iframe_producer.js )
config.assets.precompile += %w( fvb_iframe_bookmarklet.css )