Skip to content
Christian Heilmann

Posts Tagged ‘HTML’

  • πŸ”™ Older Entries
  • Newer Entries πŸ”œ

Using YQL to read HTML from a document that requires POST data

Monday, November 16th, 2009

YQL is a very cool tool to extract data from HTML documents on the web. Let’s face facts: HTML is a terrible data format as far too many documents out there are either broken, have a wrong encoding or simply are not structured the way they should be. Therefore it can be quite a mess to try to read a HTML document and then find what you were looking for using regular expressions or tools that expect XML compatible HTML documents. Python fans will know about beautiful soup for example that does quite a good job working around most of these issues.

Using YQL you can however use a simple web service to extract data from HTML documents. As an added bonus, the YQL engine will remove falsely encoded characters and run the data retrieved through HTML Tidy to get valid HTML back. For example to get the body content of CNN.com all you’d need to do is a:

select * from HTML where url="http://cnn.com"

select * from HTML where url="http://cnn.com"

The really cool thing about YQL is that it allows you to XPATH to filter down the data you want to extract. For example to get all the links from cnn.com you can use:

select * from html where xpath="//a" and url="http://cnn.com"

select * from html where xpath="//a" and url="http://cnn.com"

If you only want to have the text content of the links you can do the following:

select content from html where xpath="//a" and url="http://cnn.com"

select content from html where xpath="//a" and url="http://cnn.com"

You could use this for example to translate links using the Google translation API:

select * from google.translate where q in (
  select content from html where url="http://cnn.com" and xpath="//a"
) and target="fr"

select * from google.translate where q in ( select content from html where url="http://cnn.com" and xpath="//a" ) and target="fr"

Now, the other day my esteemed colleague Dirk Ginader came up with a bit of a brain teaser for me. His question was what to do when the HTML document you try to get needs POST data sent to it for it to render properly? You can append GET parameters to the URL, but not POST so the normal HTML document is not enough.

The good news is that YQL allows you to extend it in many ways, one of them is using an execute block in an open table to convert data with JavaScript on the server. The JavaScript has full e4x support and allows you to do any HTTP request. So the first step to solve Dirk’s dilemma was to write a demo page (the form was added to test it out):

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
  "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <title>Test for HTML POST table</title>
 
<body>
  <p>Below this should be a "yay!" when 
    the right POST data was submitted.</p>
<?php if(isset($_POST['foo']) && isset($_POST['bar'])){
  echo "<p>yay!</p>";
}?>
<form action="index.php" method="post" accept-charset="utf-8">
  <input type="text" name="foo" value="is">
  <input type="text" name="bar" value="set">
  <input type="submit" value="Continue &rarr;">
</form>
  </body>
</html>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html> <title>Test for HTML POST table</title> <body> <p>Below this should be a "yay!" when the right POST data was submitted.</p> <?php if(isset($_POST['foo']) && isset($_POST['bar'])){ echo "<p>yay!</p>"; }?> <form action="index.php" method="post" accept-charset="utf-8"> <input type="text" name="foo" value="is"> <input type="text" name="bar" value="set"> <input type="submit" value="Continue &rarr;"> </form> </body> </html>

The next step was to write an open table for YQL that does the necessary request and transformations.

<?xml version="1.0" encoding="UTF-8"?>
<table xmlns="http://query.yahooapis.com/v1/schema/table.xsd">
  <meta>
  <author>Christian Heilmann</author>
  <description>HTML pages that need post data</description>
  <sampleQuery><![CDATA[
select * from {table} where
url='http://isithackday.com/hacks/htmlpost/index.php' 
and postdata="foo=foo&bar=bar" and xpath="//p"]]></sampleQuery>
  <documentationURL></documentationURL>
  </meta>
  <bindings>
    <select itemPath="" produces="XML">
    <urls>
      <url>{url}</url>
    </urls>
    <inputs>
      <key id="url" type="xs:string" required="true" paramType="variable"/>
      <key id="postdata" type="xs:string" required="true" paramType="variable"/>
      <key id="xpath" type="xs:string" required="true" paramType="variable"/>
    </inputs>
    <execute>
    <![CDATA[
      var myRequest = y.rest(url);  
      var data = myRequest.accept('text/html').
                 contentType("application/x-www-form-urlencoded").
                 post(postdata).response;
      var xdata = y.xpath(data,xpath);
      response.object = <postresult>{xdata}</postresult>;
    ]]>
    </execute>
  </select> 
  </bindings>
