Building a data-driven image carousel with Sencha Touch 2
February 11, 2012 by Ed Spencer · 5 Comments
This evening I embarked on a little stellar voyage that I’d like to share with you all. Most people with great taste love astronomy and Sencha Touch 2, so why not combine them in a fun evening’s web app building?
NASA has been running a small site called APOD (Astronomy Picture Of the Day) for a long time now, as you can probably tell by the awesome web design of that page. Despite its 1998-era styling, this site incorporates some pretty stunning images of the universe and is begging for a mobile app interpretation.
We’re not going to go crazy, in fact this whole thing only took about an hour to create, but hopefully it’s a useful look at how to put something like this together. In this case, we’re just going to write a quick app that pulls down the last 20 pictures and shows them in a carousel with an optional title.
Here’s what it looks like live. You’ll need a webkit browser (Chrome or Safari) to see this, alternatively load up http://code.edspencer.net/apod on a phone or tablet device:
The full source code for the app is up on github, and we’ll go through it bit by bit below.
The App
Our app consists of 5 files:
index.html, which includes our JavaScript files and a little CSS
app.js, which boots our application up
app/model/Picture.js, which represents a single APOD picture
app/view/Picture.js, which shows a picture on the page
app/store/Pictures.js, which fetches the pictures from the APOD RSS feed
The whole thing is up on github and you can see a live demo at http://code.edspencer.net/apod. To see what it’s doing tap that link on your phone or tablet, and to really feel it add it to your homescreen to get rid of that browser chrome.
The Code
Most of the action happens in app.js, which for your enjoyment is more documentation than code. Here’s the gist of it:
/* * This app uses a Carousel and a JSON-P proxy so make sure they're loaded first */Ext.require([ 'Ext.carousel.Carousel', 'Ext.data.proxy.JsonP']);
/** * Our app is pretty simple - it just grabs the latest images from NASA's Astronomy Picture Of the Day * (http://apod.nasa.gov/apod/astropix.html) and displays them in a Carousel. This file drives most of * the application, but there's also: * * * A Store - app/store/Pictures.js - that fetches the data from the APOD RSS feed * * A Model - app/model/Picture.js - that represents a single image from the feed * * A View - app/view/Picture.js - that displays each image * * Our application's launch function is called automatically when everything is loaded. */Ext.application({ name: 'apod', models: ['Picture'], stores: ['Pictures'], views: ['Picture'], launch: function() { var titleVisible = false, info, carousel; /** * The main carousel that drives our app. We're just telling it to use the Pictures store and * to update the info bar whenever a new image is swiped to */ carousel = Ext.create('Ext.Carousel', { store: 'Pictures', direction: 'horizontal', listeners: { activeitemchange: function(carousel, item) { info.setHtml(item.getPicture().get('title')); } } }); /** * This is just a reusable Component that we pin to the top of the page. This is hidden by default * and appears when the user taps on the screen. The activeitemchange listener above updates the * content of this Component whenever a new image is swiped to */ info = Ext.create('Ext.Component', { cls: 'apod-title', top: 0, left: 0, right: 0 }); //add both of our views to the Viewport so they're rendered and visible Ext.Viewport.add(carousel); Ext.Viewport.add(info); /** * The Pictures store (see app/store/Pictures.js) is set to not load automatically, so we load it * manually now. This loads data from the APOD RSS feed and calls our callback function once it's * loaded. * * All we do here is iterate over all of the data, creating an apodimage Component for each item. * Then we just add those items to the Carousel and set the first item active. */ Ext.getStore('Pictures').load(function(pictures) { var items = []; Ext.each(pictures, function(picture) { if (!picture.get('image')) { return; } items.push({ xtype: 'apodimage', picture: picture }); }); carousel.setItems(items); carousel.setActiveItem(0); }); /** * The final thing is to add a tap listener that is called whenever the user taps on the screen. * We do a quick check to make sure they're not tapping on the carousel indicators (tapping on * those indicators moves you between items so we don't want to override that), then either hide * or show the info Component. * * Note that to hide or show this Component we're adding or removing the apod-title-visible class. * If you look at index.html you'll see the CSS rules style the info bar and also cause it to fade * in and out when you tap. */ Ext.Viewport.element.on('tap', function(e) { if (!e.getTarget('.x-carousel-indicator')) { if (titleVisible) { info.element.removeCls('apod-title-visible'); titleVisible = false; } else { info.element.addCls('apod-title-visible'); titleVisible = true; } } }); }});This is pretty simple stuff and you can probably just follow the comments to see what’s going on. Basically though the app.js is responsible for launching our application, creating the Carousel and info Components, and setting up a couple of convenient event listeners.
We also had a few other files:
Picture Model
Found in app/model/Picture.js, our model is mostly just a list of fields sent back in the RSS feed. There is one that’s somewhat more complicated than the rest though – the ‘image’ field. Ideally, the RSS feed would have sent back the url of the image in a separate field and we could just pull it out like any other, but alas it is embedded inside the main content.
To get around this, we just specify a convert function that grabs the content field, finds the first image url inside of it and pulls it out. To make sure it looks good on any device we also pass it through Sencha IO src, which resizes the image to fit the screen size of whatever device we happen to be viewing it on:
/** * Simple Model that represents an image from NASA's Astronomy Picture Of the Day. The only remarkable * thing about this model is the 'image' field, which uses a regular expression to pull its value out * of the main content of the RSS feed. Ideally the image url would have been presented in its own field * in the RSS response, but as it wasn't we had to use this approach to parse it out */Ext.define('apod.model.Picture', { extend: 'Ext.data.Model', config: { fields: [ 'id', 'title', 'link', 'author', 'content', { name: 'image', type: 'string', convert: function(value, record) { var content = record.get('content'), regex = /img src=\"([a-zA-Z0-9\_\.\/\:]*)\"/, match = content.match(regex), src = match[1];
if (src != "" && !src.match(/\.gif$/)) { src = "http://src.sencha.io/screen.width/" + src; } return src; } } ] }});Pictures Store
Our Store is even simpler than our Model. All it does is load the APOD RSS feed over JSON-P (via Google’s RSS Feed API) and decode the data with a very simple JSON Reader. This automatically pulls down the images and runs them through our Model’s convert function:
/** * Grabs the APOD RSS feed from Google's Feed API, passes the data to our Model to decode */Ext.define('apod.store.Pictures', { extend: 'Ext.data.Store', config: { model: 'apod.model.Picture', proxy: { type: 'jsonp', url: 'https://ajax.googleapis.com/ajax/services/feed/load?v=1.0&q=http://www.acme.com/jef/apod/rss.xml&num=20', reader: { type: 'json', rootProperty: 'responseData.feed.entries' } } }});Tying it all together
Our app.js loads our Model and Store, plus a really simple Picture view that is basically just an Ext.Img. All it does then is render the Carousel and Info Component to the screen and tie up a couple of listeners.
In case you weren’t paying attention before, the info component is just an Ext.Component that we rendered up in app.js as a place to render the title of the image you’re currently looking at. When you swipe between items in the carousel the activeitemchange event is fired, which we listen to near the top of app.js. All our activeitemchange listener does is update the HTML of the info component to the title of the image we just swiped to.
But what about the info component itself? Well at the bottom of app.js we added a tap listener on Ext.Viewport that hides or shows the info Component whenever you tap anywhere on the screen (except if you tap on the Carousel indicator icons). With a little CSS transition loveliness we get a nice fade in/out transition when we tap the screen to reveal the image title. Here’s that tap listener again:
/** * The final thing is to add a tap listener that is called whenever the user taps on the screen. * We do a quick check to make sure they're not tapping on the carousel indicators (tapping on * those indicators moves you between items so we don't want to override that), then either hide * or show the info Component. */Ext.Viewport.element.on('tap', function(e) { if (!e.getTarget('.x-carousel-indicator')) { if (titleVisible) { info.element.removeCls('apod-title-visible'); titleVisible = false; } else { info.element.addCls('apod-title-visible'); titleVisible = true; } }});The End of the Beginning
This was a really simple app that shows how easy it is to put these things together with Sencha Touch 2. Like with most stories though there’s more to come so keep an eye out for parts 2 and 3 of this intergalactic adventure.
The Class System in Sencha Touch 2 – What you need to know
January 28, 2012 by Ed Spencer · 2 Comments
Sencha Touch 1 used the class system from Ext JS 3, which provides a simple but powerful inheritance system that makes it easier to write big complex things like applications and frameworks.
With Sencha Touch 2 we’ve taken Ext JS 4’s much more advanced class system and used it to create a leaner, cleaner and more beautiful framework. This post takes you through what has changed and how to use it to improve your apps.
Syntax
The first thing you’ll notice when comparing code from 1.x and 2.x is that the class syntax is different. Back in 1.x we would define a class like this:
MyApp.CustomPanel = Ext.extend(Ext.Panel, {
html: 'Some html'
});
This would create a subclass of Ext.Panel called MyApp.CustomPanel, setting the html configuration to ‘Some html’. Any time we create a new instance of our subclass (by calling new MyApp.CustomPanel()), we’ll now get a slightly customized Ext.Panel instance.
Now let’s see how the same class is defined in Sencha Touch 2:
Ext.define('MyApp.CustomPanel', {
extend: 'Ext.Panel',
config: {
html: 'Some html'
}
});
There are a few changes here, let’s go through them one by one. Firstly and most obviously we’ve swapped out Ext.extend for Ext.define. Ext.define operates using strings – notice that both ‘MyApp.CustomPanel’ and ‘Ext.Panel’ are now wrapped in quotes. This enables one of the most powerful parts of the new class system – dynamic loading.
I actually talked about this in a post about Ext JS 4 last year so if you’re not familiar you should check out the post, but in a nutshell Sencha Touch 2 will automatically ensure that the class you’re extending (Ext.Panel) is loaded on the page, fetching it from your server if necessary. This makes development easier and enables you to create custom builds that only contain the class your app actually uses.
The second notable change is that we’re using a ‘config’ block now. Configs are a special thing in Sencha Touch 2 – they are properties of a class that can be retrieved and updated at any time, and provide extremely useful hook functions that enable you to run any custom logic you like whenever one of them is changed.
Whenever you want to customize any of the configurations of a subclass in Sencha Touch 2, just place them in the config block and the framework takes care of the rest, as we’ll see in a moment.
Consistency
The biggest improvement that comes from the config system is consistency. Let’s take our MyApp.CustomPanel class above and create an instance of it:
var myPanel = Ext.create('MyApp.CustomPanel');
Every configuration has an automatically generated getter and setter function, which we can use like this:
myPanel.setHtml('New HTML');
myPanel.getHtml(); //returns 'New HTML'
This might not seem much, but the convention applies to every single configuration in the entire framework. This eliminates the guesswork from the API – if you know the config name, you know how to get it and update it. Contrast this with Sencha Touch 1 where retrieving the html config meant finding some property on the instance, and updating it meant calling myPanel.update(’New HTML’), which is nowhere near as predictable.
Instantiating
You probably noticed that we used a new function above – Ext.create. This is very similar to just calling ‘new MyApp.CustomPanel()’, with the exception that Ext.create uses the dynamic loading system to automatically load the class you are trying to instantiate if it is not already on the page. This can make life much easier when developing your app as you don’t have to immediately manage dependencies – it just works.
In the example above we just instantiated a default MyApp.CustomPanel but of course we can customize it at instantiation time by passing configs into Ext.create:
var myPanel = Ext.create('MyApp.CustomPanel', {
html: 'Some Custom HTML'
});
We can still call getHtml() and setHtml() to retrieve and update our html config at any time.
Subclassing and Custom Configs
We created a simple subclass above that provided a new default value for Ext.Panel’s html config. However, we can also add our own configs to our subclasses:
Ext.define('MyApp.CustomPanel', {
extend: 'Ext.Panel',
config: {
html: 'Some html',
anotherConfig: 'default value'
}
});
The ‘anotherConfig’ configuration doesn’t exist on Ext.Panel so it’s defined for the first time on MyApp.CustomPanel. This automatically creates our getter and setter functions for us:
var myPanel = Ext.create('MyApp.CustomPanel');
myPanel.setAnotherConfig('Something else');
myPanel.getAnotherConfig(); //now returns 'Something else'
Notice how the getter and setter names were automatically capitalized to use camelCase like all of the other functions in the framework. This was done automatically, but Sencha Touch 2 does another couple of very nice things for you – it creates hook functions:
Ext.define('MyApp.CustomPanel', {
extend: 'Ext.Panel',
config: {
html: 'Some html',
anotherConfig: 'default value'
},
applyAnotherConfig: function(value) {
return "[TEST] " + value;
},
updateAnotherConfig: function(value, oldValue) {
this.setHtml("HTML is now " + value);
}
});
We’ve added two new functions to our class – applyAnotherConfig and updateAnotherConfig – these are both called when we call setAnotherConfig. The first one that is called is applyAnotherConfig. This is passed the value of the configuration (’default value’ by default in this case) and is given the opportunity to modify it. In this case we’re prepending “[TEST] ” to whatever anotherConfig is set to:
var myPanel = Ext.create('MyApp.CustomPanel');
myPanel.setAnotherConfig('Something else');
myPanel.getAnotherConfig(); //now returns '[TEST] Something else'
The second function, updateAnotherConfig, is called after applyAnotherConfig has had a chance to modify the value and is usually used to effect some other change – whether it’s updating the DOM, sending an AJAX request, or setting another config as we do here.
When we run the code above, as well as ‘[TEST] ‘ being prepended to our anotherConfig configuration, we’re calling this.setHtml to update the html configuration too. There’s no limit to what you can do inside these hook functions, just remember the rule – the apply functions are used to transform new values before they are saved, the update functions are used to perform the actual side-effects of changing the value (e.g. updating the DOM or configuring other classes).
How we use it
The example above is a little contrived to show the point – let’s look at a real example from Sencha Touch 2’s Ext.Panel class:
applyBodyPadding: function(bodyPadding) {
if (bodyPadding === true) {
bodyPadding = 5;
}
bodyPadding = Ext.dom.Element.unitizeBox(bodyPadding);
return bodyPadding;
},
updateBodyPadding: function(newBodyPadding) {
this.element.setStyle('padding', newBodyPadding);
}
Here we see the apply and update functions for the bodyPadding config. Notice that in the applyBodyPadding function we set a default and use the framework’s unitizeBox function to parse CSS padding strings (like ‘5px 5px 10px 15px’) into top, left, bottom and right paddings, which we then return as the transformed value.
The updateBodyPadding then takes this modified value and performs the actual updates – in this case setting the padding style on the Panel’s element based on the new configuration. You can see similar usage in almost any component class in the framework.
Find out more
This is just a look through the most important aspects of the new class system and how they impact you when writing apps in Sencha Touch 2. To find out more about the class system we recommend taking a look at the Class System guide and if you have any questions the forums are a great place to start.
Jaml updates
January 29, 2010 by Ed Spencer · Leave a Comment
Jaml seems to have been getting a lot of interest lately. Here are a few quick updates on what’s been going on:
- Tom Robinson added support for CommonJS
- Eneko Alonso ported the project to MooTools, creating mooml
- Carl Furrow wrote up a nice comparison on Jaml and EJS
- Jaml is now a rendering option in JavaScriptMVC, along with John Resig’s microtemplates
- Andrew Dupont committed a series of patches such as improving Jaml’s efficiency and optionally removing the ‘with’ and ‘eval’ magic
In addition Jaml was recently picked up by Ajaxian, and a couple of people have written up blog posts about Jaml in languages other than English, which is great to see.
Jaml is up on Github and has a number of forks already. If you like the library and have something to add, fork away and send me a pull request!
If you’ve never seen Jaml before or have forgotten what it does, it turns this:
div(
h1("Some title"),
p("Some exciting paragraph text"),
br(),
ul(
li("First item"),
li("Second item"),
li("Third item")
)
);
Into this:
<div>
<h1>Some title</h1>
<p>Some exciting paragraph text</p>
<br />
<ul>
<li>First item</li>
<li>Second item</li>
<li>Third item</li>
</ul>
</div>
OSX Screensaver emulation with Canvas: That’s Bean
December 6, 2009 by Ed Spencer · 7 Comments
OS X has a pretty little screensaver which takes a bunch of images and ‘drops’ them, spinning, onto the screen. Think of it like scattering photographs onto a table, one at a time.
Naturally, there’s a desperate need for a JavaScript/Canvas port of this functionality, resulting in the following:
I had to limit the video capture framerate a bit so the video makes it look less smooth than it actually is. Check it out running in your own browser here.
For obvious reasons I have called the code behind this Bean, and it’s all available up on Github.
For the curious, here’s a little explanation about how it works. Bean starts off with a blank canvas and a list of image urls, which it preloads before getting started. It then drops one image at a time, rotating it as it goes. Each falling image is called a Plunger, because it plunges.
Each Plunger gets a random position and rotation to end up in, and takes care of drawing itself to the canvas on each frame by calculating its current size and rotation as it falls away from you.
Drawing each Plunger image on every frame quickly starts to kill the CPU, so we take a frame snapshot every time a Plunger has finished its descent. This just entails drawing the completed Plunges first and then using Canvas’ getImageData API to grab the pixel data for the image.
This gives us a snapshot of all of the fallen Plungers, meaning we can just draw a single background image and the currently falling Plunger on each frame. This approach ensures the performance remains constant, as we are only ever drawing a maximum of 2 images per frame. Each time a Plunger finishes its descent a new snapshot is taken.
Bean attempts to draw a new frame roughly 25 times per second and modern browsers seem to handle this pretty well. Safari pulls around 60% of one core on my MacBook Pro, with Firefox somewhat less performant. Needless to say, I didn’t even bother trying to make this work with IE.
Here’s the code to set the Bean in motion. This is using a few bundled APOD images:
var bean = new Bean({
imageUrls: [
'images/DoubleCluster_cs_fleming.jpg',
'images/NGC660Hagar0_c900.jpg',
'images/filaments_iac.jpg',
'images/m78wide_tvdavis900.jpg',
'images/sunearthpanel_sts129.jpg',
'images/NGC253_SSRO_900.jpg',
'images/Ophcloud_spitzer_c800.jpg'
],
canvasId : 'main',
fillBody : true
});
bean.onReady(function(bean) {
bean.start();
});
Jaml: beautiful HTML generation for JavaScript
November 4, 2009 by Ed Spencer · 33 Comments
Generating HTML with JavaScript has always been ugly. Hella ugly. It usually involves writing streams of hard-to-maintain code which just concatenates a bunch of strings together and spits them out in an ugly mess.
Wouldn’t it be awesome if we could do something pretty like this:
div(
h1("Some title"),
p("Some exciting paragraph text"),
br(),
ul(
li("First item"),
li("Second item"),
li("Third item")
)
);
And have it output something beautiful like this:
<div>
<h1>Some title</h1>
<p>Some exciting paragraph text</p>
<br />
<ul>
<li>First item</li>
<li>Second item</li>
<li>Third item</li>
</ul>
</div>
With Jaml, we can do exactly that. Jaml is a simple library inspired by the excellent Haml library for Ruby. It works by first defining a template using an intuitive set of tag functions, and then rendering it to appear as pretty HTML. Here’s an example of how we’d do that with the template above:
Jaml.register('simple', function() {
div(
h1("Some title"),
p("Some exciting paragraph text"),
br(),
ul(
li("First item"),
li("Second item"),
li("Third item")
)
);
});
Jaml.render('simple');
All we need to do is call Jaml.register with a template name and the template source. Jaml then stores this for later use, allowing us to render it later using Jaml.render(). Rendering with Jaml gives us the nicely formatted, indented HTML displayed above.
So we’ve got a nice way of specifying reusable templates and then rendering them prettily, but we can do more. Usually we want to inject some data into our template before rendering it – like this:
Jaml.register('product', function(product) {
div({cls: 'product'},
h1(product.title),
p(product.description),
img({src: product.thumbUrl}),
a({href: product.imageUrl}, 'View larger image'),
form(
label({'for': 'quantity'}, "Quantity"),
input({type: 'text', name: 'quantity', id: 'quantity', value: 1}),
input({type: 'submit', value: 'Add to Cart'})
)
);
});
In this example our template takes an argument, which we’ve called product. We could have called this anything, but in this case the template is for a product in an ecommerce store so product makes sense. Inside our template we have access to the product variable, and can output data from it.
Let’s render it with a Product from our database:
//this is the product we will be rendering
var bsg = {
title : 'Battlestar Galactica DVDs',
thumbUrl : 'thumbnail.png',
imageUrl : 'image.png',
description: 'Best. Show. Evar.'
};
Jaml.render('product', bsg);
The output from rendering this template with the product looks like this:
<div class="product">
<h1>Battlestar Galactica DVDs</h1>
<p>Best. Show. Evar.</p>
<img src="thumbnail.png" />
<a href="image.png">View larger image</a>
<form>
<label for="quantity">Quantity</label>
<input type="text" name="quantity" id="quantity" value="1"></input>
<input type="submit" value="Add to Cart"></input>
</form>
</div>
Cool – we’ve got an object oriented declaration of an HTML template which is cleanly separated from our data. How about we define another template, this time for a category which will contain our products:
Jaml.register('category', function(category) {
div({cls: 'category'},
h1(category.name),
p(category.products.length + " products in this category:"),
div({cls: 'products'},
Jaml.render('product', category.products)
)
);
});
Our category template references our product template, achieving something rather like a partial in Ruby on Rails. This obviously allows us to keep our templates DRY and to easily render a hypothetical Category page like this:
//here's a second product
var snowWhite = {
title : 'Snow White',
description: 'not so great actually',
thumbUrl : 'thumbnail.png',
imageUrl : 'image.png'
};
//and a category
var category = {
name : 'Doovde',
products: [bsg, snowWhite]
}
Jaml.render('category', category);
All we’ve done is render the ‘category’ template with our ‘Doovde’ category, which contains an array of products. These were passed into the ‘product’ template to produce the following output:
<div class="category">
<h1>Doovde</h1>
<p>2 products in this category:</p>
<div class="products"><div class="product">
<h1>Battlestar Galactica DVDs</h1>
<p>Best. Show. Evar.</p>
<img src="thumbnail.png" />
<a href="image.png">View larger image</a>
<form>
<label for="quantity">Quantity</label>
<input type="text" name="quantity" id="quantity" value="1"></input>
<input type="submit" value="Add to Cart"></input>
</form>
</div>
<div class="product">
<h1>Snow White</h1>
<p>not so great actually</p>
<img src="thumbnail.png" />
<a href="image.png">View larger image</a>
<form>
<label for="quantity">Quantity</label>
<input type="text" name="quantity" id="quantity" value="1"></input>
<input type="submit" value="Add to Cart"></input>
</form>
</div>
</div>
</div>
You can see live examples of all of the above at http://edspencer.github.com/jaml.
Jaml currently sports a few hacks and is not particularly efficient. It is presented as a proof of concept, though all the output above is true output from the library. As always, all of the code is up on Github, and contributions are welcome
Jaml would be suitable for emulating a Rails-style directory structure inside a server side JavaScript framework – each Jaml template could occupy its own file, with the template name coming from the file name. This is roughly how Rails and other MVC frameworks work currently, and it eliminates the need for the Jaml.register lines. Alternatively, the templates could still be stored server side and simply pulled down and evaluated for client side rendering.
Happy rendering!