Sencha

Playing with Ext.js, YQL and BBC radio1 Essential Mix

I’m a big fan of the radio1 essential mix and list to almost every week. I’m also getting more and more stuck into Ext.js at work. I also saw a great talk by Christian Heilmann of Yahoo at Full Frontal last and he demo’ed YQL. So I thought I’d give it a go and make a quick Ext app which uses YQL and pulls in the data of the tracklists for the Radio 1 Essential Mixes…
And here it is: http://www.mattgoldspink.co.uk/bbcradiolastfm.html
To give a brief summary of how I did it

  1. Getting the tracklisting from the BBC
    This basically involved me picking out a rough top level element from an essential mix page of tracks (firebug is lord). This resulted in the below YQL query:
    select * from html where url="http"+"://ww"+"w.bbc.co.uk/programmes/b00sdb2z" and xpath='//div[@class="title" or @class="title with-image"]'
    
  2. Now getting this into a simple Ext.js ListView
    It was obvious to me I needed a Ext.data.ScriptTagProxy to achieve this in order to get the JSON-P cross domain call. Unfortunately the hard bit is getting the JSON from the YQL call into the right format for my JsonStore in Ext. Thankfully Ext has all the necessary hooks to do this it looks something like:
    var dataStore = new Ext.data.JsonStore(
    	{
    		proxy : new Ext.data.ScriptTagProxy(
    			{
    				url : 'http'+'://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20html%20where%20url%3D%22http%3A%2F%2Fw'+'ww.bbc.co.uk%2Fprogrammes%2Fb00sdb2z%22%20and%0A%20%20%20%20%20%20xpath%3D\'%2F%2Fdiv%5B%40class%3D%22title%22%20or%20%40class%3D%22title%20with-image%22%5D\'&format=json',
    				callbackParam: 'callback',
    				restful: true
    			}
    		),
    		root: 'query.results.div',
    		fields: [{
                        name : 'artist',
                        convert : Ext.ux.mattgoldspink.bbcessentialmix.getArtistFromJson
                    },{
                        name : 'track',
                        convert : Ext.ux.mattgoldspink.bbcessentialmix.getTitleFromJson
                    }],
    		allowBlank: false
    	}
    );
    

    Where the convert functions were:

    function js_traverse(o, keyName) {
    	var type = typeof o;
    	if (o.class && o.class === keyName)
    	{
    		return o.content;
    	}
    	if (type == "object") {
    		for (var key in o) {
    			var output = js_traverse(o[key], keyName);
    			if (output) {
    				return output;	
    			}
    		 }
    	} else {
    		return undefined;
    	}
    };
    
    Ext.ux.mattgoldspink.bbcessentialmix.getArtistFromJson = function(v, rec) {
    	var artist = js_traverse(rec, 'artist');
    	if (artist) {
    			return artist;
    	} else {
    		return 'Unknown';	
    	}
    };
    
    Ext.ux.mattgoldspink.bbcessentialmix.getTitleFromJson = function(v, rec) {
    	var artist = js_traverse(rec, 'track');
    	if (artist) {
    			return artist;
    	} else {
    		return 'Unknown';	
    	}
    };
    

    The interesting part here is that the BBC very helpfully markup all their tags so all I had to do was traverse the messy JSON tree until I found a class which marked either the title or artist and then return it (or ‘Unknown’ if it was there).
    Next we plug this into our Ext.ListView:

    var listView = new Ext.list.ListView({
    	id: 'listview',
    	store: dataStore,
    	multiSelect: true,
    	emptyText: 'No tracks found',
    	columnSort: false,
    	loadingText: 'Updating tracklist',
    	tpl: new Ext.XTemplate(
    	'<tpl for="rows">',
    		'</tpl>&lt;tpl exec="values.row = xindex;">',
    			'<dl>',
    				'<tpl for="parent.columns">',
    				'<dt style="width:{&#91;values.width*100&#93;}%;text-align:{align};">',
    				'<em unselectable="on"<tpl if="cls">',
                                    ' class="{cls}</em></dt></tpl>">',
    					'{[values.tpl.apply(parent)]}',
    				'<div class="x-clear"></div>',
    			'</dl>',
    		''
    	),
    	columns: [
    	{
    		header: 'Track #',
    		width: 0.04,
    		align: 'left',
    		dataIndex: 'row'
    	},{
    		header: 'Artist',
    		width: 0.48,
    		align: 'left',
    		dataIndex: 'artist'
    	},{
    		header: 'Track',
    		width: 0.48,
    		align: 'left',
    		dataIndex: 'track'
    	}]
    });
    

    This code is a bit more complicated than I expected because by default Ext.ListView does not with the Ext.grid.RowNumberer, so after a bit of google-fu I discovered a post showing how to achieve this using the tpl property.

  3. Getting all the possible Essential Mixes
    I decided I’d add a TreePanel so you can pick other essential mixes and view their tracks too. To do this I needed to jump back to firebug and YQL to get the available tracklists. In short my YQL query ended up being
    select * from html where url="http://www.bbc.co.uk/programmes/b006wkfp/episodes/2009" and xpath='//a[@class="url"]'
  4. Getting the TreePanel to pull in the data from YQL
    Again this is something I thought was going to be tough, but after looking at a few blog posts and examples I realised it was possible. My final TreeLoader implementation looks like:
    Ext.ux.mattgoldspink.bbcessentialmix.TreeLoader = function(){
    	Ext.ux.mattgoldspink.bbcessentialmix.TreeLoader.superclass.constructor.call(this);
    	this.proxy = new Ext.data.ScriptTagProxy({
    		url : this.dataUrl,
    		callbackParam: 'callback',
    		restful: true
    	});
    };
    Ext.extend(Ext.ux.mattgoldspink.bbcessentialmix.TreeLoader, Ext.tree.TreeLoader, {
    	dataUrl: 'http:'+'//query.yahooapis.com/v1/public/yql?'
    +'q=select%20*%20from%20html%20where%20url%3D'
    +'%22http%3A%2F%2Fwww.bbc.co.uk%2Fprogrammes'
    +'%2Fb006wkfp%2Fepisodes%2F2010%22%20and%0A'
    +'%20%20%20%20%20%20xpath%3D\'%2F%2Fa%5B'
    +'%40class%3D%22url%22%5D\'&format=json',
    	requestData : function(node, cb){
    		this.proxy.setApi(
                         Ext.data.Api.actions.read, 
                         'http'+'://query.yahooapis.com/v1/public/yql?'
    +'q=select%20*%20from%20html%20where%20url'
    +'%3D%22http%3A%2F%2Fww'
    +'w.bbc.co.uk%2Fprogrammes%2F'
    +'b006wkfp%2Fepisodes%2F' + node.attributes.nodeid 
    + '%22%20and%0A%20%20%20%20%20'
    +'%20xpath%3D\'%2F%2Fa%5B%40class'
    +'%3D%22url%22%5D\'&format=json');
    		this.proxy.request('read', null, {}, {
    			readRecords : function(o){
    				return o.query.results.a;
    			}
    		}, this.addNodes, this, {node:node, cb:cb});
    	},
    	addNodes : function(o, arg){
    		var node = arg.node;
    		for(var i = 0, len = o.length; i < len; i++){
    			var foo = {};
    			foo.text = o&#91;i&#93;.span.content,
    			foo.url =  o&#91;i&#93;.href.replace("/programmes/", '');
    			foo.leaf = true;
    			var n = this.createNode(foo);
    			if(n){
    				node.appendChild(n);
    			}
    		}
    		arg.cb(this, node);
    	}
    });
    &#91;/sourcecode&#93;
    <br />The key bits here are that before I request data for a node I change the YQL query to pick up the data for the year node that was clicked. Then once the results come back I pull out the results node from the JSON and in my addNodes function I set up leaf node.</li>
    	<li><strong>The onclick callback to reload the ListView with new tracklists</strong><br />When someone clicks one of these new tracklist nodes in the TreePanel we want to update the ListView. This was done by adding a listener to the TreePanel: 
    
    listeners: {
    	click: function(n) {
    		Ext.getCmp('listview').getStore().proxy.setApi(
                         Ext.data.Api.actions.read, 
    'http://query.yahooapis.com/v1/public/yql?'
    +'q=select%20*%20from%20html%20where'
    +'%20url%3D%22http%3A%2F%2Fww'
    +'w.bbc.co.uk%2Fprogrammes%2F' + n.attributes.url 
    +'%22%20and%0A%20%20%20%20%20%20'
    +'xpath%3D\'%2F%2Fdiv%5B%40class%3D'
    +'%22title%22%20or%20%40class%3D'
    +'%22title%20with-image%22%5D\'&format=json');
    		Ext.getCmp('listview').getStore().load();
    	}
    }
    

    We simply change the read action url in the ListView store’s proxy, then we reload the store! As simple as that.

To wrap it up I put it all in an Ext.Viewport. Feel free to view the source of the page and enjoy!
http://www.mattgoldspink.co.uk/bbcradiolastfm.html

Author: Matt Goldspink

I'm a web developer based in the UK. I'm currently UI Architect at Vlocity, Inc, developing UI's on the Salesforce platform using a mix of Web Components, Angular.js and Lightning.

One Commnet on “Playing with Ext.js, YQL and BBC radio1 Essential Mix

  1. Really nicely put together, was doing some research on ScriptTagProxy and it’s nice to see who you applied your personal hobbies to work technologies. Inspiring!
    Good deal!

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.