Avoid iFrame content clipping with CSS transform on iOS

The HTML(5) version of our SCORM based e-learning runtime heavily uses i-frames as template containers. This architecture gives a lot of flexibility but since customers mainly request iPad support we where very unhappy with Safari’s rendering capabilities on iOS. Of course we use CSS transform to get the best animation performance on mobile devices, but in Apple’s mobile browser you’ll likely see huge clipping rectangles during hardware accelerated transitions.

There was not much information about that issue online but at least this entry on stackoverflow suggested there is not much you can do. Well, it took a lot of trail an error testing time but finally we nailed it. The solution is quite simple:

Whenever you use CSS transform with i-frames or it’s parents you also need to apply a basic CSS transform to the body tag of your i-frame content. That’s it. You can also use other contents within the loaded frame HTML page but the body tag seems to be the perfect target.

So this is a little JavaScript code snipped:

// Reference to your iFrame HTML element
var iFrameElement = document.getElementById( "yourIFrameID" );
 
// Avoid clipping by applying simple css transfrom3D to the iframe content itself
$( iFrameElement.contentWindow.document.body ).css( "-webkit-transform", "translate3d(0,0,0)" );
 
// Start your iFrame transition (from a visible position)
$( iFrameElement ).css( "-webkit-transform", "translate3d(-1000px,0,0)" );

UPDATE: Make sure you start from a visible position!

Don’t forget to remove CSS transform after your animation is done:

// Don't forget to reset after your transition has finished!
$( iFrameElement.contentWindow.document.body ).css( "-webkit-transform", "none" );

I suggest to use “none” as the default value. We sometimes experienced weird behavior using an empty string…

Have fun coding!

Removing comments in CSS, HTML and ECMAScript (JavaScript)

While working on an Adobe AIR based source code editor I was looking for an easy way to remove comments from different kind of source codes. What first seemed like an easy regular expression turned out to be much more complex because comment pre- and suffixes can also occur in string literals or regular expressions and our old friend Internet Explorer allows HTML conditional comments and JavaScript conditional compilation. Great.

First I had a look at YUICompressors source code and Google Page Speed but then I decided to port a script for removing JavaScript comments created by James Padolsey.

I just improved the recognition for regular expressions a little and since this code should work with all ECMAScript based source codes I named my class ECMAScriptParser instead of JavaScriptParser:

package de.superclass.parser
{
	/**
	 * @author Markus Raab (superclass.de | blog.derRaab.com)
	 */
	public class ECMAScriptParser
	{
		/**
		 * Removes inline and block comments from an ECMAScript source string. 
		 * 
		 * This is a port of James Padolsey	 with an slightly improved RegExp recognition.
		 * @see: http://james.padolsey.com/javascript/removing-comments-in-javascript
		 * 
		 * @param ecmaScript		ECMAScript source string
		 * @param removeCondComp	Optional (default=false) - Whether to remove Internet Explorers JavasScript conditional compilation comments or not.   
		 * @return					ECMAScript source string without comments
		 */
		public static function removeComments( ecmaScript : String, removeCondComp : Boolean = false ) : String
		{
			var modeSingleQuote		: Boolean = false;
			var modeDoubleQuote		: Boolean = false;
			var modeRegExp			: Boolean = false;
			var modeBlockComment	: Boolean = false;
			var modeLineComment		: Boolean = false;
			var modeCondComp		: Boolean = false; 
 
			var vector : Vector.<String> = Vector.<String>( ( '__' + ecmaScript + '__' ).split( '' ) );
				vector.fixed = true;
 
			var c : int = vector.length;
 
			for ( var i : int = 0; i < c; i++ )
			{
				var string : String = vector[ i ];
 
				if ( modeRegExp )
				{
					if ( string === '/' && vector[ i - 1 ] !== '\\' )
					{
						modeRegExp = false;
					}
					continue;
				}
 
				if ( modeSingleQuote )
				{
					if ( string === "'" && vector[ i - 1 ] !== '\\' )
					{
						modeSingleQuote = false;
					}
					continue;
				}
 
				if ( modeDoubleQuote )
				{
					if ( string === '"' && vector[ i - 1 ] !== '\\' )
					{
						modeDoubleQuote = false;
					}
					continue;
				}
 
				if ( modeBlockComment )
				{
					if ( string === '*' && vector[ i + 1 ] === '/' )
					{
						vector[ i + 1 ] = '';
						modeBlockComment = false;
					}
					vector[ i ] = '';
					continue;
				}
 
				if ( modeLineComment)
				{
					string = vector[ i + 1 ];
					if ( string === '\n' || string === '\r' )
					{
						modeLineComment = false;
					}
					vector[ i ] = '';
					continue;
				}
 
				if ( modeCondComp )
				{
					if ( vector[ i - 2 ] === '@' && vector[ i - 1 ] === '*' && string === '/' )
					{
						modeCondComp = false;
					}
					continue;
				}
 
				if ( string === '"' )
				{
					modeDoubleQuote = true;
					continue;
				}
 
				if ( string === "'" )
				{
					modeSingleQuote = true;
					continue;
				}
 
				if ( string === '/' )
				{
					if ( ! removeCondComp && vector[ i + 1 ] === '*' && vector[ i + 2 ] === '@' )
					{
						modeCondComp = true;
						continue;
					}
 
					if ( vector[ i + 1 ] === '*' )
					{
						vector[ i ] = '';
 
						modeBlockComment = true;
						continue;
					}
 
					if ( vector[ i + 1 ] === '/' )
					{
						vector[ i ] = '';
						modeLineComment = true;
						continue;
					}
 
					for ( var k : int = i - 1; true; k-- )
					{
						string = vector[ k ];
						if ( string !== ' ' )
						{
							if ( string === '=' )
							{
								modeRegExp = true;
							}
							break;
						}
					}
				}
			}
			return vector.join( '' ).slice( 2, -2 );
		}
	}
}

Allright – CSS doesn’t allow inline or conditional comments so I removed this script functionality and created a CSSParser class:

package de.superclass.parser
{
	/**
	 * @author Markus Raab (superclass.de | blog.derRaab.com)
	 */
	public class CSSParser
	{
		/**
		 * Removes comments from a CSS string. 
		 * 
		 * This is a shrinked port of James Padolseys script.
		 * @see: http://james.padolsey.com/javascript/removing-comments-in-javascript
		 * 
		 * @param css				CSS string
		 * @return					CSS string without comments
		 */
		public static function removeComments( css : String) : String
		{
			var modeSingleQuote		: Boolean = false;
			var modeDoubleQuote		: Boolean = false;
			var modeBlockComment	: Boolean = false;
 
			var vector : Vector.<String> = Vector.<String>( ( '__' + css + '__' ).split( '' ) );
				vector.fixed = true;
 
			var c : int = vector.length;
 
			for ( var i : int = 0; i < c; i++ )
			{
				var string : String = vector[ i ];
 
				if ( modeSingleQuote )
				{
					if ( string === "'" && vector[ i - 1 ] !== '\\' )
					{
						modeSingleQuote = false;
					}
					continue;
				}
 
				if ( modeDoubleQuote )
				{
					if ( string === '"' && vector[ i - 1 ] !== '\\' )
					{
						modeDoubleQuote = false;
					}
					continue;
				}
 
				if ( modeBlockComment )
				{
					if ( string === '*' && vector[ i + 1 ] === '/' )
					{
						vector[ i + 1 ] = '';
						modeBlockComment = false;
					}
					vector[ i ] = '';
					continue;
				}
 
				if ( string === '"' )
				{
					modeDoubleQuote = true;
					continue;
				}
 
				if ( string === "'" )
				{
					modeSingleQuote = true;
					continue;
				}
 
				if ( string === '/' )
				{
					if ( vector[ i + 1 ] === '*' )
					{
						vector[ i ] = '';
 
						modeBlockComment = true;
						continue;
					}
				}
			}
			return vector.join( '' ).slice( 2, -2 );
		}
	}
}

Good. Lastly I extended the code to remove all comments within HTML source code, which can also contain JavaScript and of course CSS. This is the HTMLParser:

