Showing posts with label JavaScript. Show all posts
Showing posts with label JavaScript. Show all posts

Wednesday, 11 April 2012

Colourful times: Introducing the CSS3 gradient image generator


I've been working on a project with quite a few CSS3 gradients recently. As usual I want the site to degrade nicely back down my browser matrix with contrast ratios holding up and the general look and feel being as close as possible. (Which doesn't have to be the same.)

Establishing a workflow

I'm using only linear gradients so the requirement is for an approach that can draw the gradient either vertically or horizontally.

I settled on a workflow shown in this article whereby a background image that repeats along one of the image axis is served to browsers that don't support gradients.

So a button's default background would be defined in CSS with a gradient, i.e.

linear-gradient(#490091, #8000FF 20%, #8000FF 50%, #B469FF 50%, #B469FF 80%, #7140A3)

The CSS above gives the button this background:

Button

The background would also be defined as an image that repeats along the x-axis, i.e.

background:#490091 url(/img/purple_repeater_1.png) 0 100% repeat-x;

The CSS above gives the button this background:

Button
Cross-browser differences

The most obvious difference is that a gradient is mathematically generated and so can scale to whatever dimension needed, whereas the raster image used in the second example is fixed.

Any changes to the element size (i.e. through text resizing) will mean the image no longer fills the space but adding a background colour matching one of the gradient stops can help this. It's not quite as pretty but it keeps the colour ratio and preserves some of the look and feel:

Summary

That leaves quite a lot of tasks to perform just to get a result:

  1. Make the linear gradient in CSS.
  2. Produce all the vendor variants (made a lot easier by Lea Verou's excellent cssgradientsplease tool).
  3. Take a screen-grab of it and cut a background image for older browsers.

The biggest task here seemed to be making the background image (screengrab the web page, open Photoshop, crop & save), so I made an online tool to provide a bit of automation: http://tombye.github.com/CSS3-Gradient-image-generator/.

Using the tool

The tool accepts the same syntax as Lea Verou's parser, which is the closest we'll get to a standard at the moment, so you don't have to change it when generating the image and the CSS variants.

It will always generate an image in the page HTML for you to grab but can also download it if needed. (Note: I can't figure out how to change the download's file name, let me know if you know how.)

There's a set of unit tests in the repo for checking the CSS parsing is correct. They cover all I can think needs testing but let me know if anything isn't working as expected so I can add tests for any missed requirements.

Wednesday, 21 December 2011

jQuery Group Animate

.animate

jQuery has a great method for dealing with animations called animate. While it works for most things I've been finding its requirement of an element selection to work on a bit restricting.

Animations these days are not always just about elements animating individually. Animations that use the canvas element for example tend to be more about changing values through time and using them to draw the scene onto the canvas each frame.

gibsy.com's parallax-fuelled listing page, from webdesignerwall.com's article on parallax scrolling effects

The most obvious use of this approach is the parallax effect seen on many sites recently. (While this is not technically animating through time it is the same principle of breaking a process down into 'frames'.)

Steph band's events.frame is a nice example of how to approach this (and served as my starting point for building a solution).

My requirements

So a more thorough view of what my requirements were is

  1. Animations don't have to just run on elements
  2. Animations are split down into 'frames', which is where the code will run
  3. Code inside these frames has access to information about the animation and its progress
  4. Code that runs in the frames can sit in any place in your codebase

We can solve two & three by using the .animate method's step callback, which runs whenever the timer inside an animation is fired & gives the information needed.

The other two requirements need some more thought and a bit of hacking ;-)

Solving the rest

Using .animate without a selection

The first point made above requires a bit of a hack. Turns out you can create a dummy element to use as the selection.

var $fakeElem = $('<div style="width:0px;"></div>');
...
$fakeElem.animate({'width' : '100px'}, opts);

The element isn't added to the DOM so isn't even rendered by the browser, we're only interested in what goes on inside the animation's step callback.

Freeing up the usage

The fourth requirement, to allow the code that runs inside the callback, is solved by placing a custom event inside the step.

opts.step = function (now, fx) {
$(document).trigger('frame', { 'fx' : fx });
};

This fires a 'beacon' every time the callback runs which any code can attach itself to. Any code bound to it will have full access to all the information it needs about that step of the animation.

The code

I've put some code up on Github to demo the approach. Bear in mind this isn't a jQuery plugin, just a demonstration of an approach I've found useful.


To see the code in action, have a look at it's Github page.

Why use .animate?

To conclude I want to answer one of the most obvious questions about the above approach: 'why use .animate at all?'. Most of the code doesn't really need to be written in jQuery and we're not using an element selection so why bother?

  • .animate is really nice to use.
  • It allows users to be pretty sure when their animations will end due to it checking the progress against timestamps at each step.
  • It provides your code with all the information you need when working on a per-frame basis.
  • You can plug different easing routines into it.
  • As long as you use it according to the docs the jQuery team will improve it, you don't have to. (For example, the requestAnimationFrame method used by modern browsers to improve animation was added soon after it became available.)

Ultimately though, you don't need to. All the main bits, from the use of custom events to that of jQuery can be swapped out for others, I just wanted something with all the benefits above that I could use now.

Monday, 10 October 2011

Learning JavaScript: From jQuery up

Being someone who's gone from puzzling over DOM scripting to fairly advanced JavaScript I've a decent amount to write about learning it.

Starting with jQuery

jQuery is amazing. It's syntax is so clear and well thought out that you can write JavaScript by coding something that's pretty close to how you would explain what you want to do. For example:

$('img.thumbnail').mouseover(function () { $(this).css('border', 'solid 2px red'); });

Writing code like this is easily readable and, knowing the quality of jQuery's internals, pretty efficient. The issue I have with it is less to do with using jQuery and more to do with its style.

Early in my learning I wrote a lot of code like you see above but the more I had to write, the less I thought that style was the best approach. When the problem you are trying to solve passes a certain complexity, using the above style can create duplication of resources and repetition of actions.

A different approach

The best solution is to start looking at what JavaScript as a language can do to solve these problems.

Let's look at some actual real-world code as a way to look for some solutions. Below is a simple accordian, written using jQuery for most things with a bit of structure for organisation.

// hide all accordion content divs
$('div.accordionContent').hide();

// when you click a tab
$('a.accordionTab').click(function () {
       // 1. Selection of active element every time you click
       var $currentActive = $('a.accordionTab').filter('.active');
 
       // if this tab is active, close it's content div
       if ($currentActive[0] === this) {
              close($(this));
       } else {
            if ($currentActive.length > 0) {
                  close($currentActive);
            }
            open($(this));
       }
       return false;
});

// mark the tab as inactive and hide it's content div
close = function ($el) {
       // 2. DOM traversal & element selection every time this function is run
       $el.removeClass('active')
              .parent()
              .find('.accordionContent')
              .slideUp();
};

open = function ($el) {
       // 2. DOM traversal & element selection every time this function is run
       $el.addClass('active')
              .parent()
              .find('.accordionContent')
              .slideDown();
};

The main problems are, as listed above

  1. Finding the current active tab every time the click event runs through element selection.
  2. Every time open or close are run, it causes DOM traversal and element selection.

The code also runs in the same scope as any other script in the document (which can lead to variables & functions being overwritten or used by accident) when it should really be contained in a single place.

Solutions

  1. Put the whole thing in an object and store that in 1 variable*.
  2. Do your selections once and store the result in variables. That includes selection by DOM traversal.

* This variable should really be stored in a namespace when we are at the production stage.

Pray explain

OK, so in an effort to make this a bit clearer I've stuck the code on Github. Download it now (clone it if you know how to use git, or click the Downloads button and select the .zip).

**Update** Having figured out Git hub pages the code is now more easily accessible here

It doesn't need to be accessed via a server, just open the .html files in your browser and we'll work our way through, starting with base_pattern.html.

base_pattern.html

The JavaScript (js/pattern.js) here is a base pattern with this structure:

All code is contained in one object stored in the pattern variable. That object has a single method called init that you call when you have an element you want to add behaviour to.

Inside pattern is a constructor called Constr.

Constr = function (elm) {
    ...
Every time you run pattern's init method it uses Constr to create an object for each element matched to hold its behaviours.

init : function (context) {
       // 4. Searches are always performed within a context
       if (context === 'undefined') {
           context = document.body;
       }

       // 5. For each matching element, create an object using the Constr constructor 
       $('.accordion', context).each(function () {
           new Constr(this);
       });
}

Notice how searches are always performed inside an context element, even if this is document.body. This means that you can run init, not just on a whole document but also on a sub-section of one (if you replace a sub-section with AJAX for example).

Apart from that the structure we started with is mainly the same. We're still attaching an event to each accordion tab and the logic inside that is using open and close methods to control the accordion content areas.

The main difference is that, thinking a bit more programatically, we are setting all our variables at the top of Constr, including those that hold element selections.

var $elm = $(elm),
    $tabs = $elm.find('.accordionTab'),
    tabIdx = $tabs.length,
    $contentAreas = $elm.find('.accordionContent'),
    activeIdx = $tabs.index($tabs.filter('.'+ activeClass)),
    that = this,
    onClick;

By wrapping everything in pattern we also create a closed scope that means we can define what we like safely.

If you open your browser's Developer tools (in Chrome, Safari or IE9, Firebug in Firefox or Dragonfly in Opera) and type pattern you'll be able to see and inspect the pattern object.

One last efficiency

The pattern is quite nice now. The structure is a nice mapping of the logic that makes the accordion work, variables are all stored and re-used and changes to DOM elements in open and close are just to properties of their jQuery wrappers; no DOM traversal or selection is needed.

It's a bit personal but the last thing that's bugging me now is that at the top of onClick the idx variable is set each time by jQuery looping through the $tabs object which feels a bit inefficient.

onClick = function () {
            var idx = $tabs.index(this);

We are creating an onClick function for all tabs so it would make more sense to give each of these functions access to the index of that tab in $tabs. It is possible to use closure to do this so let's have a go.

base_pattern_with_closure.html

So in the JavaScript for this page (js/pattern_with_closure.html) let's have a look at the new onClick.

// This function uses closure to create a function with access to the idx at the point it is called
onClick = function (idx) {
       // capture each index using a closure
       return function (eventObj) {
              if(activeIdx !== null) {
                     that.close();
              }
              if (idx === activeIdx) {
                     activeIdx = null;
              } else {
                     activeIdx = idx;
                     that.open();
              }
       };
};

So now rather than onClick being a variable containing a function to run on the click event, it now is like a factory, returning a function to do this.

This makes more sense if we look at it's use.

// for each tab, bind a function to its click event which has access to the tab's index
while (tabIdx--) {
       $tabs.eq(tabIdx).bind('click', onClick(tabIdx));
}

The onClick function is now run at the point we bind the event and the function it returns is what fires on that event, not onClick.

When the function it returns is created (at the event binding stage), onClick sends it a single parameter called idx which is the index of the tab in $tabs that was clicked. idx only exists at the point onClick runs but, thanks to closure, the internal function will always have access to it.

Because we use closure we are effectively pushing the effort onto scope resolution rather than looping through an array.

More info

I'm not exaggerating when I say it took me almost a year to 'get' closure after I first came across it. By contrast I once explained it to a colleague (with a lot of experience of heavy programming) and they got it straight away. Depending on your speed of understanding here's a few links to help:

What else?

In the rest of the examples I've tried to explore the different options you have when approaching the problem in this way (see index.html). I'd be very interested in any suggested changes to these examples or to other options so if you can think of any, let me know (or just fork the repositry :).

Tuesday, 13 September 2011

JSLint in your workflow

Primer

JSLint is a code validation tool for JavaScript

Using it will help ensure JavaScript on a project is produced in a consistent way that avoids syntax errors.

The option to 'JSLint' a JavaScript file is everywhere but the options that it is being run under are usually not shown. This means you're validating your code against invisible settings. It also means members of a team can be validating their JavaScript against settings different to yours, which is dangerous.

Goal

Be able to lint code against the same options on everyone's IDE across the project

A bit more specifically, we need to run a set version of JSLint, with set options and we need to be able to run this on any OS and with any IDE.

Programs used
  • Java (Java Developer Kit (JDK) or Java Runtime Executable (JRE) - used to run Java programs)
  • JSLint4Java - a Java program used to run jslint
  • Rhino - Mozilla's Java implementation of JavaScript. Can run any JavaScript program.
  • JSLint

Using Java on your computer

Check if you have a version of Java on your computer:

Windows
  1. Go to Start menu
  2. Go to 'Run'
  3. Type 'cmd' to enter the command line
  4. Type 'java -version'
Mac
  1. Open the Terminal App (in Applications -> Utilities)
  2. Type 'java -version'

If you get:'java is not recognised as an internal or external command, operable program or batch file' you don't have Java.

If you get output that starts 'java version' you have it.

If you haven't got a version of java on your computer, download the JRE or JDK from Oracle

On both Windows & Mac, the installer will take care of everything to do with the installation. On Mac, installing a new version of Java will not always update your core version. If this happens, use the Java Preferences App (Applications -> Utilities -> Java Preferences App) to switch versions.

Running JSLint through Java

We are interested in running JSLint using Java because:

  1. Java is supported cross-platform and cross-OS
  2. Running JSLint through a non-browser-based program allows a lot of flexibility.
  3. Jars are supported by most Continuous Integration Processes.

So, in short, what we are trying to do is run a program that checks a file against JSLint validation and outputs the results of this. There are a few options of how to do this.

Options for doing this

The first time I tried this (under the tutelage of @agentdeal using Mozilla's Rhino, a Java program that runs JavaScript programs. It does this by running jslint.js and sending it the JavaScript file you are testing as a parameter.

java -jar js.jar jslint.js filetotest.js

The results are produced by Rhino as standard output meaning it's up to you what you do with them.

Even now this is still a good and solid method and benefits from being very simple and also by no part of its process hidden. It also doesn't pack JSLint into the jar meaning you can grab the latest version from Douglas Crockford's repo on Github

If you prefer a jar with all this as options, rather than file dependancies (which is a lost less hassle to plug into your Maven or Ant config file, you may like JSLint4Java.

I'm not going to tell you which to chose but I do list each approach on separate pages:

I personally prefer to leave the options and globals settings in the JavaScript files themselves so people can see what their code is being validated against.

A word of caution

How ever you go about running it JSLint can, to quote Mr Crockford, Hurt your feelings with its bluntness. This is what you want (clarity) but it will only work if you don't confuse this directness your own set up errors.

So make sure the options you are setting confirm the idea of valid code your developers are working to and be aware that any options you don't set values for will inherit them from the version of jslint.js you are using.

Also ensure you use an external version of jslint.js, preferably the latest from github. Without specifying this with JSlint4Java you will be using the one baked into the jar.

Running JSLint through JSLint4Java

Note: this post assumes you already have a version of Java installed on your OS. If not, refer to this post for how to get it.

JSLint4Java is a Jar that allows us to run JSLint & output the results.

Installation

Get the latest JSLint4Java. Make sure you download a file that ends .dist.zip as those ending in .src.zip are the source files for development, not end users.

Unzip the downloaded file and store the resulting folder in a sensible location (like C:\Program Files\Java if Windows, or /usr/local/bin if Mac). It doesn't matter where it goes, just that you know the path to that location as you'll need it later on.

Inside the folder you'll find a jar file that's pretty clearly named, for example jslint4java-2.0.0.jar. This is the JSLint4Java jar file you'll be using.

Get the latest version of JSLint. (Usually saved as jslint.js.) If you have a version you are required to use, you can substitute it for jslint.js.

Running JSLint4Java

This is fairly simple but, in my opinion, made difficult by not being documented very well on JSLint4Java's Google code page. If you're like me and want to run it from the command line before you go plugging it into any CI, doing this is not well explained.

To use it in this way you will always be running it like this:

java -jar [path to jslint4java jar file] [options] [file to validate]

So the only bits of this that vary are the options and the file to validate. To see which options are available, run one of these 2 commands:

Windows

java -jar jslint4java-2.0.0.jar --help

Mac

man jslint4java-2.0.0.jar

Core options

Most of the options are duplicates of the JSLint ones with a few important exceptions.

Setting jslint version

The --jslint option allows you to specify the jslint.js file to use:

java -jar jslint4java-2.0.0.jar --jslint jslint.js

Controlling output

The --report option allows you to specify the format the results are in. The options are:

  • plain (plain text output)
  • xml
  • junit (in junit xml)
  • checkstyle (in checkstyle xml)
  • report (in HTML)

Basic usage is typically like this:

java -jar jslint4java-2.0.0.jar --report xml

This is usually only useful if you can send the output to a file. This is done like so (assuming the output file is in the same folder as jslint4java-2.0.0.jar):

Windows

java -jar jslint4java-2.0.0.jar --report xml > results.xml

Mac

java -jar jslint4java-2.0.0.jar --report xml | results.xml

Monday, 12 September 2011

Running JSLint using Mozilla Rhino

Note: this post assumes you already have a version of Java installed on your OS. If not, refer to this post for how to get it.

Mozilla's Rhino is a Java program that runs JavaScript. What this means to you is you can use it to run JavaScript programs outside of the browser, like Python, Perl, PHP, etc...

Because we are trying to run a JavaScript program (JSLint) on the command-line that takes arguments and outputs results in text form, it's quite useful.

Installation

Rhino takes a little longer to install, mainly because the downloaded files then need building. What I mean by this is that some of the installation process is done by a build tool called ant.

This sounds excessive, but if you have a Mac you'll probably already have it. If you don't, you should, it's amazing. (It's also used by many Java programs as part of their installation so you'll need it in future.)

To check if you have it, try typing ant on either your commmand-line (Windows) or in your Terminal (Mac).

If you get this: Buildfile: build.xml does not exist!

...then you have it. (Ant uses build.xml as its default configuration file so this message is just saying there isn't one.)

If you get: ant is not recognised as an internal or external command, operable program or batch file ...or a similar message then you haven't got Ant.

Installing Ant
Getting the files

If you're on Windows, it's a good idea at this stage to create a folder called 'Apache Software Foundation' in your C:\Program FIles folder. (This is just for organisational purposes, to keep all software you may use from Apache in one place.)

Download the latest .zip file of Apache Ant from here (It will be called something like apache-ant-1.8.2-bin.zip)

On Windows, extract it into the 'Apache Software Foundation' folder you made before. This should create a folder called what the .zip is but without the -bin.zip bit. Something like 'apache-ant-1.8.2'.

On Mac it's better to keep it somewhere like /usr/local/bin so you should do the following in your Terminal app (Applications -> Utilities -> Terminal).

Assuming you downloaded it to your Downloads folder, navigate to it like so: cd Downloads (This assumes this is your 1st command & therefore being executed in your home folder).

Type mv apache-ant-1.8.2.zip /usr/bin/apache-ant-1.8.2.zip to move it to the /usr/bin folder.

Type: tar -xvf /usr/bin/apache-ant-1.8.2.zip ...to extract the zip archive into a folder of the same name, using the tar utility program. Options used are to 'extract', 'use a verbose style to report the results' & 'use the file specified by the given path'.

Type: rm /usr/bin/apache-ant-1.8.2.zip ...to remove the original zip file.

If you have issues with any of the commands above you may need to preface them with the sudo command, i.e. sudo tar -xvf /usr/bin/apache-ant-1.8.2.zip

Note: do not use the sudo prefix without understanding it, it removes safe-guards to any commands you run.

Installation

In Windows, in your explorer window, copy the path to this folder (i.e. C:\Program Files\Apache Software Foundation\apache-ant-1.8.2)

Ant uses enviroment variables (variables that are available to all programs on an OS, used point to common resources) to work.

On Windows, these are set up like so:

  1. Go to your desktop and right-click on the 'My Computer' icon.
  2. Select 'properties' on the menu.
  3. Click on the 'Advanced' tab.
  4. Inside this tab there is a button called 'Environment Variables'. Click on it to bring up another menu.
  5. In the section at the bottom called 'System variables', click the 'New' button.
  6. Enter the variable name as ANT_HOME and the variable value as the path to your ant folder you copied before. (i.e. C:\Program Files\Apache Software Foundation\apache-ant-1.8.2) This creates an environment variable pointing to where ant is on your system.
  7. In the section at the bottom called 'System variables' scroll the list until you find an entry called 'Path' and click on it to select.
  8. Click the 'Edit' button and add this to the end: ;%ANT_HOME%\bin. The semi-colon adds a new sub-section to the path, %ANT_HOME% uses the enviroment variable you created and adding \bin to the end. So you're effectively adding C:\Program Files\Apache Software Foundation\apache-ant-1.8.2\bin to it.*

* This means that when you type 'ant' at the command prompt, Windows will search the local directory for a program called 'ant' and when it doesn't find one will search all folders listed in the Path string. As the bin folder (inside your ant folder) is now one of these, it will look inside there and run the 'ant' program.

Note: Running the JDK/JRE installer should have added an environment variable called JAVA_HOME. If you don't have this, you'll need to add it, pointing to the JDK or JRE folder in C:\Program Files\Java, i.e. C:\Program Files\Java\jdk1.7.0'

On Mac, open the Terminal app & type these commands:

export ANT_HOME=/usr/bin/apache-ant-1.8.2

export PATH=${PATH};${ANT_HOME}/bin

In the above commands, we are assuming your Apache Ant folder is as we specified above & we are then adding that environment variable to your path.

If the ANT_HOME environment variable is set to /usr/bin/apache-ant-1.8.2 then PATH=${PATH};${ANT_HOME}/bin is adding /usr/bin/apache-ant-1.8.2/bin to the end of the PATH variable string.

After all this, close and reopen the command prompt and type ant. If you get Buildfile: build.xml does not exist! ...you now have ant. Otherwise, check the stages above to make sure all is set up ok.

Installing Rhino

You can get the files for Rhino from here.

Place the zip where ever you want the Rhino program to sit (i.e. on Windows it might sit in C:\Program Files\Java). Unzipping the file should leave you with a similarly-named folder, for example, something like C:\Program Files\Java\Rhino1_7R3 from Rhino1_7R3.zip.

Now use either command-line (on Windows) or Terminal (on Mac) to move into the new folder:

(Windows) cd C:\Program Files\Java\Rhino1_7R3

(Mac) cd /usr/local/bin/Rhino1_7R3

Type ant and Ant should build Rhino. If it works you should get no errors and be left with a file called js.jar in the folder.

Using Rhino

Rhino runs like any other jar file, taking the JavaScript file you want to run as the 1st argument and any following arguments are passed to the JavaScript file. So this:

java -jar C:\Program Files\Java\Rhino1_7R3\js.jar fulljslint.js filetotest.js

Uses Java to run the C:\Program Files\Java\Rhino1_7R3\js.jar jar program, sending it the JavaScript file fulljslint.js and sending that file filetotest.js as its first parameter.

Now hold on a minute

You may have spotted that there's something I've left out. I've not explained about the file you're sending to Rhino (fulljslint.js). It turns out you can't just send Rhino jslint.js and there's a good reason for this.

Rhino will run any JavaScript you send it but that's all it will do. jslint.js is pretty simple, it takes 2 arguments: the JavaScript file to validate as a string and an object of options to validate against. It can't output the results to the command-line/terminal, or anything else you tell it to because it doesn't know how (JavaScript programs generally have no access to the output stream). So fulljslint.js is actually jslint.js with some extra code at the end to print the results it produces.

I made a version of fulljslint.js with the latest version of jslint.js from github (dated 2011-07-19) here.

If you want to update the version of jslint.js in it, just get the version you need and add this file on the end.

In closing

Well done you made it to the end, that took ages didn't it? In getting this far you have:

  • Got Apache ant on your machine which is brilliant.
  • Got Rhino, which can be used to run any JavaScript file outside of the browser.
  • Understand how it all works so you can change it.

A quick note. I got the Rhino code via http://hustoknow.blogspot.com/2011/02/jslint-and-rhino-support.html so thanks to Roger Hu for his work on this.