rowid,title,contents,year,author,author_slug,published,url,topic
64,Being Responsive to the Small Things,"It’s that time of the year again to trim the tree with decorations. Or maybe a DOM tree?
Any web page is made of HTML elements that lay themselves out in a tree structure. We start at the top and then have multiple branches with branches that branch out from there.
To decorate our tree, we use CSS to specify which branches should receive the tinsel we wish to adorn upon it. It’s all so lovely.
In years past, this was rather straightforward. But these days, our trees need to be versatile. They need to be responsive!
Responsive web design is pretty wonderful, isn’t it? Based on our viewport, we can decide how elements on the page should change their appearance to accommodate various constraints using media queries.
Clearleft have a delightfully clean and responsive site
Alas, it’s not all sunshine, lollipops, and rainbows.
With complex layouts, we may have design chunks — let’s call them components — that appear in different contexts. Each context may end up providing its own constraints on the design, both in its default state and in its possibly various responsive states.
Media queries, however, limit us to the context of the entire viewport, not individual containers on the page. For every container our component lives in, we need to specify how to rearrange things in that context. The more complex the system, the more contexts we need to write code for.
@media (min-width: 800px) {
.features > .component { }
.sidebar > .component {}
.grid > .component {}
}
Each new component and each new breakpoint just makes the entire system that much more difficult to maintain.
@media (min-width: 600px) {
.features > .component { }
.grid > .component {}
}
@media (min-width: 800px) {
.features > .component { }
.sidebar > .component {}
.grid > .component {}
}
@media (min-width: 1024px) {
.features > .component { }
}
Enter container queries
Container queries, also known as element queries, allow you to specify conditional CSS based on the width (or maybe height) of the container that an element lives in. In doing so, you no longer have to consider the entire page and the interplay of all the elements within.
With container queries, you’ll be able to consider the breakpoints of just the component you’re designing. As a result, you end up specifying less code and the components you develop have fewer dependencies on the things around them. (I guess that makes your components more independent.)
Awesome, right?
There’s only one catch.
Browsers can’t do container queries. There’s not even an official specification for them yet. The Responsive Issues (née Images) Community Group is looking into solving how such a thing would actually work.
See, container queries are tricky from an implementation perspective. The contents of a container can affect the size of the container. Because of this, you end up with troublesome circular references.
For example, if the width of the container is under 500px then the width of the child element should be 600px, and if the width of the container is over 500px then the width of the child element should be 400px.
Can you see the dilemma? When the container is under 500px, the child element resizes to 600px and suddenly the container is 600px. If the container is 600px, then the child element is 400px! And so on, forever. This is bad.
I guess we should all just go home and sulk about how we just got a pile of socks when we really wanted the Millennium Falcon.
Our saviour this Christmas: JavaScript
The three wise men — Tim Berners-Lee, Håkon Wium Lie, and Brendan Eich — brought us the gifts of HTML, CSS, and JavaScript.
To date, there are a handful of open source solutions to fill the gap until a browser implementation sees the light of day.
Elementary by Scott Jehl
ElementQuery by Tyson Matanich
EQ.js by Sam Richards
CSS Element Queries from Marcj
Using any of these can sometimes feel like your toy broke within ten minutes of unwrapping it.
Each take their own approach on how to specify the query conditions. For example, Elementary, the smallest of the group, only supports min-width declarations made in a :before selector.
.mod-foo:before {
content: “300 410 500”;
}
The script loops through all the elements that you specify, reading the content property and then setting an attribute value on the HTML element, allowing you to use CSS to style that condition.
.mod-foo[data-minwidth~=""300""] {
background: blue;
}
To get the script to run, you’ll need to set up event handlers for when the page loads and for when it resizes.
window.addEventListener( ""load"", window.elementary, false );
window.addEventListener( ""resize"", window.elementary, false );
This works okay for static sites but breaks down on pages where elements can expand or contract, or where new content is dynamically inserted.
In the case of EQ.js, the implementation requires the creation of the breakpoints in the HTML. That means that you have implementation details in HTML, JavaScript, and CSS. (Although, with the JavaScript, once it’s in the build system, it shouldn’t ever be much of a concern unless you’re tracking down a bug.)
Another problem you may run into is the use of content delivery networks (CDNs) or cross-origin security issues. The ElementQuery and CSS Element Queries libraries need to be able to read the CSS file. If you are unable to set up proper cross-origin resource sharing (CORS) headers, these libraries won’t help.
At Shopify, for example, we had all of these problems. The admin that store owners use is very dynamic and the CSS and JavaScript were being loaded from a CDN that prevented the JavaScript from reading the CSS.
To go responsive, the team built their own solution — one similar to the other scripts above, in that it loops through elements and adds or removes classes (instead of data attributes) based on minimum or maximum width.
The caveat to this particular approach is that the declaration of breakpoints had to be done in JavaScript.
elements = [
{ ‘module’: “.carousel”, “className”:’alpha’, minWidth: 768, maxWidth: 1024 },
{ ‘module’: “.button”, “className”:’beta’, minWidth: 768, maxWidth: 1024 } ,
{ ‘module’: “.grid”, “className”:’cappa’, minWidth: 768, maxWidth: 1024 }
]
With that done, the script then had to be set to run during various events such as inserting new content via Ajax calls. This sometimes reveals itself in flashes of unstyled breakpoints (FOUB). An unfortunate side effect but one largely imperceptible.
Using this approach, however, allowed the Shopify team to make the admin responsive really quickly. Each member of the team was able to tackle the responsive story for a particular component without much concern for how all the other components would react.
Each element responds to its own breakpoint that would amount to dozens of breakpoints using traditional breakpoints. This approach allows for a truly fluid and adaptive interface for all screens.
Christmas is over
I wish I were the bearer of greater tidings and cheer. It’s not all bad, though. We may one day see browsers implement container queries natively. At which point, we shall all rejoice!",2015,Jonathan Snook,jonathansnook,2015-12-19T00:00:00+00:00,https://24ways.org/2015/being-responsive-to-the-small-things/,code
147,Christmas Is In The AIR,"That’s right, Christmas is coming up fast and there’s plenty of things to do. Get the tree and lights up, get the turkey, buy presents and who know what else. And what about Santa? He’s got a list. I’m pretty sure he’s checking it twice.
Sure, we could use an existing list making web site or even a desktop widget. But we’re geeks! What’s the fun in that? Let’s build our own to-do list application and do it with Adobe AIR!
What’s Adobe AIR?
Adobe AIR, formerly codenamed Apollo, is a runtime environment that runs on both Windows and OSX (with Linux support to follow). This runtime environment lets you build desktop applications using Adobe technologies like Flash and Flex. Oh, and HTML. That’s right, you web standards lovin’ maniac. You can build desktop applications that can run cross-platform using the trio of technologies, HTML, CSS and JavaScript.
If you’ve tried developing with AIR before, you’ll need to get re-familiarized with the latest beta release as many things have changed since the last one (such as the API and restrictions within the sandbox.)
To get started
To get started in building an AIR application, you’ll need two basic things:
The AIR runtime. The runtime is needed to run any AIR-based application.
The SDK. The software development kit gives you all the pieces to test your application. Unzip the SDK into any folder you wish.
You’ll also want to get your hands on the JavaScript API documentation which you’ll no doubt find yourself getting into before too long. (You can download it, too.)
Also of interest, some development environments have support for AIR built right in. Aptana doesn’t have support for beta 3 yet but I suspect it’ll be available shortly.
Within the SDK, there are two main tools that we’ll use: one to test the application (ADL) and another to build a distributable package of our application (ADT). I’ll get into this some more when we get to that stage of development.
Building our To-do list application
The first step to building an application within AIR is to create an XML file that defines our default application settings. I call mine application.xml, mostly because Aptana does that by default when creating a new AIR project. It makes sense though and I’ve stuck with it. Included in the templates folder of the SDK is an example XML file that you can use.
The first key part to this after specifying things like the application ID, version, and filename, is to specify what the default content should be within the content tags. Enter in the name of the HTML file you wish to load. Within this HTML file will be our application.
ui.html
Create a new HTML document and name it ui.html and place it in the same directory as the application.xml file. The first thing you’ll want to do is copy over the AIRAliases.js file from the frameworks folder of the SDK and add a link to it within your HTML document.
The aliases create shorthand links to all of the Flash-based APIs.
Now is probably a good time to explain how to debug your application.
Debugging our application
So, with our XML file created and HTML file started, let’s try testing our ‘application’. We’ll need the ADL application located in BIN folder of the SDK and tell it to run the application.xml file.
/path/to/adl /path/to/application.xml
You can also just drag the XML file onto ADL and it’ll accomplish the same thing. If you just did that and noticed that your blank application didn’t load, you’d be correct. It’s running but isn’t visible. Which at this point means you’ll have to shut down the ADL process. Sorry about that!
Changing the visibility
You have two ways to make your application visible. You can do it automatically by setting the placing true in the visible tag within the application.xml file.
true
The other way is to do it programmatically from within your application. You’d want to do it this way if you had other startup tasks to perform before showing the interface. To turn the UI on programmatically, simple set the visible property of nativeWindow to true.
Sandbox Security
Now that we have an application that we can see when we start it, it’s time to build the to-do list application. In doing so, you’d probably think that using a JavaScript library is a really good idea — and it can be but there are some limitations within AIR that have to be considered.
An HTML document, by default, runs within the application sandbox. You have full access to the AIR APIs but once the onload event of the window has fired, you’ll have a limited ability to make use of eval and other dynamic script injection approaches. This limits the ability of external sources from gaining access to everything the AIR API offers, such as database and local file system access. You’ll still be able to make use of eval for evaluating JSON responses, which is probably the most important if you wish to consume JSON-based services.
If you wish to create a greater wall of security between AIR and your HTML document loading in external resources, you can create a child sandbox. We won’t need to worry about it for our application so I won’t go any further into it but definitely keep this in mind.
Finally, our application
Getting tired of all this preamble? Let’s actually build our to-do list application. I’ll use jQuery because it’s small and should suit our needs nicely. Let’s begin with some structure:
Now we need to wire up that button to actually add a new item to our to-do list.
And just like that, we’ve got a to-do list! That’s it! Just never close your application and you’ll remember everything. Okay, that’s not very practical. You need to have some way of storing your to-do items until the next time you open up the application.
Storing Data
You’ve essentially got 4 different ways that you can store data:
Using the local database. AIR comes with SQLLite built in. That means you can create tables and insert, update and select data from that database just like on a web server.
Using the file system. You can also create files on the local machine. You have access to a few folders on the local system such as the documents folder and the desktop.
Using EcryptedLocalStore. I like using the EcryptedLocalStore because it allows you to easily save key/value pairs and have that information encrypted. All this within just a couple lines of code.
Sending the data to a remote API. Our to-do list could sync up with Remember the Milk, for example.
To demonstrate some persistence, we’ll use the file system to store our files. In addition, we’ll let the user specify where the file should be saved. This way, we can create multiple to-do lists, keeping them separate and organized.
The application is now broken down into 4 basic tasks:
Load data from the file system.
Perform any interface bindings.
Manage creating and deleting items from the list.
Save any changes to the list back to the file system.
Loading in data from the file system
When the application starts up, we’ll prompt the user to select a file or specify a new to-do list. Within AIR, there are 3 main file objects: File, FileMode, and FileStream. File handles file and path names, FileMode is used as a parameter for the FileStream to specify whether the file should be read-only or for write access. The FileStream object handles all the read/write activity.
The File object has a number of shortcuts to default paths like the documents folder, the desktop, or even the application store. In this case, we’ll specify the documents folder as the default location and then use the browseForSave method to prompt the user to specify a new or existing file. If the user specifies an existing file, they’ll be asked whether they want to overwrite it.
var store = air.File.documentsDirectory;
var fileStream = new air.FileStream();
store.browseForSave(""Choose To-do List"");
Then we add an event listener for when the user has selected a file. When the file is selected, we check to see if the file exists and if it does, read in the contents, splitting the file on new lines and creating our list items within the interface.
store.addEventListener(air.Event.SELECT, fileSelected);
function fileSelected()
{
air.trace(store.nativePath);
// load in any stored data
var byteData = new air.ByteArray();
if(store.exists)
{
fileStream.open(store, air.FileMode.READ);
fileStream.readBytes(byteData, 0, store.size);
fileStream.close();
if(byteData.length > 0)
{
var s = byteData.readUTFBytes(byteData.length);
oldlist = s.split(“\r\n”);
// create todolist items
for(var i=0; i < oldlist.length; i++)
{
createItem(oldlist[i], (new Date()).getTime() + i );
}
}
}
}
Perform Interface Bindings
This is similar to before where we set the click event on the Add button but we’ve moved the code to save the list into a separate function.
$('#add').click(function(){
var t = $('#text').val();
if(t){
// create an ID using the time
createItem(t, (new Date()).getTime() );
}
})
Manage creating and deleting items from the list
The list management is now in its own function, similar to before but with some extra information to identify list items and with calls to save our list after each change.
function createItem(t, id)
{
if(t.length == 0) return;
// add it to the todo list
todolist[id] = t;
// use DOM methods to create the new list item
var li = document.createElement('li');
// the extra space at the end creates a buffer between the text
// and the delete link we're about to add
li.appendChild(document.createTextNode(t + ' '));
// create the delete link
var del = document.createElement('a');
// this makes it a true link. I feel dirty doing this.
del.setAttribute('href', '#');
del.addEventListener('click', function(evt){
var id = this.id.substr(1);
delete todolist[id]; // remove the item from the list
this.parentNode.parentNode.removeChild(this.parentNode);
saveList();
});
del.appendChild(document.createTextNode('[del]'));
del.id = 'd' + id;
li.appendChild(del);
// append everything to the list
$('#list').append(li);
//reset the text box
$('#text').val('');
saveList();
}
Save changes to the file system
Any time a change is made to the list, we update the file. The file will always reflect the current state of the list and we’ll never have to click a save button. It just iterates through the list, adding a new line to each one.
function saveList(){
if(store.isDirectory) return;
var packet = '';
for(var i in todolist)
{
packet += todolist[i] + '\r\n';
}
var bytes = new air.ByteArray();
bytes.writeUTFBytes(packet);
fileStream.open(store, air.FileMode.WRITE);
fileStream.writeBytes(bytes, 0, bytes.length);
fileStream.close();
}
One important thing to mention here is that we check if the store is a directory first. The reason we do this goes back to our browseForSave call. If the user cancels the dialog without selecting a file first, then the store points to the documentsDirectory that we set it to initially. Since we haven’t specified a file, there’s no place to save the list.
Hopefully by this point, you’ve been thinking of some cool ways to pimp out your list. Now we need to package this up so that we can let other people use it, too.
Creating a Package
Now that we’ve created our application, we need to package it up so that we can distribute it. This is a two step process. The first step is to create a code signing certificate (or you can pay for one from Thawte which will help authenticate you as an AIR application developer).
To create a self-signed certificate, run the following command. This will create a PFX file that you’ll use to sign your application.
adt -certificate -cn todo24ways 1024-RSA todo24ways.pfx mypassword
After you’ve done that, you’ll need to create the package with the certificate
adt -package -storetype pkcs12 -keystore todo24ways.pfx todo24ways.air application.xml .
The important part to mention here is the period at the end of the command. We’re telling it to package up all files in the current directory.
After that, just run the AIR file, which will install your application and run it.
Important things to remember about AIR
When developing an HTML application, the rendering engine is Webkit. You’ll thank your lucky stars that you aren’t struggling with cross-browser issues. (My personal favourites are multiple backgrounds and border radius!)
Be mindful of memory leaks. Things like Ajax calls and event binding can cause applications to slowly leak memory over time. Web pages are normally short lived but desktop applications are often open for hours, if not days, and you may find your little desktop application taking up more memory than anything else on your machine!
The WebKit runtime itself can also be a memory hog, usually taking about 15MB just for itself. If you create multiple HTML windows, it’ll add another 15MB to your memory footprint. Our little to-do list application shouldn’t be much of a concern, though.
The other important thing to remember is that you’re still essentially running within a Flash environment. While you probably won’t notice this working in small applications, the moment you need to move to multiple windows or need to accomplish stuff beyond what HTML and JavaScript can give you, the need to understand some of the Flash-based elements will become more important.
Lastly, the other thing to remember is that HTML links will load within the AIR application. If you want a link to open in the users web browser, you’ll need to capture that event and handle it on your own. The following code takes the HREF from a clicked link and opens it in the default web browser.
air.navigateToURL(new air.URLRequest(this.href));
Only the beginning
Of course, this is only the beginning of what you can do with Adobe AIR. You don’t have the same level of control as building a native desktop application, such as being able to launch other applications, but you do have more control than what you could have within a web application. Check out the Adobe AIR Developer Center for HTML and Ajax for tutorials and other resources.
Now, go forth and create your desktop applications and hopefully you finish all your shopping before Christmas!
Download the example files.",2007,Jonathan Snook,jonathansnook,2007-12-19T00:00:00+00:00,https://24ways.org/2007/christmas-is-in-the-air/,code
184,Spruce It Up,"The landscape of web typography is changing quickly these days. We’ve gone from the wild west days of sIFR to Cufón to finally seeing font embedding seeing wide spread adoption by browser developers (and soon web designers) with @font-face. For those who’ve felt limited by the typographic possibilities before, this has been a good year.
As Mark Boulton has so eloquently elucidated, @font-face embedding doesn’t come without its drawbacks. Font files can be quite large and FOUT—that nasty flash of unstyled text—can be a distraction for users.
Data URIs
We can battle FOUT by using Data URIs. A Data URI allows the font to be encoded right into the CSS file. When the font comes with the CSS, the flash of unstyled text is mitigated. No extra HTTP requests are required.
Don’t be a grinch, though. Sending hundreds of kilobytes down the pipe still isn’t great. Sometimes, all we want to do is spruce up our site with a little typographic sugar.
Be Selective
Dan Cederholm’s SimpleBits is an attractive site.
Take a look at the ampersand within the header of his site. It’s the lovely (and free) Goudy Bookletter 1911 available from The League of Movable Type. The Opentype format is a respectable 28KB. Nothing too crazy but hold on here. Mr. Cederholm is only using the ampersand! Ouch. That’s a lot of bandwidth just for one character.
Can we optimize a font like we can an image? Yes. Image optimization essentially works by removing unnecessary image data such as colour data, hidden comments or using compression algorithms. How do you remove unnecessary information from a font? Subsetting.
If you’re the adventurous type, grab a copy of FontForge, which is an open source font editing tool. You can open the font, view and edit any of the glyphs and then re-generate the font. The interface is a little clunky but you’ll be able to select any character you don’t want and then cut the glyphs. Re-generate your font and you’ve now got a smaller file.
There are certainly more optimizations that can also be made such as removing hinting and kerning information. Keep in mind that removing this information may affect how well the type renders.
At this time of year, though, I’m sure you’re quite busy. Save yourself some time and head on over to the Font Squirrel Font Generator.
The Font Generator is extremely handy and allows for a number of optimizations and cross-platform options to be generated instantly. Select the font from your local system—make sure that you are only using properly licensed fonts!
In this particular case, we only want the ampersand. Click on Subset Fonts which will open up a new menu. Unselect any preselected sets and enter the ampersand into the Single Characters text box.
Generate your font and what are you left with? 3KB.
The Font Generator even generates a base64 encoded data URI stylesheet to be imported easily into your project.
Check out the Demo page. (This demo won’t work in Internet Explorer as we’re only demonstrating the Data URI font embedding and not using the EOT file format that IE requires.)
No Unnecessary Additives
If you peeked under the hood of that demo, did you notice something interesting? There’s no around the ampersand. The great thing about this is that we can take advantage of the font stack’s natural ability to switch to a fallback font when a character isn’t available.
Just like that, we’ve managed to spruce up our page with a little typographic sugar without having to put on too much weight.",2009,Jonathan Snook,jonathansnook,2009-12-19T00:00:00+00:00,https://24ways.org/2009/spruce-it-up/,code
321,Tables with Style,"It might not seem like it but styling tabular data can be a lot of fun. From a semantic point of view, there are plenty of elements to tie some style into. You have cells, rows, row groups and, of course, the table element itself. Adding CSS to a paragraph just isn’t as exciting.
Where do I start?
First, if you have some tabular data (you know, like a spreadsheet with rows and columns) that you’d like to spiffy up, pop it into a table — it’s rightful place!
To add more semantics to your table — and coincidentally to add more hooks for CSS — break up your table into row groups. There are three types of row groups: the header (thead), the body (tbody) and the footer (tfoot). You can only have one header and one footer but you can have as many table bodies as is appropriate.
Sample table example
Inspiration
Table Striping
To improve scanning information within a table, a common technique is to style alternating rows. Also known as zebra tables. Whether you apply it using a class on every other row or turn to JavaScript to accomplish the task, a handy-dandy trick is to use a semi-transparent PNG as your background image. This is especially useful over patterned backgrounds.
tbody tr.odd td {
background:transparent url(background.png) repeat top left;
}
* html tbody tr.odd td {
background:#C00;
filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(
src='background.png', sizingMethod='scale');
}
We turn off the default background and apply our PNG hack to have this work in Internet Explorer.
Styling Columns
Did you know you could style a column? That’s right. You can add special column (col) or column group (colgroup) elements. With that you can add border or background styles to the column.
...
Check out the example.
Fun with Backgrounds
Pop in a tiled background to give your table some character! Internet Explorer’s PNG hack unfortunately only works well when applied to a cell.
To figure out which background will appear over another, just remember the hierarchy:
(bottom) Table → Column → Row Group → Row → Cell (top)
The Future is Bright
Once browser-makers start implementing CSS3, we’ll have more power at our disposal. Just with :first-child and :last-child, you can pull off a scalable version of our previous table with rounded corners and all — unfortunately, only Firefox manages to pull this one off successfully. And the selector the masses are clamouring for, nth-child, will make zebra tables easy as eggnog.",2005,Jonathan Snook,jonathansnook,2005-12-19T00:00:00+00:00,https://24ways.org/2005/tables-with-style/,code