package de.superclass.parser
{
	/**
	 * @author Markus Raab (superclass.de | blog.derRaab.com)
	 */
	public class HTMLParser
	{
		/**
		 * Removes all HTML, CSS and ECMAScript comments from a HTML string.
		 * 
		 * This is an extended port of James Padolseys script with an improved RegExp recognition.
		 * @see: http://james.padolsey.com/javascript/removing-comments-in-javascript
		 * 
		 * @param html						HTML string
		 * @param removeHTMLCondComment		Optional (default=false) - Whether to remove Internet Explorers HTML conditional comments or not.
		 * @param removeJSCondComp			Optional (default=false) - Whether to remove Internet Explorers JavasScript conditional compilation comments or not.
		 * @return							HTML string without comments
		 */
		public static function removeComments( html : String, removeHTMLCondComment : Boolean = false, removeJSCondComp : Boolean = false ) : String
		{
			var modeSingleQuote		: Boolean = false;
			var modeDoubleQuote		: Boolean = false;
			var modeRegExp			: Boolean = false;
			var modeBlockComment	: Boolean = false;
			var modeLineComment		: Boolean = false;
 
			var modeJSCondComp		: Boolean = false;
 
			var modeHTMLComment		: Boolean = false;
			var modeHTMLCondComment	: Boolean = false;
 
			var vector : Vector.<String> = Vector.<String>( ( '__' + html + '__' ).split( '' ) );
				vector.fixed = true;
 
			var c : int = vector.length;
 
			for ( var i : int = 0; i < c; i++ )
			{
				var string : String = vector[ i ];
 
				if ( modeRegExp )
				{
					if ( string === '/' && vector[ i - 1 ] !== '\\' )
					{
						modeRegExp = false;
					}
					continue;
				}
 
				if ( modeSingleQuote )
				{
					if ( string === "'" && vector[ i - 1 ] !== '\\' )
					{
						modeSingleQuote = false;
					}
					continue;
				}
 
				if ( modeDoubleQuote )
				{
					if ( string === '"' && vector[ i - 1 ] !== '\\')
					{
						modeDoubleQuote = false;
					}
					continue;
				}
 
				if ( modeBlockComment )
				{
					if ( string === '*' && vector[ i + 1 ] === '/')
					{
						vector[ i + 1 ] = '';
						modeBlockComment = false;
					}
					vector[ i ] = '';
					continue;
				}
 
				if ( modeLineComment)
				{
					string = vector[ i + 1 ];
					if ( string === '\n' || string === '\r' )
					{
						modeLineComment = false;
					}
					vector[ i ] = '';
					continue;
				}
 
				if ( modeJSCondComp )
				{
					if ( vector[ i - 2 ] === '@' && vector[ i - 1 ] === '*' && string === '/' )
					{
						modeJSCondComp = false;
					}
					continue;
				}
 
				if ( modeHTMLComment )
				{
					// --> 
					if ( string === '-' && vector[ i + 1 ] === '-' && vector[ i + 2 ] === '>' )
					{
						vector[ i + 1 ] = '';
						vector[ i + 2 ] = '';
 
						modeHTMLComment = false;
					}
					vector[ i ] = '';
					continue;
				}
 
				if ( modeHTMLCondComment )
				{
					if ( string === 'i' && vector[ i + 1 ] === 'f' && vector[ i + 2 ] === ']' && vector[ i + 3 ] === '-' && vector[ i + 4 ] === '-' && vector[ i + 5 ] === '>' ) // if]--> 
					{
						modeHTMLCondComment = false;
						i += 5;
					}
					continue;
				}
 
				if ( string === '<' && vector[ i + 1 ] === '!' && vector[ i + 2 ] === '-' && vector[ i + 3 ] === '-' ) // <!--
				{
					if ( ! removeHTMLCondComment && vector[ i + 4 ] === '[' && vector[ i + 5 ] === 'i' && vector[ i + 6 ] === 'f' ) // <!--[if
					{
						modeHTMLCondComment = true;
					}
					else
					{
						vector[ i ] = '';
						modeHTMLComment = true;
					}
				}
 
				if ( string === '"' )
				{
					modeDoubleQuote = true;
					continue;
				}
 
				if ( string === "'" )
				{
					modeSingleQuote = true;
					continue;
				}
 
				if ( string === '/' )
				{
					if ( ! removeJSCondComp && vector[ i + 1 ] === '*' && vector[ i + 2 ] === '@' )
					{
						modeJSCondComp = true;
						continue;
					}
 
					if ( vector[ i + 1 ] === '*' )
					{
						vector[ i ] = '';
 
						modeBlockComment = true;
						continue;
					}
 
					if ( vector[ i + 1 ] === '/' )
					{
						vector[ i ] = '';
						modeLineComment = true;
						continue;
					}
 
					for ( var k : int = i - 1; true; k-- )
					{
						string = vector[ k ];
						if ( string !== ' ' )
						{
							if ( string === '=' )
							{
								modeRegExp = true;
							}
							break;
						}
					}
				}
			}
			return vector.join( '' ).slice( 2, -2 );
		}
	}
}

