What is this all about?

Bubbles is a desktop application that allows you to work with your web resources in the way you want to work with them.

Key Figures

There's the Bubbles application window, we'll call it a Bubble. A Bubble carries the web resource almost like a web browser does. Since the Bubble has an advances browser capabilities there's an advanced control device for it — the Bubble seed — an XML files we call Smart Bubble. It defines the whats & the hows of its Bubble window.

Smart Bubble is sort of the embryo of the Bubble. It has the information about what Bubble will load, how it will look on the desktop, what capabilities will it have etc., just like the embryo's DNA.

If you have a Jamal's 30Boxes Smart Bubble you have a good potential of becoming Jamal's 30Boxes Bubble's parent.

So it goes from the Smart Bubble into a grown Bubble that lives on your desktop, accessible from the system tray.

Now if you don't have any Smart Bubble do not despair; open he Bubbles Settings window and find a list of preinstalled Smart Bubbles (or just load a web resource in the URL text box).

What is a Smart Bubble?

What is in the Bubbles DNA? What can a (Smart) Bubble grow into?

Here's a list of setting a Smart Bubble can contain:

  1. Web resource to load,
  2. Sys-tray icon,
  3. Bubble name
  4. and meta info
  5. Load on startup toggle
  6. Grease Monkey Javascript page handlers
  7. Bubble custom shortcut menu specs

Here's how it looks

<site>
	<title>30 Boxes</title>
	<url>http://www.30boxes.com/</url>
	<startup>0</startup>
	<icon>bubbles.ico</icon>
	<update_icon>1</update_icon>
	<shortcuts>http://www.3d3r.com/30boxes-for-vdz.xml</shortcuts>
	
	<info>
		<![CDATA[
			author: Ohad Pressman, 3D3R Software studios
			url:	http://www.3d3r.com/bubbles
			date:	26/06/2006
			desc:	This scripts generates visual notifications (BB_balloon) for 30boxes events
					For more information on the Bubbles API, visit http://www.3d3r.com/bubbles/developers.php
		]]>
	</info>
	
	<page_handler>
		<url>30boxes.com</url>
		<match>strstr</match>
		<code>
			javascript:
			<JS code>
		</code>
	</page_handler>
</site>
					

Explanation

Obviously <site> is a root element, and every definition should be inside it.

<title>30 Boxes</title> — The Smart Bubble title as appears in the Bubbles settings window.

<url>http://www.30boxes.com/</url> — Web resource URL, duh!

<startup>0</startup> — 0 — do not lot on start up, 1 — do load this Bubble on start up.

<icon>bubbles.ico</icon> — Bubble's icon location, if not specified website's favicon is used.

<shortcuts>http://www.3d3r.com/30boxes-for-vdz.xml</shortcuts> — Bubble shortcut menu is specified by this URL. If the shortcuts tag is omitted or empty, Bubble looks for custom menu in the following two locations by default:

  1. Resource native: if there's a bubbles.xml file with shortcuts menu on the hosting domain it'll be used for building the shortcuts menu for the Bubble. For example suppose you load http://vdz.livejournal.com the bubbles.xml file will be searched for at livejournal.com.
  2. If the first option yielded no results there's yet another default location for the custom shortcuts menus on our website. Once 3D3R team needs a shortcut menu for a frequently used Bubble it puts the XML file at 3d3r.com/bubbles/site-menus, while each menu file has a name in accordance with the domain name of the resource: livejournal.com.xml.

Learn how to make your own shortcut menus.

Meta data is used for … It's contains a free text and is defines as character data (<![CDATA[]]>). Meta data format is carriage-return-separated text lines, while the definition is the first work with a colon (:) :

  • author: Ohad Pressman, 3D3R Software studios
  • url: http://www.3d3r.com/bubbles
  • date: 26/06/2006
  • desc: This scripts generates visual notifications (BB_balloon) for 30boxes events

Very much like the HTML meta tags.

Javascript page handlers

Now to the funky part: what if we've told you that you can alter or add your own Javascript to any location inside the web resource of the Bubble. Using Greasemonkey, Bubbles adds page handlers easily even before you load the resource.

Here's how:

<page_handler>

Define a new page handler, you’ll usually want to define different handlers for different site locations

	<url>30boxes.com</url>

Here's where you specify where to add this Javascript handler. If the Bubbles loads a resource with URL matting the pattern specified in this XML node, the handler script will be added to the page and ready to be activated.

	<match>strstr</match>

This is a default matching method, for checking whether the resource address is valid for applying the handler

	<code>

And here goes you code …

		JS code
	</code>
</page_handler>

You get the picture.