</table>

<?xml version="1.0" encoding="UTF-8"?> <table xmlns="http://query.yahooapis.com/v1/schema/table.xsd"> <meta> <author>Christian Heilmann</author> <description>HTML pages that need post data</description> <sampleQuery><![CDATA[ select * from {table} where url='http://isithackday.com/hacks/htmlpost/index.php' and postdata="foo=foo&bar=bar" and xpath="//p"]]></sampleQuery> <documentationURL></documentationURL> </meta> <bindings> <select itemPath="" produces="XML"> <urls> <url>{url}</url> </urls> <inputs> <key id="url" type="xs:string" required="true" paramType="variable"/> <key id="postdata" type="xs:string" required="true" paramType="variable"/> <key id="xpath" type="xs:string" required="true" paramType="variable"/> </inputs> <execute> <![CDATA[ var myRequest = y.rest(url); var data = myRequest.accept('text/html'). contentType("application/x-www-form-urlencoded"). post(postdata).response; var xdata = y.xpath(data,xpath); response.object = <postresult>{xdata}</postresult>; ]]> </execute> </select> </bindings> </table>

Using this, you can now send POST data to any HTML document (unless its robots.txt blocks the YQL server or it needs authentication) and get the HTML content back. To make it work, you define the table using the “use” command:

use "http://isithackday.com/hacks/htmlpost/htmlpost.xml" as htmlpost;
select * from htmlpost where
url='http://isithackday.com/hacks/htmlpost/index.php'
and postdata="foo=foo&bar=bar" and xpath="//p"

use "http://isithackday.com/hacks/htmlpost/htmlpost.xml" as htmlpost; select * from htmlpost where url='http://isithackday.com/hacks/htmlpost/index.php' and postdata="foo=foo&bar=bar" and xpath="//p"

You can try this example in the console.

I’ve also added the table to the open YQL tables repository on github so it should show up sooner or later in the console.

Here’s a quick explanation what is going on:

<?xml version="1.0" encoding="UTF-8"?>
<table xmlns="http://query.yahooapis.com/v1/schema/table.xsd">
  <meta>
  <author>Christian Heilmann</author>
  <description>HTML pages that need post data</description>
  <sampleQuery><![CDATA[
select * from {table} where
url='http://isithackday.com/hacks/htmlpost/index.php' 
and postdata="foo=foo&bar=bar" and xpath="//p"]]></sampleQuery>
  <documentationURL></documentationURL>
  </meta>

<?xml version="1.0" encoding="UTF-8"?> <table xmlns="http://query.yahooapis.com/v1/schema/table.xsd"> <meta> <author>Christian Heilmann</author> <description>HTML pages that need post data</description> <sampleQuery><![CDATA[ select * from {table} where url='http://isithackday.com/hacks/htmlpost/index.php' and postdata="foo=foo&bar=bar" and xpath="//p"]]></sampleQuery> <documentationURL></documentationURL> </meta>

You define the schema and add meta data like the author, a description and a sample query. The latter is really important as that will show up in the YQL console when people click the table. You should normally also provide a documentation URL, but this post wasn’t written when I wrote the table so I kept it empty.

  <bindings>
    <select itemPath="" produces="XML">
    <urls>
      <url>{url}</url>
    </urls>

<bindings> <select itemPath="" produces="XML"> <urls> <url>{url}</url> </urls>

The bindings of the table describe the real API data endpoints the table points to. You have select, insert, update and delete – much like any other database. You provide an itemPath to cut down on the data returned and tell YQL if the data returned is XML or JSON.

    <inputs>
      <key id="url" type="xs:string" required="true" paramType="variable"/>
      <key id="postdata" type="xs:string" required="true" paramType="variable"/>
      <key id="xpath" type="xs:string" required="true" paramType="variable"/>
    </inputs>

<inputs> <key id="url" type="xs:string" required="true" paramType="variable"/> <key id="postdata" type="xs:string" required="true" paramType="variable"/> <key id="xpath" type="xs:string" required="true" paramType="variable"/> </inputs>