Let me know if I made a mistake but it seems to work quite well. Or do you know a good library that already does this kind of optimizations?

Well, that’s it for now. Have fun coding!

Ilias SCORM 2004 can’t read timestamp

If you’re working with Ilias LMS you might run into this issue as well, so I’ll share this here.

We’re using SCORM 2004 with it’s cmi.comments_from_learner but it turned out that it wasn’t possible to read a previously set timestamp value during the next SCO visit. Did nobody ever read a timestamp again? Anyway, since we strongly needed this functionality I had to figure out where the problem was. After making definitely sure it wasn’t our mistake I downloaded a fresh copy of the latest stable Ilias version (4.1.7) and tried to investigate how this system works.

A timestamp value yyyy-mm-ddThh:mm:ss.msZ was stored using MySQL datetime and therefore read back as yyyy-mm-dd hh:mm:ss. Time zone designator and 2 digits accuracy are SCORM 2004 optional, but the missing ‘T’ separator killed the data conversion.

So let’s have a look at the related original JavaScript function:

function setItemValue (key, dest, source, destkey) 
{
	if (source && source.hasOwnProperty(key)) 
	{
		var d = source[key];
		var temp=d;
		if (!isNaN(parseFloat(d)) && !(/[A-Za-z]/.test(d))) {
			d = Number(d);
		} else if (d==="true") {
			d = true;
		} else if (d==="false") {
			d = false;
		}
		//special handling for titles - no conversion
		if (key == "title") {
			d=temp;
		}
		dest[destkey ? destkey : key] = d;
	}
}

And here is my temporary fix:

function setItemValue (key, dest, source, destkey)
{
    if (source && source.hasOwnProperty(key))
    {
        var d = String( source[key] );
 
        if ( destkey === "timestamp" )
        {
            // Fixed timestamp casting to NaN!
            // 26.09.2011 - derRaab - http://blog.derRaab.com
            //
            // a) No conversion
            // b) Ilias delivers date string without 'T': yyyy-mm-dd hh:mm:ss -> yyyy-mm-ddThh:mm:ss
            if ( d.length > 10 && d.charAt( 10 ) === " ")
            {
                d = d.split( " " ).join( "T" );
            }
        }
        else if ( key === "title" )
        {
            //special handling for titles - no conversion
        }
        else
        {
            // 26.09.2011 - derRaab - http://blog.derRaab.com
            // Use Number() instead of parseFloat()
            if (!isNaN(Number(d)) && !(/[A-Za-z]/.test(d)))
            {
                d = Number(d);
            }
            else if (d==="true")
            {
                d = true;
 
            } else if (d==="false")
            {
                d = false;
            }
        }
        dest[destkey ? destkey : key] = d;
    }
}

Please note that I replaced the use of parseFloat() with Number() – I’m investigating another problem with the cmi.location characterstring – but that’s fixed for now.

