del.icio.us .:. tweet

Yahoo! Search + Adobe AIR = AIR Search .:. kentbrewster.com

In which our friends Structure, Presentation, and Behavior go on a New Adventure, outside the browser window....

What's This?

Adobe's Integrated Runtime (AIR) environment claims to builds cross-platform desktop widgets from Flex files or plain old HTML, CSS, and JavaScript. As somebody who's been suspicious of Flash since it first started hanging Netscape 4, I was very interested to see whether Adobe's claims of interoperability and transparent development were true.

The short answer: yes. They are. The very first time I tried compiling an AIR app, it worked. Hopefully what follows will convey some of the delight I felt when my little widget popped up and ran.

We're going to build a Yahoo! Search widget, made with nothing but standards-compliant HTML, CSS, JavaScript, and a little help from the Yahoo! Developer Network.

Not a Coder? Just Want the Widget?

Go ahead and grab the compiled version, which looks and acts exactly like the little purple box immediately to my right. Thanks for stopping by!

Want to Poke at the Source First?

Of course you do! Here it is:

Structure:

<html>
   <head>
      <title>Yahoo! Search Widget</title>
      <link rel="stylesheet" type="text/css" href="presentation.css" />
   </head>
   <body>
      <div id="yahooSearch"></div>
      <script type="text/javascript" src="behavior.js"></script>
   </body>
</html>

Presentation:

#yahooSearch * {border:0; color:#000; font-family:arial; font-size:100%; font-size:13px; font-weight:normal; margin:0; padding:0; width:auto; }
#yahooSearch {background-color:#7c3481; border:4px solid #404; position:relative; width:180px; }
#yahooSearch a {cursor:pointer; text-decoration:none; }
#yahooSearch a.loading, #yahooSearch a.home{display:block; background:transparent url('http://l.yimg.com/us.yimg.com/i/us/my/mw/anim_loading_sm.gif') 50% 50% no-repeat; position:absolute; top:5px; left:3px; height:16px; width:20px; }
#yahooSearch a.home {background:#fff url('http://l.yimg.com/us.yimg.com/i/us/ydn/py.gif') 50% 50% no-repeat; }
#yahooSearch a.x {background:transparent url('http://l.yimg.com/us.yimg.com/i/nt/ic/ut/bsc/close12_1.gif') 50% 50% no-repeat; display:block; height:15px; position:absolute; right:4px; top:6px; width:12px; }
#yahooSearch dl {background-color:#fff; margin:0 5px 5px 5px; position:relative; width:170px; }
#yahooSearch dl dd {background:#ffa; border:1px solid #000; display:none; margin:0 10px; overflow:hidden; position:absolute; width:190px; }
#yahooSearch dl dd p {margin:5px; font-size:87%; }
#yahooSearch dl dt {position:relative; overflow:hidden; text-indent:15px; white-space:nowrap; width:170px; }
#yahooSearch dl dt.odd {background-color:#eee; }
#yahooSearch dl dt a.bookmark {background:transparent url('http://l.yimg.com/us.yimg.com/i/ypicks/icons/delicious12.gif') 0 50% no-repeat; left:2px; height:1.2em; position:absolute; top:0; width:12px; }
#yahooSearch input {border:0; background-color:#ffa; margin:5px 5px 5px 26px; height:16px; width:134px; cursor:text; }

Behavior:

var YAHOOSEARCH = function() {
   $ = {};
   return {
      init : function(el) {
         $.badge = document.getElementById(el);
         // add move handle -- only do this if we're in an AIR window
         if (window.nativeWindow){
            $.handle = document.createElement('DIV');
            $.handle.style.height = '4px';
            $.handle.style.width = '180px';
            $.handle.style.position = 'absolute';
            $.handle.style.top = '-2px';
            $.handle.style.cursor = 'move';
            $.handle.onmousedown = function() {
               window.nativeWindow.startMove();
            }
            $.badge.appendChild($.handle);
         }
         $.h = document.createElement('A');
         $.h.className = 'home';
         $.h.title = 'Visit Yahoo! Search';
         $.h.target = '_blank';
         $.badge.appendChild($.h);
         $.q = document.createElement('INPUT');
         $.badge.appendChild($.q);
         $.x = document.createElement('A');
         $.x.title = 'close';
         $.x.className = 'x';
         $.x.onmouseup = function() {
           self.close();
         }
         $.badge.appendChild($.x);
         $.r = document.createElement('DL');
         $.r.style.display = 'none';
         $.badge.appendChild($.r);
         YAHOOSEARCH.ping = [];
         $.lastQuery = '';
         setInterval(YAHOOSEARCH.runSearch, 500);
      },
      runSearch : function(query) {
         if ($.q.value) {
            if ($.q.value != $.lastQuery) {
               $.lastQuery = $.q.value;
               var n = YAHOOSEARCH.ping.length;
               $.h.className = 'loading';
               YAHOOSEARCH.ping[n] = function(result) {
                  delete YAHOOSEARCH.ping[n];
                  $.h.href = 'http://search.yahoo.com/search?p=' + $.q.value;
                  YAHOOSEARCH.removeScript('YAHOOSEARCH.ping[' + n + ']');
                  $.r.innerHTML = '';
                  $.r.style.display = 'block';
                  if (result.ResultSet.Result.length) {
                     for (var i = 0; i < result.ResultSet.Result.length; i++) {
                        var dt = document.createElement('DT');
                        var a = document.createElement('A');
                        a.className = 'bookmark';
                        a.title = 'save to del.icio.us';
                        a.onmouseup = function() {
                           YAHOOSEARCH.saveBookmark(this);
                        }
                        dt.appendChild(a);
                        var a = document.createElement('A');
                        a.innerHTML = result.ResultSet.Result[i].Title;
                        a.href = result.ResultSet.Result[i].Url;
                        a.target = '_blank';
                        a.onmouseover = function() {
                           this.parentNode.nextSibling.style.display = 'block';
                        }
                        a.onmouseout = function() {
                           this.parentNode.nextSibling.style.display = 'none';
                        };
                        dt.appendChild(a);
                        if (i % 2) { dt.className = 'odd'; }
                        $.r.appendChild(dt);
                        var dd = document.createElement('DD');
                        var p = document.createElement('P');
                        p.innerHTML = result.ResultSet.Result[i].Summary;
                        dd.appendChild(p);
                        dd.style.zIndex = i + 100;
                        $.r.appendChild(dd);
                     }
                  } else {
                     var dt = document.createElement('DT');
                     dt.innerHTML = 'Nothing found, sorry!';
                     $.r.appendChild(dt);
                  }
                  $.h.className = 'home';
               };
               var callback = 'YAHOOSEARCH.ping[' + n + ']';
               var url = 'http://search.yahooapis.com/WebSearchService/V1/webSearch?';
               url += '&appid=YahooAirSearch';
               url += '&results=10';
               url += '&output=json';
               url += '&query=' + $.q.value;
               url += '&callback=' + callback;
               YAHOOSEARCH.runScript(url, callback);
            }
         } else {
            if ($.lastQuery) {
               $.r.style.display = 'none';
               $.r.innerHTML = '';
               $.h.href = 'http://search.yahoo.com';
            }
         }
      },
      saveBookmark : function(el) {
         var ns = el.nextSibling;
         var url='http://del.icio.us/post/?';
         url += 'url=' + escape(ns.href);
         url += '&title=' + escape(ns.innerHTML);
         url += '&jump=no';
         window.open(url,'popup','width=720px,height=420px',0);
      },
      runScript : function(url, id) {
         var s = document.createElement('script');
         s.id = id;
         s.type ='text/javascript';
         s.src = url;
         document.getElementsByTagName('body')[0].appendChild(s);
      },
      removeScript : function(id) {
         var s = '';
         if (s = document.getElementById(id)) {
            s.parentNode.removeChild(s);
         }
      }
   };
}();
window.onload = function() {
   YAHOOSEARCH.init('yahooSearch');
};

XML Wrapper:

<?xml version="1.0" encoding="UTF-8"?>
<application xmlns="http://ns.adobe.com/air/application/1.0.M4" 
  appId="KENTBREW.YahooSearch" version="0.1" >
  <name>Yahoo! Search</name>
  <rootContent systemChrome="none" 
     height="500" 
     transparent="true" 
     visible="true" 
  >structure.html</rootContent>
</application>
	 

Couldn't All This Stuff Go In One File?

Not quite. The XML wrapper still needs to be separate. But yes, we could put the CSS and JavaScript into the HTML and not have to worry about separate files. I'm not doing this, because it's not good Web development practice; unless you're running PHP or some other build process behind your site, linking your CSS and JavaScript is definitely the way to go.

Let's Build It!

If you have not already done so, you'll need to install Adobe's Integrated Runtime environment. When it's all installed and working, come on back and download the source:

Download all of these to your Adobe development directory, go there (mine is C:\Program Files\Adobe\AIR\dev\) and run this:

adl YahooSearch.xml

Did your widget come up and run? When you're ready to build it, do this:

adt -package ys.air YahooSearch.xml structure.html presentation.css behavior.js

If all goes well, your search widget will be ready to install in just a few seconds. As always, have fun with this, and please let me know how it goes.

Interested in Learning How the Search Part Works?

I've done a bunch of these things; see SpiffY!Search, Build a BBC Search Widget, and Build a Wikipedia Widget, for three. The heavy lifting is all done by Yahoo!'s Search APIs, which are documented on the Yahoo! Developer Network. All we're doing here is creating some script tags on the fly to generate and receive the output, and a bit of structure to display it when it shows up.

Comments from before Disqus:

Copyright Kent Brewster 1987-2014 .:. FAQ .:. RSS .:. Contact