So no you can load a Bubble using Smart Bubble settings, now learn what you can do with the Bubble.

Extended Document Object Module

As was explained before Bubble is very much like a browser window (IE). It is stripped from unnecessary user interface element and enriched with features that allow you to manipulate it using your regular Javascript.

So imagine that you want to overwrite Google Calendar alerts with system tray notifications (!!!) — simple:

In a new Google Calendar Smart Bubble add a page handler that looks like this:

<page_handler>
	<url>google.com/calendar</url>
	<match>strstr</match>
	<code>
		function alert(msg) // Overwriting Google’s own alert function …
		{
		        //… with Bubble's BB_Balloon function that pops up 
		        //  a system tray notification!
			BB_Balloon('Google Calendar', msg); 
		}
	</code>
</page_handler>
				    

Can it be any simpler?!

What else can you do with the Bubble's DOM

You can:

  1. Know whether Bubble is visible or not — BB_visible
  2. Pop Bubble balloon — BB_balloon(title, message, action, delay)
  3. Set Bubble icon — BB_setIcon(url)
  4. Manage Bubble shortcut menus:
    1. BB_addMenuItem(title, action, iconUrl)
    2. BB_modifyMenuItem(itemId, title, action, iconUrl)
    3. BB_removeMenuItem(itemId)
    4. BB_clearMenu()
  5. Managed files "dropped" into the Bubble:
    1. BB_dropHandler()
    2. BB_numDropFiles
    3. BB_getDropFileName(index)
    4. BB_getDropFileData(index, encoding)
  6. Manage Bubble context session:
    1. persistent: BB_setValue(name, value)
    2. persistent: BB_getValue(name)
    3. volatile: BB_setSessionValue(name, value)
    4. volatile: BB_getSessionValue(name)
  7. Logging — BB_log(message)
  8. XmlHttpRequest — BB_xmlHttpRequest
  9. Bubbles version — BB_version

Let's have an example

Flickr drag, drop & upload script

Checking if it's the first load of the Bubble:

// tell people about the ability to drag and drop
if (BB_getValue('firstRun') == '')
{
BB_setValue('firstRun', '1');
	BB_balloon('Welcome\n', 
	           'Upload up to six pictures at a time by dragging them into the Flickr bubble', '', 15);
}

So as you can see that firstRun variable wasn't defines and is an empty string. Once set, though, will remember its value throught the …

Adding file drop handler:

// add a drop handler to any flickr page
function BB_dropHandler()
{
 	// are we logged in?
	var loggedIn = (typeof global_auth_hash != 'undefined' && global_auth_hash != 0);
				
	if (!loggedIn)
	{
		BB_balloon("Oops!', 'It looks like you\'re not logged-in to Flickr, please login in order to upload', 
		           "document.location.replace('http://www.flickr.com/signin/');", 30);
	}
	else
	{
		bblsDoFlickrUpload();
	}
}

Flickr has it's [web API published], and we make use of it in everything concerning currently logged in user's account. Information & tutorials on how to work with Flickr's API are to be found in abundance on the web. For example, [how do I know if the current user is authenticated by Flickr].

So if the user is authenticated we move on:

function bblsDoFlickrUpload()
{
// prepare the form variables
    var formVars = 
    {	
        'done'          : '1',
        'complex_perms'	: '0',
        'magic_cookie'	: global_auth_hash,
        'MAX_FILE_SIZE'	: '31457280',
        'is_public_0'	: '1',
        'tags'          : '',
        'Submit'        : 'UPLOAD'
    };
    								
    // prepare the files to upload (referenced by form-id and dragged-file-id)
    // we currently cap it at 6 files, we can later split this to several 
    // httprequests if needed
    var numFiles = BB_numDropFiles();
    if (numFiles > 6) numFiles = 6;

    // the 'for' loop gave us problem for some unknown reason
    // this syntax: {'dragged_id' : 0}, tells bubbles 
    // to use the binary data of dragged-file X
    // and send it as variable-name Y ('file1' for example)
    if (numFiles > 0) formVars['file1'] = {'dragged_id' : 0};
    if (numFiles > 1) formVars['file2'] = {'dragged_id' : 1};
    if (numFiles > 2) formVars['file3'] = {'dragged_id' : 2};
    if (numFiles > 3) formVars['file4'] = {'dragged_id' : 3};
    if (numFiles > 4) formVars['file5'] = {'dragged_id' : 4};
    if (numFiles > 5) formVars['file6'] = {'dragged_id' : 5};
    			
    // show feedback to user (uploading...)
    bblsCreateUploadModal();

    // post to flickr
    BB_xmlHttpRequest(
    {
	    method:	    'POST',
	    url:        'http://flickr.com/photos_upload_process.gne',
	    headers:    {'User-agent'	: 'Mozilla/4.0 (compatible) Bubbles'},
	    form:       formVars,
	    onload:     'bbls_onLoad',
	    onerror:    'bbls_onError'
        } );
}