I reported these issues to the Ilias development team and they fixed at least the ‘T’ separator problem for Ilias 4.2.0:
http://www.ilias.de/mantis/view.php?id=7846
http://www.ilias.de/mantis/view.php?id=7847

JavaScript development – libraries and OOP coding conventions

As I wrote earlier I’m currently porting our ActionScript framework to JavaScript, which basically means I’m diving into JavaScript from and ActionScript developers perspective. Of course I’m still at the beginning of becoming a good JavaScript developer, but I made some progress I’d like to share.

I’ll start right away recommending some core libraries:

  • Objs – Namespaces and class inheritance
  • PureMVC Objs – MVC framework (Objs port)
  • JS-Signals – Event / Messaging system
  • jQuery – Helps with core functionalities and cross browser issues

The existing ActionScript project is based on PureMVC so it was obvious to settle on the same horse, but it wasn’t easy to decide which PureMVC port! This is a fundamental decision because it also defines how to write OOP JavaScript.

Objs has a clean syntax (and documentation) and after developer M. Saunier Frédéric was kind enough to resolve my license problem I used his lightweight version with one little customization: I changed the constructor function name from initialize() to __construct() as it looks more appropriate to me and I already used some initialize() functions for different tasks.
Changed versions: customized puremvc-objs-2.0.js and customized objs-2.0.js.

ActionScript developers probably know Robert Penners AS-Signals but I never implemented it in one of my projects so now I use the concept with JS-Signals.

And of course I also use jQuery with it’s great developer community.

So this is how a “class” with applied coding conventions would look like:

/**
 * Created by derRaab(); - http://blog.derRaab.com
 */
( function () {
 
	/**
	 * Namespace vars for minification (only necessary if used multiple times, but you get the idea how to save strings)
	 */
	var nsCom = "com.",
		nsComDerraab = nsCom+ "derraab.",
		nsComDerraabExamples = nsComDerraab+ "examples.",
 
	/**
	 * Final class
	 */
	ExampleClass,
 
	/**
	 * Temporary class prototype (separated because that helps with IDE code completion)
	 */
	ExampleClassPrototype =
	{
		/**
		 * Without leading underscore part of public API.
		 *
		 * @type {Boolean}
		 * @private
		 */
		publicVar : false,
 
		/**
		 * With one leading underscore part of protected API,
		 * Use it only in this and it's subclasses.
		 *
		 * @type {String}
		 * @private
		 */
		_protectedVar : "",
 
		/**
		 * With two leading underscores use it only in this class!
		 * @type {Number}
		 * @private
		 */
		__privateVar : 0,
 
		/**
		 * <code>ExampleClass</code> constructor (called on instance creation!)
		 *
		 * @param {Number}	privateValue
		 * @param {String}	protectedValue
		 * @param {Boolean}	publicValue
		 */
		__construct: function( privateValue, protectedValue, publicValue )
		{
			// Call to superclass constructor (See Objs documentation)
			// ExampleClass.$super.__construct.apply( this, arguments );
 
			this.__privateVar	= privateValue;
			this._protectedVar	= protectedValue;
			this.publicVar		= publicValue;
 
			// Use static values
			this.__privateVar = ExampleClass.DEFAULT_NUMBER;
		},
 
		/**
		 * Without leading underscore part of public API.
		 */
		publicMethod: function()
		{
		},
 
		/**
		 * With one leading underscore part of protected API,
		 * Use it only in this and it's subclasses.
		 */
		_protectedMethod: function()
		{
		},
 
		/**
		 * With two leading underscores use it only in this class!
		 */
		__privateMethod: function()
		{
		}
	};
 
	/**
	 * Let Objs create our "real" class (see Objs documentation for more details
	 */
	ExampleClass = Objs( nsComDerraabExamples + "ExampleClass", ExampleClassPrototype );
 
	/**
	 * Now add some "static" values
	 */
	ExampleClass.DEFAULT_BOOLEAN = true;
	ExampleClass.DEFAULT_STRING = "georgeous";
	ExampleClass.DEFAULT_NUMBER = 1;
 
}());

This style is still evolving but I think this is kind of how I like to work.