The inputs section defines what variables are expected, if they are required and what their IDs are. These IDs will be available for you as variables in the embedded JavaScript block and are normally defined by the API your table points to.

    <execute>
    <![CDATA[
      var myRequest = y.rest(url);  
      var data = myRequest.accept('text/html').
                 contentType("application/x-www-form-urlencoded").
                 post(postdata).response;
      var xdata = y.xpath(data,xpath);
      response.object = <postresult>{xdata}</postresult>;
    ]]>
    </execute>

<execute> <![CDATA[ var myRequest = y.rest(url); var data = myRequest.accept('text/html'). contentType("application/x-www-form-urlencoded"). post(postdata).response; var xdata = y.xpath(data,xpath); response.object = <postresult>{xdata}</postresult>; ]]> </execute>

Here comes the JavaScript magic inside the execute block. The y.rest(url) command sends a REST query to the URL. in the easiest form this would just mean to get the data back but in our case we need to define a few more things. We expect html back so we set the request accept header to text/html. This also ensures that the result is run through HTML Tidy before it is returned. The content type has to be like a form submission and we need to send the string postdata as a post request. The response then contains whatever our request brings back.

As we want to have the handy functionality of the original HTML table, we also need to do an xpath transformation which is done with the method of the same name.

Any JavaScript in the execute block needs to define a response.object which will become the result of the YQL query. As you can see, the E4X support of YQL allows you to simply write XML blocks without any DOM pains and you can embed any JavaScript variables inside curly braces.

  </select> 
  </bindings>
</table>

</select> </bindings> </table>

And we’re done. Using YQL execute you can move a lot of JavaScript that does complex transformations to the Yahoo server farm without slowing down your end user’s computers. And you have a secure environment to boot as there are no DOM vulnerabilities.

Tags: executetables, HTML, opentables, scraping, serverside javascript, yql
Posted in General | 11 Comments »

TTMMHTM: 8 bit lego animation, blind phreaker, code collaboration, uk postcodes and SVG for IE

Monday, August 24th, 2009

Things that made me happy this morning:

  • 8 bit trip is a 1500 hour work stop-motion movie with lego bricks.
  • James Gibbons collected a great list of assistive technology videos
  • Labuat has a wonderful interactive song and ink thing in Flash
  • If you need a UK postcode API there is Ernest Maples
  • SVGWeb is a wrapper library for SVG that renders MSIE results in Flash
  • The boy who heard too much is a very cool story about the blind phreaker “Lil’ hacker”
  • The enigma machine in Java is fun to play with
  • An attempt at version control for HTML using ins and del elements
  • Codepad is much like pastebin.com but also executes and validates the code you share in it

Tags: 8bit, accessibility, animation, assistivetechnology, codepad, collaboration, deskign, enigma, flash, HTML, ie, ink, lego, phreaking, postcodes, screenreaders, security, stopmotion, svg, uk, versioncontrol
Posted in General | 1 Comment »

TTMMHTM: Email clients survey, Disney Steampunk, RFID luggage, search interfaces, Wave protocol open sourced and a great colour schemer

Thursday, July 30th, 2009

Things that made me happy this morning:

  • My colleagues in Korea did a nice screencast of GeoMaker in Korean
  • A cool video of a fractal animation of generated mountains and lakes done in 1979-80 on a VAX!
  • Interesting survey about the popularity of email clients from campaign monitor.
  • Another good introduction to object oriented PHP – I started using json_decode(‘{foo:”bar”,baz:[“moo”,”bark”]}’);
  • PostScript and PDF for HTML5 canvas – OK then…
  • Cool, almost steampunk Disney design concepts
  • A very large collection of search interface screenshots
  • Seems like more and more airports will start tagging luggage with RFID – makes more sense than the barcode stickers anyway
  • Google releases the messaging protocol of Wave as open source – this should make it easier to extend it.
  • The Color scheme designer is not only really pretty but also allows you to simulate different forms of color blindness.

Tags: canvas, colourschemes, css, disney, emailclients, geomaker, Google, HTML, interface, korean, luggage, oo, pdf, php, postscript, protocol, rfid, screencast, search, steampunk, survey, wave
Posted in General | Comments Off on TTMMHTM: Email clients survey, Disney Steampunk, RFID luggage, search interfaces, Wave protocol open sourced and a great colour schemer