Here's the bblsCreateUploadModal, a function that creates a lightbox overlay screen and prints an upload progress message. You can experiment with any other visual solution of course, but this is what we did:

// this is just for creating a fancy 'uploading...' modal
function bblsCreateUploadModal()
{
	// prepare ie: body element
	bod = document.getElementsByTagName('body')[0];
	bod.style.height = '100%';
	bod.style.overflow = 'hidden';

	htm = document.getElementsByTagName('html')[0];
	htm.style.height = '100%';
	htm.style.overflow = 'hidden'; 

	// create the overlay block			
	bod                     = document.getElementsByTagName('body')[0];
	overlay                 = document.createElement('div');
	overlay.id              = 'overlay';
	overlay.style.cssText   = "position:absolute; top:0px; left:0px; 
	                          width:100%; height:100%; z-index:5000; 
	                          background-color:#000; -moz-opacity: 0.8; opacity:.80; 
	                          filter: alpha(opacity=80);text-align: center;";
	bod.appendChild(overlay);
	
	// create the lightbox block
	bod                 = document.getElementsByTagName('body')[0];
	lb                  = document.createElement('div');
	lb.id               = 'bbls_lb';
	lbPosX              = (document.body.offsetWidth/2)-100;
	lbPosY              = (document.body.offsetHeight/2)-25;
	lb.style.cssText    = 'position: absolute; top:' + lbPosY 
	                      + 'px; left:' + lbPosX + 'px; z-index:9999; width:200px; 
	                      height:50px; margin:0; border:2px solid #fff; 
	                      background:#fefeff; text-align:left; 
	                      font: arial,sans-serif; font-size: 16pt;
	                      border:5px solid #000;text-align:center;padding:20px;';
	
	// put in a progress message
	lb_txt                  = document.createElement('text');
	lb_txt.innerHTML        = 'Uploading &hellip;';
	lb_txt.id               = 'bbls_lb_txt';
	lb_img                  = document.createElement('img');
	lb_img.src              = 'http://www.3d3r.com/bubbles/images/preloader.gif';
	lb_img.style.cssText    = 'display:block;margin: 15px auto 0';
	
	lb.appendChild(lb_txt);
	lb.appendChild(lb_img);
	
	bod.appendChild(lb);
}

Now let's look into how BB_xmlHttpRequest works:

// post to flickr
BB_xmlHttpRequest(
{
	method:     'POST',
	url:        'http://flickr.com/photos_upload_process.gne',
	headers:    {'User-agent'	: 'Mozilla/4.0 (compatible) Bubbles'},
	form:       formVars,
	onload:     'bbls_onLoad',
	onerror:    'bbls_onError'
} );

method, url & headers are pretty much self explanatory, they are usually used as the parameters for the XmlHttpRequest.open() function.

form specifies what parameters to upload to the specified URL, like this: form: {"name" : "joe", "file1" : {'dragged_id' : 4}} this shows how to post a regular variable called name, and a binary file called file1.

onload & onerror specify event handler functions bbls_onLoad & bbls_onError respectively. Now let's take a look on these functions. All the work is done “behind the scenes”;

function bbls_onError(responseDetails)
{
    BB_balloon('Flickr\n', 'Photo upload failed.', '', 15);
    		
    document.getElementById('bbls_lb').stlye.display = 'none';
    document.getElementById('bbls_overlay').stlye.display = 'none';
}

Here after the form is submitted and the data is uploaded and there wasn't any errors in receiving the server side result, the event handler receives the XmlHttpRequest object as responseDetails:

function bbls_onLoad(responseDetails)
{
    // feedback to user (after uploading comes processing...)
    bblsCreateProcessingModal();

    // now we need to check if the image is ready on flickr
    	
    // prepare a few variables, parse them out of the response flickr sent us
    var getRespVar = function(varName)
    {
	    var x   = responseDetails.responseText;
	    var a   = x.search("var " + varName + " = '");
	    var b   = x.substring(a, x.length-a-1);
	    var c   = b.search("'")+1;
	    var d   = b.substring(c, b.length-c-1);
	    var e   = d.search("'");
	    var f   = d.substring(0, e);

	    BB_setSessionValue(varName, f);
    }
    			
    getRespVar('tickets');
    getRespVar('oks');
    getRespVar('fails');

    // we'd go with this but it gave JS errors				
    //document.write(responseDetails.responseText);
    			
    // check the status
    bbls_checkUploadStatus();
}