I suggest you read my last post regarding JavaScript library development using ANT and YUICompressor which might help you with project setup and code compression.

And a word to JavaScript IDEs:

I bought WebStorm but it doesn’t integrate ANT. Luckily I earned a IntelliJ IDEA license earlier this year but I’m not absolutely happy with it. It uses a lot of system resources, needs to set up an Java project and so on. Any hints?

JavaScript library development using ANT and YUICompressor

Since I need to port a huge ActionScript project to JavaScript I see myself confronted with different practices of code organization, workspace structures and a lot more JavaScript specific stuff. I’m used to big, strongly typed ActionScript library development with hundreds of classes and interfaces but I’m not quite sure how to transfer my programming skills from ActionScript to JavaScript.

After reading loads of informations I think I created an reusable and understandable build process for my JavaScript project. I bet you’ve already read a lot about YUICompressor and ANT so here you’ll find just some brief informations.

This is my example workspace with file type src subfolders and even another subfolder within the js directory named like the JavaScript library it contains. All ANT files can be found in src/ant:
 

Now let’s have a look at system.properties. This file bundles the system specific settings and usually won’t need changes at all after your initial setup. Only YUI_COMPRESSOR_JAR and YUI_COMPRESSOR_CHARSET will affect the build.xml. I still don’t know what’s the correct charset, but my encoding problems might be caused by the editor. I’ll figure that out later…

#
# SYSTEM PROPERTIES
#
 
# Version number separated for easy changes
YUI_COMPRESSOR_VERSION=2.4.6
 
# Absolut path to YUICompressor home directory
YUI_COMPRESSOR_HOME=/Users/you/.../YUICompressor/${YUI_COMPRESSOR_VERSION}
 
# Absolute path to JAR file (used in build.xml!)
YUI_COMPRESSOR_JAR=${YUI_COMPRESSOR_HOME}/build/yuicompressor-${YUI_COMPRESSOR_VERSION}.jar
 
# Charset used with YUICompressor - MAKE SURE THIS IS CORRECT FOR YOUR SYSTEM!!!
YUI_COMPRESSOR_CHARSET=MacRoman
#YUI_COMPRESSOR_CHARSET=UTF-8
#YUI_COMPRESSOR_CHARSET=ANSI
#YUI_COMPRESSOR_CHARSET=ISO-8859-1

The project.properties currently only contains the basic workspace structure.

#
# PROJECT PROPERTIES
#
 
# Workspace root relative to build.xml
WORKSPACE_ROOT=../..
 
# Main binary directory
BIN_DIR=${WORKSPACE_ROOT}/bin
 
# JavaScript bin directory
BIN_JS_DIR=${BIN_DIR}/js
 
# Main source directory
SRC_DIR=${WORKSPACE_ROOT}/src
 
# JavaScript source directory
SRC_JS_DIR=${SRC_DIR}/js

Finally there is the main build.xml. I was trying hard to write an understandable documentation. Just follow these steps (also found more detailed within the file):

  1. Replace the default library name and prefix with your’s.
  2. Add your source files.
  3. Choose your browser and html file (works at least with Mac OS X).
  4. Run the ANT task called YOUR_LIBRARY_PREFIX_run().