Building a hack using YQL, Flickr and the web – step by step

Wednesday, March 11th, 2009

As you probably know, I am spending a lot of time speaking and mentoring at hack days for Yahoo. I go to open hack days, university hack days and even organized my own hackday revolving around accessibility last year.

One of the main questions I get is about technologies to use. People are happy to find content on the web, but getting it and mixing it with other sources is still a bit of an enigma.

Following I will go through a hack I prepared at the Georgia Tech University hack day. I am using PHP to retrieve information of the web, YQL to filter it to what I need and YUI to do the CSS layout and add extra functionality.

The main ingredient of a good hack – the idea

I give a lot of presentations and every time I do people ask me where I get the pictures I use from. The answer is Flickr and some other resources on the internet. The next question is how much time I spend finding them and that made me think about building a small tool to make this easier for me.

This is how Slidefodder started and following is a screenshot of the hack in action. If you want to play with it, you can download the Slidefodder source code.

Slide Fodder - find CC licensed photos and funpics for your slides

Step 1: retrieving the data

The next thing I could have done is deep-dive into the Flick API to get photos that I am allowed to use. Instead I am happy to say that using YQL gives you a wonderful shortcut to do this without brooding over documentation for hours on end.

Using YQL I can get photos from flickr with the right license and easily display them. The YQL statement to search photos with the correct license is the following:


select id from flickr.photos.search(10) where text=’donkey’ and license=4

Retrieving CC licensed photos from flickr in YQL

You can try the flickr YQL query here and you’ll see that the result (once you’ve chosen JSON as the output format) is a JSON object with photo results:


{

“query”: {
“count”: “10”,
“created”: “2009-03-11T01:23:00Z”,
“lang”: “en-US”,
“updated”: “2009-03-11T01:23:00Z”,
“uri”: “http://query.yahooapis.com/v1/yql?q=select+*+from+flickr.photos.search%2810%29+where+text%3D%27donkey%27+and+license%3D4”,
“diagnostics”: {
“publiclyCallable”: “true”,
“url”: {
“execution-time”: “375”,
“content”: “http://api.flickr.com/services/rest/?method=flickr.photos.search&text=donkey&license=4&page=1&per_page=10”
},
“user-time”: “376”,
“service-time”: “375”,
“build-version”: “911”
},
“results”: {
“photo”: [
{

“farm”: “4”,
“id”: “3324618478”,
“isfamily”: “0”,
“isfriend”: “0”,
“ispublic”: “1”,
“owner”: “25596604@N04”,
“secret”: “20babbca36”,
“server”: “3601”,
“title”: “donkey image”
}

[...]
]

}
}

}

The problem with this is that the user name is not provided anywhere, just their Flickr ID. As I wanted to get the user name, too, I needed to nest a YQL query for that:

select farm,id,secret,server,owner.username,owner.nsid from flickr.photos.info where photo_id in (select id from flickr.photos.search(10) where text='donkey' and license=4)

This gives me only the information I really need (try the nested flickr query here):


{

“query”: {
“count”: “10”,
“created”: “2009-03-11T01:24:45Z”,
“lang”: “en-US”,
“updated”: “2009-03-11T01:24:45Z”,
“uri”: “http://query.yahooapis.com/v1/yql?q=select+farm%2Cid%2Csecret%2Cserver%2Cowner.username%2Cowner.nsid+from+flickr.photos.info+where+photo_id+in+%28select+id+from+flickr.photos.search%2810%29+where+text%3D%27donkey%27+and+license%3D4%29”,
“diagnostics”: {
“publiclyCallable”: “true”,
“url”: [
{

“execution-time”: “394”,
“content”: “http://api.flickr.com/services/rest/?method=flickr.photos.search&text=donkey&license=4&page=1&per_page=10”
},
[...]
],
“user-time”: “1245”,
“service-time”: “4072”,
“build-version”: “911”
},
“results”: {
“photo”: [
{

“farm”: “4”,
“id”: “3344117208”,
“secret”: “a583f1bb04”,
“server”: “3355”,
“owner”: {
“nsid”: “64749744@N00”,
“username”: “babasteve”
}

}
[...]
}

]
}

}
}