What happens above is that after successfully receiving flckr's response, we put up and new progress message on screen (“Processing …” — this is a simple function that does not need a specific explanation), fetch tickets, oks & fails values from the flickr’s response and save them in Bubble session.

After the pictures are uploaded to flickr you need to check if there are ready to use, we do it using these functions:

function bbls_checkUploadStatus()
{
    // check if the images are ready
    var qs	 = 'oks=' + BB_getSessionValue('oks');
    qs		+= '&fails=' + BB_getSessionValue('fails');
    qs		+= '&tickets=' + BB_getSessionValue('tickets');
    qs		+= '&cb=' + (new Date().getTime());
    			
    BB_xmlHttpRequest(
    {
	    method:     'GET',
	    url:        'http://flickr.com/photos_upload_rs_check.gne?' + qs,
	    onload:     'bbls_checkUploadOnLoad',
	    onerror:    'bbls_checkUploadOnError'
    });
}

In the bbls_checkUploadStatus we send a request to flickr according to what's [specified in their API reference] in order to receive pictures' readiness status. Ohad please explain what is “req” the parameter, I guess it’s the XmlHttpRequest object.

The following functions both receive the XmlHttpRequest (in question) object as a parameter.

function bbls_checkUploadOnError(req) { bbls_uploadComplete(0); }

function bbls_checkUploadOnLoad(req)
{
    // we've got some sort of response, check what it means
    var A = req.responseText.split('|');
    if (A.length != 3) 
    { 
	    setTimeout('bbls_checkUploadStatus()', 750);
	    return;
    }

    var tickets = A[0];
    var ticketsA = tickets.split(',');
    var fails = A[2];
    var oks = A[1];

    if (tickets.length == 0)
    {
	    bbls_uploadComplete(1);
    } else 
    {
	    BB_setSessionValue('tickets', tickets);
	    BB_setSessionValue('fails', fails);
	    BB_setSessionValue('oks', oks);
	    setTimeout('bbls_checkUploadStatus()', 750);
    }
}

Ohad can you provide more details about the nature of the test in the above function

function bbls_uploadComplete(success)
{
    // go the user's photos page
    var username = eval("document.getElementById('TopBar').childNodes[0].childNodes[0].childNodes[0].childNodes[1].childNodes[1].innerHTML");

    var urlAdd		= (typeof userName != 'undefined' && userName != 0) ? 
    '/'+userName+'/' : '';

    document.location.replace('http://www.flickr.com/photos' + urlAdd);

    if (success)
    {
	    var words = ['Funky', 'Halluluya', 'Eye Karamba', 'Wham', 'Sweet', 'Yeah', 'Yeeha'];
	    var word	= words[ Math.floor(words.length * (Math.random() % 1)) ];
	    BB_balloon(word + '!\n', 'Photos uploaded succesfully', '', 6);
    }
    else
    {
	    BB_balloon('Flickr\n', 'Photo processing failed.', '', 15);
    }
}

And finally the Bubble is redirected to the flickr user's home page, and, if upload was successful, the Bubble balloon is displayed with a random “success message”.

Learn To Make Your Own Shortcut Menus

What are Bubbles shortcuts?

[shortcuts screenshots on the side]

They are links or Javascript command to be loaded in a Bubble from a systray right-click menu. [image]

Why do I need those?

So that you can jump to your “commented-on” flickr photos with 2 clicks, or to your friends page in your Live Journal blog, or your “To do list” in your 30boxes calendar. You got the idea …

How is the shortcuts menu defined?

The menu is defined using an XML file, structured to specify menu items: caption & action:

<shortcuts site="30boxes.com">
	<shortcut>
		<name>Today</name>
		<action>javascript:d = new Date();showDayView(d.valueOf());</action>
	</shortcut>
	<shortcut>
		<name>Recent Updates</name>
		<action>javascript:showSearchView('getRecentUpdates','');</action>
	</shortcut>
	<shortcut>
		<name>My Account</name>
		<action>/account.php</action>
	</shortcut>
</shortcuts>

As you can see, it’s easy! Name node is for the item’s caption, and action is for the link to be redirected to or javascript to be executed (sort of like [favelets]/[bookmarklets]).

How do I deploy the menu file?

Either have it on your computer locally or upload to the web, then specify the file’s URL in your Smart Bubble (<shortcuts>). If you are a site builder you can just put the file on your website’s root directory, or [send it to us], to make a default shortcuts menu.