<?xml version="1.0" encoding="UTF-8"?>
<project name="JavaScript Project Tasks" basedir=".">
 
	<!--
		Created without much experience ;) by derRaab(); - http://blog.derRaab.com
	-->
 
	<!-- Load external properties -->
	<property file="project.properties" />
	<property file="system.properties" />
 
	<!--
		_createWorkspace() - Creates the basic workspace structure..
	-->
    <target name="_createWorkspace()" description="Creates the basic workspace structure.">
    	<mkdir dir="${BIN_CSS_DIR}"/>
    	<mkdir dir="${BIN_JS_DIR}"/>
        <mkdir dir="${SRC_JS_DIR}"/>
        <mkdir dir="${SRC_CSS_DIR}"/>
    </target>
 
	<!--
		__openBrowser( browserName, targetFile ) - Opens a browser with a specific file.
 
		This might be Mac OS X specific, so please refer to
		http://www.darronschall.com/weblog/2007/12/launching-firefox-from-ant-on-osx.cfm
		for further informations.
 
		@param browserName		e.g. 'Firefox', 'Safari', 'Chrome'
		@param targetFile		e.g. 'path/to/index.html'
	-->
    <target name="__openBrowser()" description="Utility task - Opens a browser with a specific file.">
        <echo message="__openBrowser( ${browserName}, ${targetFile} )" />
    	<exec executable="open" spawn="yes">
    		<arg line="-a ${browserName}" />
    		<arg line="${targetFile}" />
    	</exec>
    </target>
 
	<!--
		_concatenate( inputFileListID, inputFileSetID, outputFile ) - Concatenates a FileSet into one fresh file.
 
		@param inputFileListID
		@param inputFileSetID
		@param outputFile		e.g. 'path/to/file/name.ext'
	-->
    <target name="__concatenate()" description="Utility task - Concatenates a FileSet into one fresh file.">
        <echo message="_concatenate( ${inputFileSetID}, ${outputFile} )" />
    	<delete file="${outputFile}"/>
        <concat destfile="${outputFile}" fixlastline="yes">
            <filelist refid="${inputFileListID}"/>
            <fileset refid="${inputFileSetID}"/>
        </concat>
    </target>
 
	<!--
		_yuiCompress( workDir, inputFile, outputFile ) - Compresses a single JavaScript file into one fresh file within the same directory.
 
		@param workDir			e.g. 'path/to/file'
		@param inputFile		e.g. 'name.js'
		@param outputFile		e.g. 'name.min.js'
	-->
    <target name="__compress()" description="Utility task - Compresses a single JavaScript file into one fresh file within the same directory.">
    	<echo message="_yuiCompress( ${workDir}, ${inputFile}, ${outputFile} )" />
    	<delete file="${workDir}/${outputFile}"/>
        <apply dest="${workDir}" executable="java" verbose="true">
            <fileset dir="${workDir}">
                <include name="${inputFile}" />
            </fileset>
            <arg line="-jar" />
            <arg path="${YUI_COMPRESSOR_JAR}" />	
            <arg value="--charset" />
            <arg value="${YUI_COMPRESSOR_CHARSET}" />
            <arg value="-o" />
            <targetfile />
            <mapper type="glob" from="${inputFile}" to="${outputFile}" />
        </apply>
    </target>
 
	<!--
 
		SETTING UP YOUR BUILD PROCESS:
 
		1.	Recognise the default name ('library') and prefix ('LIBRARY_JS') in this default build.xml 
		1.	Choose a unique name and prefix for your library.
		2.	Use your editor's case sensitive replace all functionality to change the default values
			('library'->'yourLibrary' and 'LIBRARY_JS_'->'YOUR_LIBRARY_JS_')
			NOTE: You can add multiple libraries to one build.xml
		3.	Add a version number (see ...VERSION) if needed.
		4.	Add files to your file list (see ...FILE_LIST) - OR! add a bunch of files to your file set (see ...FILE_SET)
		5.	Choose your browser (see ...RUN_BROWSER_NAME)
		6	Choose your run file (see ...RUN_FILE_NAME)
 
	-->
 
	<!--
		...NAME					Library name used in file system
		...VERSION				Optional library version suffix e.g. '-1.2.6' or ''
		...BIN_FILE_DIR			Library binary folder e.g. 'bin/js'
		...BIN_FILE_NAME		Full file name of concatenated file
		...BIN_MIN_FILE_NAME	Full file name of minimized file (must be different!)
		...SRC_DIR				Library source folder e.g. 'src/js/library'
		...RUN_BROWSER_NAME		Your test browser of choice (Mac OS X only?) e.g. 'Firefox', 'Safari', 'Chrome' 
		...RUN_FILE_NAME		Your test file e.g. 'index.html' 
	-->
	<property name="LIBRARY_JS_NAME" value="library"/>
	<property name="LIBRARY_JS_VERSION" value=""/>
	<property name="LIBRARY_JS_BIN_DIR" value="${BIN_JS_DIR}"/>
	<property name="LIBRARY_JS_BIN_FILE_NAME" value="${LIBRARY_JS_NAME}${LIBRARY_JS_VERSION}.js"/>
	<property name="LIBRARY_JS_BIN_MIN_FILE_NAME" value="${LIBRARY_JS_NAME}${LIBRARY_JS_VERSION}.min.js"/>
	<property name="LIBRARY_JS_SRC_DIR" value="${SRC_JS_DIR}/${LIBRARY_JS_NAME}"/>
	<property name="LIBRARY_JS_RUN_BROWSER_NAME" value="Safari"/>
	<property name="LIBRARY_JS_RUN_FILE_NAME" value="index.html"/>
 
	<!--
		...FILE_LIST	File list used within _concatenate()
		...FILE_SET		File set used within _concatenate()
 
		Important:
 
		1.	YUICompressor will use the ...FILE_LIST first and ...FILE_SET afterwards!
			So you can first assign a special file order and then additionally add a bunch of unordered files
		2.	Avoid duplicates since YUICompresser doesn't check if a file was already added!
		3.	It's a good approach to only use one of them and leave the other one empty.
	-->
    <filelist id="LIBRARY_JS_FILE_LIST" dir="${LIBRARY_JS_SRC_DIR}">
    	<file name="core.js"/>
    	<file name="more.js"/>
    	<file name="evenmore.js"/>
    </filelist>
    <fileset id="LIBRARY_JS_FILE_SET" dir="${LIBRARY_JS_SRC_DIR}">
    	<exclude name="**/*.*"/><!-- by default file set is not used! -->
    	<!--<include name="**/*.js"/> this is how you add all js files-->
    </fileset>
 
	<!--
		..._createWorkspace()	- Creates the library source dir.
		..._concatenate()		- Concatenates the library.
		..._compress()			- Compresses the library.
		..._openBrowser()		- Open's the index.html in your preferred browser.
		..._run()				- Compresses the library and open's the index.html in your preferred browser.
	-->
	<target name="LIBRARY_JS_createWorkspace()" depends="_createWorkspace()">
		<mkdir dir="${LIBRARY_JS_SRC_DIR}"/>
	</target>
	<target name="LIBRARY_JS_concatenate()" depends="LIBRARY_JS_createWorkspace()">
		<antcall target="__concatenate()">
			<param name="inputFileListID" value="LIBRARY_JS_FILE_LIST"/>
			<param name="inputFileSetID" value="LIBRARY_JS_FILE_SET"/>
			<param name="outputFile" value="${LIBRARY_JS_BIN_DIR}/${LIBRARY_JS_BIN_FILE_NAME}"/>
		</antcall>
	</target>
	<target name="LIBRARY_JS_compress()" depends="LIBRARY_JS_concatenate()">
		<antcall target="__compress()">
			<param name="workDir" value="${LIBRARY_JS_BIN_DIR}"/>
			<param name="inputFile" value="${LIBRARY_JS_BIN_FILE_NAME}"/>
			<param name="outputFile" value="${LIBRARY_JS_BIN_MIN_FILE_NAME}"/>
		</antcall>
	</target>
	<target name="LIBRARY_JS_openBrowser()">
		<antcall target="__openBrowser()">
			<param name="browserName" value="${LIBRARY_JS_RUN_BROWSER_NAME}"/>
			<param name="targetFile" value="${basedir}/${BIN_DIR}/${LIBRARY_JS_RUN_FILE_NAME}"/>
		</antcall>
	</target>
	<target name="LIBRARY_JS_run()">
		<antcall target="LIBRARY_JS_compress()"/>
		<antcall target="LIBRARY_JS_openBrowser()"/>
	</target>
 
</project>

Notice that it’s possible to have multiple libraries within a single build.xml. Simply use different prefixes!

In the end I found most of the core informations at http://www.samaxes.com and http://www.darronschall.com/weblog. Thank you guys! And of course the ANT and YUICompressor manuals accessible through Google ;).

And now I have two question:

  • Will you tell me your preferred editor?
  • Will you give me a hint according to code organization?

Please.