The next step was getting the data from the other resources I am normally tapping into: Fail blog and I can has cheezburger. As neither of them have an API I need to scrape the HTML data of the page. Luckily this is also possible with YQL, all you need to do is select the data from html and give it an XPATH. I found the XPATH by analysing the page source in Firebug:

Using Firebug to find the right xpath to an image

This gave me the following YQL statement to get images from both blogs. You can list several sources as an array inside the in() statement:


select src from html where url in (‘http://icanhascheezburger.com/?s=donkey’,’http://failblog.org/?s=donkey’) and xpath=”//div[@class=’entry’]/div/div/p/img”

Retrieving blog images using YQL

The result of this query is again a JSON object with the src values of photos matching this search:


{

“query”: {
“count”: “4”,
“created”: “2009-03-11T01:28:35Z”,
“lang”: “en-US”,
“updated”: “2009-03-11T01:28:35Z”,
“uri”: “http://query.yahooapis.com/v1/yql?q=select+src+from+html+where+url+in+%28%27http%3A%2F%2Ficanhascheezburger.com%2F%3Fs%3Ddonkey%27%2C%27http%3A%2F%2Ffailblog.org%2F%3Fs%3Ddonkey%27%29+and+xpath%3D%22%2F%2Fdiv%5B%40class%3D%27entry%27%5D%2Fdiv%2Fdiv%2Fp%2Fimg%22”,
“diagnostics”: {
“publiclyCallable”: “true”,
“url”: [
{

“execution-time”: “1188”,
“content”: “http://failblog.org/?s=donkey”
},
{

“execution-time”: “1933”,
“content”: “http://icanhascheezburger.com/?s=donkey”
}

],
“user-time”: “1939”,
“service-time”: “3121”,
“build-version”: “911”
},
“results”: {
“img”: [
{

“src”: “http://icanhascheezburger.files.wordpress.com/2008/09/funny-pictures-you-are-making-a-care-package-very-correctly.jpg”
},
{

“src”: “http://icanhascheezburger.files.wordpress.com/2008/01/funny-pictures-zebra-donkey-family.jpg”
},
{

“src”: “http://failblog.files.wordpress.com/2008/11/fail-owned-donkey-head-intimidation-fail.jpg”
},
{

“src”: “http://failblog.files.wordpress.com/2008/03/donkey.jpg”
}

]
}

}
}

Writing the data retrieval API

The next thing I wanted to do was writing a small script to get the data and give it back to me as HTML. I could have used the JSON output in JavaScript directly but wanted to be independent of scripting. The script (or API if you will) takes a search term, filters it and executes both of the YQL statements above before returning a list of HTML items with photos in them. You can try it out for yourself: search for the term donkey or search for the term donkey and give it back as a JavaScript call

I use cURL to get the data as my server has external pulling of data via PHP disabled. This should work for most servers, actually.

Here’s the full “API” code:


';
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $flickurl);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$output = curl_exec($ch);
curl_close($ch);
$flickrphotos = json_decode($output);
foreach($flickrphotos->query->results->photo as $a){
$o = $a->owner;
$out.= '
  • '. ''; $href = 'http://www.flickr.com/photos/'.$o->nsid.'/'.$a->id; $out.= ''.$href.' - '.$o->username.'
  • '; } $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $failurl); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); $output = curl_exec($ch); curl_close($ch); $failphotos = json_decode($output); foreach($failphotos->query->results->img as $a){ $out.= '
  • '; if(strpos($a->src,'failblog') = 7){ $out.= ''; } else { $out.= ''; } $out.= ''.$a->alt.'
  • '; } $out.= ''; if($_GET['js']=’yes’){ $out.= ‘’})’; } echo $out; } else { echo ($_GET[‘js’]!==’yes’) ? ‘

    Invalid search term.

    ’ : ‘seed({html:”Invalid search Term!”})’; } } ?>

    Let’s go through it step by step:


    if($_GET[‘js’]===’yes’){
    header(‘Content-type:text/javascript’);
    $out = ‘seed({html:’‘;
    }

    I test if the js parameter is set and if it is I send a JavaScript header and start the JS object output.


    if(isset($_GET[’s’])){
    $s = $_GET[’s’];
    if(preg_match(“/^[0-9|a-z|A-Z|-| |+|.|_]+$/”,$s)){

    I get the search term and filter out invalid terms.

    
    $flickurl = 'http://query.yahooapis.com/v1/public/yql?q=select'.
    '%20farm%2Cid%2Csecret%2Cserver%2Cowner.username'.
    '%2Cowner.nsid%20from%20flickr.photos.info%20where%20'.
    'photo_id%20in%20(select%20id%20from%20'.
    'flickr.photos.search(10)%20where%20text%3D''.
    $s.''%20and%20license%3D4)&format=json';
    $failurl = 'http://query.yahooapis.com/v1/public/yql?q=select'.
    '%20*%20from%20html%20where%20url%20in'.
    '%20('http%3A%2F%2Ficanhascheezburger.com'.
    '%2F%3Fs%3D'.$s.''%2C'http%3A%2F%2Ffailblog.org'.
    '%2F%3Fs%3D'.$s.'')%20and%20xpath%3D%22%2F%2Fdiv'.
    '%5B%40class%3D'entry'%5D%2Fdiv%2Fdiv%2Fp%2Fimg%22%0A&'.
    'format=json';
    

    These are the YQL queries, you get them by clicking the “copy url” button in YQL.


    $out.= ‘
      ‘;

    I then start the output list of the results.


    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $flickurl);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    $output = curl_exec($ch);
    curl_close($ch);
    $flickrphotos = json_decode($output);
    foreach($flickrphotos->query->results->photo as $a){
    $o = $a->owner;
    $out.= ‘
  • ‘.
    ‘‘.$href.’ – ‘.$o->username.’
  • ‘;
    }

    I call cURL to retrieve the data from the flickr yql query, do a json_decode and loop over the results. Notice the rather annoying way of having to assemble the flickr url and image source. I found this by clicking around flickr and checking the src attribute of images rendered on the page. The images with the “ico” class should tell me where the photo was from.


    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $failurl);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    $output = curl_exec($ch);
    curl_close($ch);
    $failphotos = json_decode($output);
    foreach($failphotos->query->results->img as $a){
    $out.= ‘
  • ‘;
    if(strpos($a->src,’failblog’) = 7){
    $out.= '';
    } else {
    $out.= '';
    }

    $out.= ''.$a->alt.'

  • ';
    }

    Retrieving the blog data works the same way, all I had to do extra was check for which blog the resulting image came from.


    $out.= ‘‘;
    if($_GET[‘js’]=’yes’){
    $out.= ‘’})’;
    }

    echo $out;

    I close the list and – if JavaScript was desired – the JavaScript object and function call.


    } else {
    echo ($_GET[‘js’]!==’yes’) ?
    ‘

    Invalid search term.

    ’ :
    ‘seed({html:”Invalid search Term!”})’;
    }

    }
    ?>

    If there was an invalid term entered I return an error message.

    Setting up the display

    Next I went to the YUI grids builder and created a shell for my hack. Using the generated code, I added a form, my yql api, an extra stylesheet for some colouring and two IDs for easy access for my JavaScript:


    HTML PUBLIC “-//W3C//DTD HTML 4.01//EN”
    “http://www.w3.org/TR/html4/strict.dtd”>


    Slide Fodder – find CC licensed photos and funpics for your slides





    Slide Fodder


















    Slide Fodder by Christian Heilmann, hacked live at Georgia Tech University Hack day using YUI and YQL.

    Photo sources: Flickr, Failblog and I can has cheezburger.






    Rounding up the hack with a basket

    The last thing I wanted to add was a “basket” functionality which would allow me to do several searches and then copy and paste all the photos in one go once I am happy with the result. For this I’d either have to do a persistent storage somewhere (DB or cookies) or use JavaScript. I opted for the latter.

    The JavaScript uses YUI and is no rocket science whatsoever:


    function seed(o){
    YAHOO.util.Dom.get(‘content’).innerHTML = o.html;
    }

    YAHOO.util.Event.on(‘f’,’submit’,function(e){
    var s = document.createElement(‘script’);
    s.src = ‘yql.php?js=yes&s=’+ YAHOO.util.Dom.get(’s’).value;
    document.getElementsByTagName(‘head’)[0].appendChild(s);
    YAHOO.util.Dom.get(‘content’).innerHTML = ‘‘;

    YAHOO.util.Event.preventDefault(e);
    });

    YAHOO.util.Event.on(‘content’,’click’,function(e){
    var t = YAHOO.util.Event.getTarget(e);
    if(t.nodeName.toLowerCase()===’img’){
    var str = ‘

    ‘;
    if(t.src.indexOf(‘flickr’)!==-1){
    str+= ‘

    ‘+t.parentNode.getElementsByTagName(‘a’)[0].innerHTML+’

    ‘;
    }

    str+=’x

    ‘;
    YAHOO.util.Dom.get(‘basket’).innerHTML+=str;
    }

    YAHOO.util.Event.preventDefault(e);
    });
    YAHOO.util.Event.on(‘basket’,’click’,function(e){
    var t = YAHOO.util.Event.getTarget(e);
    if(t.nodeName.toLowerCase()===’a’){
    t.parentNode.parentNode.removeChild(t.parentNode);
    }

    YAHOO.util.Event.preventDefault(e);
    });

    Again, let’s check it bit by bit:


    function seed(o){
    YAHOO.util.Dom.get(‘content’).innerHTML = o.html;
    }

    This is the method called by the “API” when JavaScript was desired as the output format. All it does is change the HTML content of the DIV with the id “content” to the one returned by the “API”.


    YAHOO.util.Event.on(‘f’,’submit’,function(e){
    var s = document.createElement(‘script’);
    s.src = ‘yql.php?js=yes&s=’+ YAHOO.util.Dom.get(’s’).value;
    document.getElementsByTagName(‘head’)[0].appendChild(s);
    YAHOO.util.Dom.get(‘content’).innerHTML = ‘ ‘src=”http://tweeteffect.com/ajax-loader.gif”’+
    ‘style=”display:block;margin:2em auto”>‘;
    YAHOO.util.Event.preventDefault(e);
    });

    When the form (the element with th ID “f”) is submitted, I create a new script element,give it the right src attribute pointing to the API and getting the search term and append it to the head of the document. I add a loading image to the content section and stop the browser from submitting the form.


    YAHOO.util.Event.on(‘content’,’click’,function(e){
    var t = YAHOO.util.Event.getTarget(e);
    if(t.nodeName.toLowerCase()===’img’){
    var str = ‘
    ‘;
    if(t.src.indexOf(‘flickr’)!==-1){
    str+= ‘

    ‘+t.parentNode.getElementsByTagName(‘a’)[0].innerHTML+’

    ‘;
    }

    str+=’x

    ‘;
    YAHOO.util.Dom.get(‘basket’).innerHTML+=str;
    }

    YAHOO.util.Event.preventDefault(e);
    });

    I am using Event Delegation to check when a user has clicked on an image in the content section and create a new DIV with the image to add to the basket. When the image was from flickr (I am checking the src attribute) I also add the url of the image source and the user name to use in my slides later on. I add an “x” link to remove the image from the basket and again stop the browser from doing its default behaviour.


    YAHOO.util.Event.on(‘basket’,’click’,function(e){
    var t = YAHOO.util.Event.getTarget(e);
    if(t.nodeName.toLowerCase()===’a’){
    t.parentNode.parentNode.removeChild(t.parentNode);
    }

    YAHOO.util.Event.preventDefault(e);
    });

    In the basket I remove the DIV when the user clicks on the “x” link.

    That’s it

    This concludes the hack. It works, it helps me get photo material faster and it took me about half an hour to build all in all. Yes, it could be improved in terms of accessibility, but this is enough for me and my idea was to show how to quickly use YQL and YUI with a few lines of PHP to deliver something that does a job :)

    Tags: flickr, hack, HTML, javascript, php, scraping, yql
    Posted in General | 5 Comments »

    Will a new browser war help web innovation?

    Friday, January 2nd, 2009

    Aircraft in formationI just spent an hour on the cycle in the gym watching the video of Douglas Crockford’s Web Forward presentation on my iPod touch. Douglas makes some great points about the state of the current technology for the web – especially browsers – being counterproductive to innovation.

    I agree with all of what Douglas says (especially the security aspects of JavaScript and the need for vats), but I am not too sure about the notion at the end that we need another browser war to go forward.

    I understand Douglas’ point about browser vendors and users knowing what they need, but I also see a big danger in allowing the way we work on the web to become multi-track once again. I worked through the first browser wars, and I am thoroughly sick of having to write code to work for one or the other browser. This is why we use libraries to work around these issues. The thing that is a bit academic about the view that browser vendors could fuel innovation by navel-gazing is that the end users are not really going to upgrade their browsers just to make our lives easier. Even serious security flaws don’t really get people to upgrade their browsers (I am not talking about us geeks, I am talking about offices and home users that just want to read their mails and get the news). We can innovate until the cows come home, but if it doesn’t reach the people we work for this is progress that makes us move away rather than forward.

    I agree with Douglas that the W3C standards are a failure when it comes to innovation. For starters they haven’t moved in ages and the standards are not nearly as good as they should be to make us work efficiently. The DOM standard is too complex, HTML does not really provide what we need to describe interfaces and interaction and CSS is not the layout engine it could be and we need to hack with positioning and floating just to get a multi column layout.

    You have to cut the W3C some slack though – if browser vendors hadn’t concentrated on putting bespoke functionality in browsers and followed the guidelines we’d have had a much easier life as web developers in the last few years and could have concentrated on working with the W3C to get the standards extended. This has improved immensely in the last years and even the biggest evildoers now got the CSS2 specs supported in the 8th revision of their browser. Communication is happening, the problem is speed.

    The process of the W3C is academic and broken, I do very much agree with that. The WHATWG are kicking butt left right and center with the HTML5 specs and got a good gig going working with browser vendors to get support for what they do. I think this is a great approach and seeing that the W3C is now looking at HTML5 in favour of the overly complex XHTML shows we are moving in the right direction.

    What I lack in the proposals of innovating with techies is that a standard is much more than how it works technically. This is what we have already done in the first browser wars: we coded to make it work. It bit us in the butt a few years later as what we built was either flaky and broke or bloated and full of hacks that are not needed any longer (I doubt you’d ever need a if(document.layers){} these days).

    Web Development is a very frustrating and complex job. Simply making things work to me is not enough – it needs to work, be usable and easy to understand for developer who take over from you. Hacks and browser specific solutions are the opposite of that.

    To me, pragmatic development means “keep it easy to understand”, not “make it work in all browsers” as “all browsers” is a very moving target. The danger we are running into right now is that we are looking at (bleeding) edge cases and see them as innovation and great pragmatic ways of working. I am a big fan of performance tweaking and saving bytes wherever we can. However you can overdo that. As Dustin Diaz explained Google are using as their doctype to save on some bytes and David Calhoun proved that it is working across the browser board right now. Fine and in the case of Google or Yahoo this does make quite a difference. However, a DOCTYPE is not only there to trigger standards mode – this is a nice side-effect. Its purpose is to tell user agents (and that is more than a browser) what the document is, how it is structured and what elements are allowed in which hierarchy. If you wanted to convert a document with this “skinny doctype” you are in trouble as the conversion tool has to hope that all is fine and dandy. Systems like Yahoo Pipes or YQL are a great way of getting data from the web and re-using it. If the data we put out on the web is not in a format we can rely on being valid, this data is unavailable.

    I like to see the web as a pool of semantic and linked information, not as a collection of documents that render correctly.

    At least one thing is for sure: this year will be interesting in terms of innovation and how we build for the web.

    Check out Douglas’ video:


    Douglas Crockford: "Web Forward" @ Yahoo! Video

    (I am tempted to add VNV Nation’s Darkangel as the ambient soundtrack)

    Tags: browserwars, crockford, css, HTML, innovation, javascript, pragmatism, standards, video, w3c, whatwg
    Posted in General | 7 Comments »

    • < Older Entries
    • Newer Entries >
    Skip to search
    • Christian Heilmann Avatar
    • About this
    • Archives
    • Codepo8 on GitHub
    • Chris Heilmann on BlueSky
    • Chris Heilmann on Mastodon
    • Chris Heilmann on YouTube
    Christian Heilmann is the blog of Christian Heilmann chris@christianheilmann.com (Please do not contact me about guest posts, I don't do those!) a Principal Program Manager living and working in Berlin, Germany.

    Theme by Chris Heilmann. SVG Icons by Dan Klammer . Hosted by MediaTemple. Powered by Coffee and Spotify Radio.

    Get the feed, all the cool kids use RSS!