Showing posts with label code. Show all posts
Showing posts with label code. Show all posts

Saturday, 21 April 2012

AppCode Delegate Pattern Live Template

Here's a Live Template to generate a common delegate pattern in AppCode, JetBrain's Objective-C IDE.

Thursday, 5 April 2012

Get version number of live app on App Store in Phonegap app

A nice feature you might want to add to your iOS app is to notify the user when a new version is available on the App Store. Of course the user can go to the App Store manually and install the update, but we've noticed quite a few users don't do this very often. Some carefully-judged prompting is bound to help.

It turns out there's an API that Apple run that can return, in a handy JSON or JSONP format, the metadata for a particular app on the app store. This includes both the version number of the app, and even better, the 'what's new in this version' text.

You can use this information on your app's startup to check if a new version is available, and tell the user what's in it, and then finally, link them to the update page for the app.

Here's some bare-bones bits of example code to get you going. You'll need to know your app's Apple ID (e.g. 387022138). You can find this in the iTunes Connect interface). I'm also assuming that you're using jQuery or Zepto in your app.

The below also works in the browser.

// Alert the version number and release notes of an app.

    $.ajax({
        url:"http://itunes.apple.com/lookup?id=<APPLE ID HERE>&callback=?",
        success:function(data) {
            if (data && data.results && data.results.length) {
                alert(data.results[0].version);
                alert(data.results[0].releaseNotes);
            }
        }
    });

Here's the full list of data you might want to use:
Screengrab showing properties of the response from the API call. Click to view full size.


To open iTunes looking at your App's update page open the following URL:

itms-apps://phobos.apple.com/WebObjects/MZStore.woa/wa/viewSoftwareUpdate?id=<APPLE ID HERE>&mt=8









Tuesday, 28 February 2012

How to link to unreleased App Store apps


For a client I'm making an app that comes in 'full' and 'lite' versions. The client wanted to have a button in the lite version that opens the full version in the App Store on the device.  Now this was a little bit tricky because both the full and lite versions were in progress and hadn't been released yet. They didn't even have names.. I needed to release both apps at the same time so testing the link worked seemed impossible.

The solution was to have the 'upgrade' button link to simply open an html page on a site I own in Safari on the device. Doing this meant I could alter what the link actually did at any time.

Once the client had set up the apps in iTune Connect, I asked for the Apple ID of the full version of the apps (shown in iTunes connect on the App Info Page).

I then updated the HTML page that was opened by the app to contain the following (replacing the 123456789 with the real app ID of course).

<script type="text/javascript">
window.location = "itms-apps://itunes.apple.com/app/id123456789?mt=8"
</script>

Now when that button is pressed, Safari flashes up briefly (but doesn't actually display the page) and then the App Store app opens and displays the app. Voila!

Ah, I hear you say. Can't I just use the app name rather than the Apple ID? Well, yes you can. Here's a load of different ways of linking to apps on the app store.

For me though, these options weren't so good as my solution because:
  • There a fairly fiddly way the actual name is converted to a url-friendly name. Easy to get wrong.
  • My app names weren't finalized at the time of writing anyway.
  • If the client changes the name of the app, the link will break.


Thursday, 10 November 2011

Edit AndroidManifest.xml in Android Phonegap Build App

Phonegap build is an awesome service that allows you to convert a bunch of HTML files into actual mobile apps for iPhone, Android, Blackberry, and more. It's great, so great in fact that Adobe recently bought the company behind it (Nitobi).

You can read more about the details at their site, I won't repeat it all here.

I've been playing around with the Android version of this for a client project I've been working on, and came up with a bit of a problem.  There's a file called AndroidManifest.xml that every Android app includes that sets up loads of important stuff such as the intent filters and the permissions needed to run the app.

Phonegap build allows you to change some of these settings using it's config.xml system.  But for other settings you are, it seems, stuck.  I needed to change the intent filter for one of my activities, and there was no way that the config.xml could do this. I posted on their forums and the very helpful guy said they may add the feature in a future release.

In the meantime though, I found another way.

It's possible to decompile the apk file that you download from Phonegap Build, change the AndroidManifest.xml file, re-compile, re-sign, and install it onto your device.

Getting the tools we need
I'm assuming that you have the Android SDK installed, if not install it and follow the instructions to put the build tools into your path.
First you need to download apktool.  This great tool allows you to decode and re-encode apk files. Download it and follow the instructions to install for your platform.
We're also going to use:

  • jarsigner - part of the Java JVN (I have a binary at /usr/bin/jarsigner) 
  • adb - part of the Android SDK installation (my binary is at ~/android/android-sdk-mac_x86/platform-tools/adb)
Extracting the APK file

  • Download the apk file for your app from Phonegap build.
  • Open up the terminal and type (substituting your folders for path/to and filenames where appropriate)
apktool d path/to/yourApp.apk path/to/output-folder
  • This will extract the contents of the app into the folder output-folder.
  • The AndroidManifest.xml file will be at the root of that folder.
Now you can make whatever changes to the AndroidManifest you need (setting intent filters etc).

Re-building the APK file

  • Once you've finished making your changes, you can rebuild the APK file from the contents of the output-folder
apktool b path/to/output-folder path/to/yourAppV2.apk 
Re-signing the APK
If you try to install the new apk file you'll find that it fails with an invalid certificate error.
This is because apktool doesn't re-sign the apk.  It's pretty easy to re-sign the APK with the development certificate (which is what Eclipse uses when making APKs for testing).

Here's how:
jarsigner -verbose -keystore ~/.android/debug.keystore path/to/yourAppV2.apk androiddebugkey
By default the development certificate is in the ~/.android folder. When you run this, it'll ask for a passphrase - use android.

Your APK is now ready for installation!
adb install path/to/yourAppV2.apk

Wednesday, 27 July 2011

Knockout.js: Making an observable that fits into an undo system

Knockout is a JavaScript library that helps you to create rich, responsive display and editor user interfaces with a clean underlying data model. Any time you have sections of UI that update dynamically (e.g., changing depending on the user’s actions or when an external data source changes), KO can help you implement it more simply and maintainably.
(from the homepage)

I'm using knockout fairly extensively thoughout a new app I'm building. This app has a full undo / redo system for all user actions, and this caused a bit of a problem with knockout. By default knockout will immediately update the data model with the results of user actions. Furthermore, when it notifies you of changes to values, it only tells you the value that the data has changed to - and it's too late to find out what the value was before.

Of course there are hacky ways around this, but a much better solution suggested by (someone on a mailing list I need to look up again) was to create a 'subclass' of observable that allows access to the previous value of a variable.

Here's the code:
$("document").ready(function() {

/**
* An observable that allows access to the last value. Useful for undo
* purposes.
*
* Usage:
* viewModel.foobar = ko.lastValueObservable();
* viewModel.foobar.subscribe(function(val) { alert("foobar changed from " + viewModel.foobar.lastValue() + " to " + val); } );
*/
ko.lastValueObservable = function(initValue) {
var _value = ko.observable(initValue);
var _lastValue = initValue;

var result = ko.dependentObservable({
// Just return the real value
read: function() {
return _value();
},

write: function(newValue) {
// store the last value before writing...
_lastValue = _value();

_value(newValue);
}
});

// Add a new function to return the last value
result.lastValue= function() {
return _lastValue;
};

return result;
};
});

Tuesday, 26 July 2011

HTML5: Transferring localStorage data between machines & browsers

An app I've been building recently makes extensive use of the HTML5 localStorage API, which is beautifully simple and just works. This system stores a limited (normally maximum 5MB) of data as name / value string pairs on a per-domain basis in the user's browser. Check out my other post on localStorage for more details

I came across an issue today where a colleague had saved some data locally that I needed to use, and it seemed there wasn't an easy way to transfer the data across. Luckily a few minutes of fiddling showed it was in fact super-easy.

Just write out the total contents of the localStorage file as a JSON string, email or otherwise transfer it to the other computer, and then read it back into localStorage.

Here's the code. I ran this by opening the JavaScript console in my browser and just typed the commands
// Write out the whole contents of the localStorage..
JSON.stringify(localStorage);
// Copy the output..

// Now on the other computer, read it in again..

var storage = !! PASTE THE DATA HERE !!;
for (var name in storage) { localStorage.setItem(name, storage[name] ); }

Monday, 25 July 2011

Creating an XML-to-JSONP converter using node.js

A client of ours had a simple request to include an RSS feed in a mobile website and have it dynamically update. The obvious solution to this was to use AJAX, parse the XML and generate the required HTML.
However the RSS feed was on a different domain to the website itself, so we quickly run into 'traditional' cross-domain restrictions. Simply put, you can only AJAX data from the same domain that your website is served from.

Now, the JSONP format is widely considered a great way to get around this issue. The way this works is that a script tag is dynamically inserted into the HTML document. The src attribute of the script tag is set to the data source, e.g.

<script src="http://www.example.com/foo.js?callback=myCallback" type="application/javascript">


The 'callback' query string parameter is used to specify the name of a JavaScript function that will be executed when the data is downloaded. This function will be passed the data itself as the first parameter. E.g. in this example, we'd create a JavaScript function like this:

function myCallback(data) { console.log(data); }


The data parameter would then be a JavaScript object containing the data that was loaded. Pretty neat, I think you'll agree!

If you use the inspector in your browser to see what's actually downloaded, you'll see something like this:

myCallback( {  a:1, b:2, c:3 } );


It's simply the JSON-encoded data wrapped in a call to the function we specified in the query string parameter earlier on.

What we're going to do in the remainder of this article is to create a node.js server that we pass the URL of an RSS feed in & the name of the callback, and we get a JSONP response back again.

Now, going back to the requirements, our client asked for a RSS feed reader. Of course RSS is an XML-based format and not a JSON format so we're going to need to convert XML into JSON.

Happily, there's a module for doing exactly this called xml2js available via npm, the node package manager.

sudo npm install -g xml2js


The -g call tells npm to install the package globally - so it's available to all node apps.

We're also going to use journey, a node module that simplifies mapping request urls to js functions.

sudo npm install -g journey


Here's the code that sets up the server itself and creates the mapping for the url /xml2jsonp. This is the js file that should be run using the node binary.

server.js


require.paths.push('/usr/local/lib/node_modules'); // my installation required this for any modules to load.. your directory may be different..
var http = require('http'),
journey = require('journey'),
xml2Jsonp = require('./xml2jsonp'), // this is the file containing the implementation of the actual conversion.. We'll create this in a bit!

//======================================================================
// Create the routing table request => handling function
//======================================================================
var router = new (journey.Router);
router.map(function () {

// Send a welcome message if you hit the root
//==================================================================
this.root.bind(function (req, res) {
res.send("Welcome")
});

// map /xml2jsonp
//==================================================================
this.get("/xml2jsonp").bind(function (req, res, params) {

// the 'params' parameter is filled in with the query string as an object, provided your client includes a query string in the request url (?a=b&c=d)

// the xml2jsonp.get function is defined in the xml2jsonp.js file defined later on.
// we get out the 'url' and 'callback' parameters from the query string and pass them in.
xml2Jsonp.get(res, params.url, params.callback, function(result) {
// we've got back the text! use res.send to respond to our client!
res.send(200, {'Content-Type': 'application/javascript'}, result);
});
});
});

//======================================================================
// Set up the server
//======================================================================
http.createServer(
function (req, res) {

// This section is boilerplate code from the journey docs to read the request from the client and pass it onto the routing table above.
var body = "";

req.addListener('data', function (chunk) {
body += chunk
});
req.addListener('end', function () {
// Dispatch the request to the router
router.handle(req, body, function (result) {
res.writeHead(result.status, result.headers);
res.end(result.body);
});
});

}).listen(80);

console.log('Server running..');


Now here's the interesting bit, the implementation of the actual XML 2 JSONP conversion.

xml2jsonp.js

var
url = require("url"),
xml2js = require("xml2js"),
http = require("http");

/**
* Downloads the XML file, converts it to JSONP, and calls the callback function with the result.
*
* @param res the http response object
* @param xmlUrl the url of the xml we want to download
* @param callbackFunctionName the name of the JSONP callback function
* @param callback the function to run once this call succeeds. The first parameter will be the result as a string.
*/
exports.get = function(res, xmlUrl, callbackFunctionName, callback) {

if (!xmlUrl || !callbackFunctionName) {
callback(callbackFunctionName + "({error:'invalid parameters'}");
return;
}

// get query passed in..
var urlIn = url.parse(xmlUrl);

if (!callbackFunctionName) {
callbackFunctionName = "";
}

var options = {
host: urlIn.hostname,
port: 80,
path: urlIn.pathname,
"user-agent": "node.js" // some web servers require a user agent string..
};

// download the XML as a string..
downloadTextFile(options,
function success(data) {

// use the xml2js library to parse into a JS object.
var parser = new xml2js.Parser();
parser.addListener('end', function(result) {
// Use JSON.stringify to convert back into the JSON text we'll return. Note that in a production environment you'll
// probably want to omit the null, 4 part on this call. It makes the output more human-readable but increases the file size.
callback(callbackFunctionName + "(" + JSON.stringify(result, null, 4) + ");");
// Note here we're wrapping the JSON in a call to the JSONP callback function - this is the key part of the JSONP format!
});
parser.parseString(data);
},
function error(msg, e) {
// Note: because we're in an async environment it's important that we call the callback even if things fail, else the client will hang around waiting for a response.
callback(callbackFunctionName + '({error:' + msg + '})');
});
};

/**
* Call to download a text file from the specified url
* @param options {host:[host name],port:[port],path:[path] }
* @param success
* @param error
*/
function downloadTextFile(options, success, error) {
http.get(options,
function(res) {

console.log("Got response: " + res.statusCode);

var data = "";

res
.on('data', function (chunk) {
data += chunk;
})
.on('end', function() {
success(data)
});

})
.on('error', function(e) {
var msg = "Failed to download text from " + options.host + options.path + " " + e.message;
console.log(msg);
error(msg, e);
});
}


I'm hosting the above code on a joyent node.js smartmachine a (currently at the time of writing) free service from joyent. I thoroughly recommend trying it out if you're looking for free node.js hosting. It's a little fiddly getting started, but if you follow the docs at http://wiki.joyent.com/display/node/Getting+Started+with+a+Node.js+SmartMachine and ignore the many obsolete documents I kept finding on google then you should be ok.

Update: I've added this project to github, and updated it a little to also provide json to jsonp conversion.
https://github.com/benvium/nodejs-xml2jsonp

Monday, 29 November 2010

Simple method of horizontally centering a DIV of unknown width

Centering in CSS is always a pain. If you are just centering text, or you are using (god forbid) IE6 then you can simply use the text-align property. If you are centering something of a known width, it's easy too, just set margin-left and margin-right to 'auto' and all is good. However, if you don't know how wide the element is going to be, for example, you have an element that expands to fit some text pulled from a database, it's a bit tricker.

There are loads of hacks around of varying reliability and complexity, but the simplest method I have found yet is the following.

  • The outer div is set to text-align: center

  • The inner div is set to display: inline-block



That's it. The fact that the inner div is set to inline-block means it picks up its parent's setting of text-align to center itself. Is kinda wierd, but works on all the browsers I've so far tested.

Wednesday, 17 November 2010

Zebra Striping Tables with Pure CSS

I've seen quite a lot of tutorials for this effect that use Javascript, particularly jquery.
You can do it with pure CSS though using the handy :nth-child(odd) and :nth-child(even) selectors in CSS3.
This is a fragment from a javascript errors dialog I created for an online code editor for Calvium.com

Tuesday, 14 September 2010

iPhone SDK: Running a selector after a delay..

Quite often I've needed to run a selector after a certain time interval. Usually what I've done is the following:

[NSTimer scheduledTimerWithTimeInterval:0.0 target:self selector:@selector(doIt:) userInfo:nil repeats:YES];


A shorter and slightly easier method is a selector on NSObject (now that's a base class with a lot of methods if ever I've seen one!)

[self performSelector:@selector(doit) withObject:nil afterDelay:1.0];


If you set afterDelay to be 0.0, you have a quick and easy way of making a selector run once the current run loop has completed.

Thursday, 9 September 2010

iPhone SDK: What NSURL methods actually return

Apple's NSURL documentation has very little detail on what exactly the various NSURL methods actually return. It just says unhelpful things like 'returns the path' - what path?

Here's an example NSURL, and the output you actually get when using some of the methods.

NSURL* url = [NSURL URLWithString:@"http://username:password@www.example.com:888/folder1/folder2/filename.html?query1=value&query2=value#anchor1"];
[url scheme] -> "http"
[url user] -> "username"
[url password] -> "password"
[url host] -> "www.example.com"
[url port] -> "888"
[url path] -> "/folder1/folder2/filename.html"
[url lastPathComponent] -> "filename.html"
[url pathExtension] -> "html"
[url fragment] -> "anchor1"
[url absoluteString] -> "http://username:password@www.example.com:888/folder1/folder2/filename.html?query1=value&query2=value#anchor1"

Monday, 6 September 2010

HTML5 Local Storage

There are a number of different technologies for storing data locally in the web client. Cookies, SQLite (in those browsers that support it), the offline cache manifest, and also 'Local Storage'. If you simply want to store some name-value pairs, such as user config data, local storage is by far the simplest option. Up until today we had been using Brian le Roux's lawnchair for local storage, which we used in the mode where it wraps SQLite (specifically the Webkit version). The fact that all requests were asynchronous, while nice in most situations, didn't fit our model. So I've switched to localStorage instead, which has the added bonus of being ridiculously simple.

var text = localStorage.getItem("foo");
if (text === "hello") {
alert("You've been here before!");
} else {
localStorage.setItem("foo", "hello");
}


The system doesn't support storing javascript objects or array though. Happily, this is easily fixed with a combination of JSON.stringify and JSON.parse to move the object to and from a string representation.

var st = localStorage.getItem("foo");
var ob = JSON.parse(st);
if (ob && ob.text === "hello") {
alert("You've been here before!");
} else {
var toSave = { text:"hello", other:[1,2,3] };
localStorage.setItem("foo", JSON.stringify( toSave ) );
}


You can clear the contents by using the localStorage.clear() method.

There's a simple demo HTML5 local storage in action available here

Tuesday, 31 August 2010

Bourne Shell Script Commands

Here's a bunch of bourne shell script commands I use on a regular basis
Using variables
VARNAME=3
echo The value is $VARNAME


Concatenating a list of text files
This is most useful when the list of files is long.

files=(
a.txt
b.txt
c.txt)
for p in "${files[@]}"; do cat "$p"; done > output.txt


Check to see if file exists

if [ -f filename ];
then
echo "OK"
else
echo "ERROR: File not found"
exit
fi


Note that the spaces before the -f and after the filename are essential. Can be irritating if you forget!

Check to see if a directory exists

if [ -d directoryName ];
then
echo "Directory OK"
else
echo "ERROR: Can't find directory"
exit
fi


Checking a script is running on a particular machine
if [ "`hostname`" == "myhost.calvium.com" ];
then
echo "this is the right server";
fi;


Note that the spaces around the [, == and ] are ESSENTIAL. This gets me every time..

Wednesday, 25 August 2010

Pop up a list of options - iPhone SDK

Here's a quick snippet for popping up a list of options using the UIActionSheet in iPhone OS.

What's slightly confusing is that the index of the 'cancelButton' is 1, and 'destructiveButton' is 0, even though 'cancelButton' is listed first in the initWithTitle selector. This gets me each time!


// place this code in a UIViewController subclass

- (void)debugButtonPressed:(id)sender
{
UIActionSheet* action = [[UIActionSheet alloc]
initWithTitle:@"Menu"
delegate:self
cancelButtonTitle:@"Continue"
destructiveButtonTitle:@"Quit"
otherButtonTitles:@"Debug",nil ];
[action showInView:self.view];
[action release];
}

- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex {
switch (buttonIndex) {
case 0: // Quit
NSLog(@"Quit");
break;
case 1: // Continue
NSLog(@"Continue");
break;
case 2: // Debug
NSLog(@"Debug");
break;
}
}

Converting between rgb and hex colors in javascript

Recently I've need to be able to convert between rgb and hex colors.

Here's the javascript functions I used.

function hex2Rgba(hexColor, opacity) {
// Only accepts #123456 format for now
if (color[0] === '#' && color.length === 7) {

var r,g,b;
r = parseInt(color.substring(1,3), 16);
g = parseInt(color.substring(3,5), 16);
b = parseInt(color.substring(5,7), 16);

//
return "rgba("+r+","+g+","+b+","+opacity+")";
}
return null;
}

Obviously this is pretty basic, and doesn't support the #123 format.

For converting in the other direction I found this code on stackoverflow..

function rgb2hex(rgb) {
rgb = rgb.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/);
function hex(x) {
return ("0" + parseInt(x).toString(16)).slice(-2);
}
return "#" + hex(rgb[1]) + hex(rgb[2]) + hex(rgb[3]);
}

Tuesday, 24 August 2010

Profiling Javascript Performance in Safari 5

We're currently in the process of building some pretty large and complex javascript apps here at Calvium. One of the parts of the system was running rather slower than it should have, so I took it upon myself to find out why. It turned out that all of the tools I needed to track down the performance problems were available right in the browser I was using - Safari 5 (and also Chrome, which shares the same engine) has an excellent Javascript Profiler available in the Inspector.
Using it is as simple as opening the page you want to profile, and in Safari, going to Develop / Web Inspector.
Once there, click the Profiles button and select 'enable profiling for this session'.

You can control the profiler by clicking the black circular 'record' button at the bottom left. When you stop recording you'll get a breakdown of the percentage of the time spent in each function. This can be handy, but often what you really want is to profile a very specific part of your app, for instance the process kicked off when a particular button is pressed.

Luckily there are some console commands you can insert into your Javascript to start and stop the profiler automatically.

Here's an extremely simple example. Here the goal is to find out which function is causing the slow operation of this script - is it slowFunction, or fastFunction? Guesses on a postcard please..

<html>
<head>
<script type="text/javascript">
setTimeout(function() {
// start profiling
console.profile("why so slow?");
slowFunction();
fastFunction();
// stop profiling
console.profileEnd();
}, 2000);

function slowFunction() {
for(var i=0; i < 100; i++) { stuff();}
}

function stuff() {
var count = 0;
for (var i=0; i < 1000; i++) {
for (var j=0; j < 1000; j++) {
count = count + 5;
}
}
}

function fastFunction() { var j = 23; }
</script>
</head>
</html>


When you open this page in Safari and run the profiler you get the following output:



The entries in the 'self' column shows the percentage of the profile time the execution of that particular function took - excluding time taken executing other sub-function calls made in that function. Here we can see that 'slowFunction' only took 0.02% itself, but 'stuff' took 99.93%. Looks like 'slowFunction' itself doesn't need any optimisation.
The 'total' column shows the time including any sub-function calls made, and here we can see that 'slowFunction' takes a massive 99.95% of the program execution. We can discover why this is easily using the Profiler tool. (I know its obvious in this case, but imagine with a huge complex app!).

You can change the way the results are displayed from the default 'Heavy (bottom up)', which shows each function individually, to the 'Tree (Top Down)' view. The tree view shows the call stack, and the time taken for each function along the way. Using this we can quickly drill down to discover that 'slowFunction' calls 'stuff', and that's why it takes so long. You can also see how many times each function is run.

Using this technique, you can easily discover the parts of your app that need optimisation. I manage to speed up the screen loading of a part of our application by a factor of six by identifying one particularly slow function that was called a huge number of times.

There's more info on using the 'console' javascript API on the apple developer site

Using Lawnchair to save config data in a web app

Lawnchair, created by PhoneGap's Brian Leroux, is a simple wrapper to various local storage technologies such as the HTML5 local storage system or even cookies. You can store and retrieve Javascript objects or arrays with just a few lines of code.

We use Lawnchair quite extensively at Calvium. We currently have a few web apps for internal use designed to be run directly from the file system (I'll probably write some more blogs about this subject as it is quite an interesting problem). This is great as it means the web app can read local files using Ajax calls, but due to cross-domain restrictions it cannot send anything to a server. So we can't store anything such as user preferences on the server, so we need to store it locally.
This is where Lawnchair comes in..

The docs don't make a few things very clear, such as how you specify the 'adaptor' to use. This is how you tell the system where data should be stored - cookie, database etc. Here's where my example hopefully may help!



Here's an excerpt of the HTML file. You need to include Lawnchair.js (obviously), and LawnchairAdaptorHelpers.js, and the adaptor you wish to use (in this case WebkitSQLiteAdaptor).

<html>
<head>
   <!--lawnchair-->
    <script type="text/javascript" src="js/libs/Lawnchair.js"></script>
    <script type="text/javascript" src="js/libs/LawnchairAdaptorHelpers.js"></script>
    <script type="text/javascript" src="js/libs/WebkitSQLiteAdaptor.js"></script>
</head>
<body>
   <input type="text" id="myConfigInput"/>
</body>
</html>


(Note: here's a really handy little site for converting HTML example code into a format you can copy into blogger)

Here's the javascript..
 // create the lawnchair object, telling it to create a table called myapp, using the 'WebkitSQLListAdaptor'.
var _lawnchair = new Lawnchair({table: "myapp", adaptor: "webkit"});

// create the javascript object with my default data in it.
var _config = {"userSetting":"", "key": "config"};
// key is the string used to get the data out in the 'get' call below.

// Load the config
_lawnchair.get("config", function(r) {

// If there isn't an item in the database with the 'config' key set, you'll get null. Make sure we don't overwrite the default!
if (r !== null) {
_log("LoadFileDialog: config loaded.");

// TODO: might be better to merge..
_config = r;

// Update interface with the values we've loaded.
}
});

// Note that the get function is asynchronous, so the get may not have completed by this point.

Thursday, 19 August 2010

Conditional Compilation in HTML

I'm currently building a large and fairly complicated app using JQuery'd Javascript, HTML and CSS. To keep the javascript code manageable and maintainable, I've arranged everything into classes, with one class per javascript file, and the filename of the JS file matching the class name. CSS files quickly become unwieldy too, so I have a similar approach with these.
This makes remembering which file contains the code I want very easy, but of course it means my app has dozens of JS and CSS files. As all web developers know - lots of files means lots of requests to the server means a sloooow site.

So, obviously, the answer is to concatenate all your JS files into one long file, and then use a tool like Yahoo's Compressor to shrink the javascript file into a fraction of it's former size.

I make a shell script like this:

jsfiles=(
trees.js
toast.js
armchair.js
)

for p in "${jsfiles[@]}"; do cat "$p"; done > code.js

java -jar yuicompressor.jar -o release/code-min.js codejs


So this is all great, but it left me with an issue. While I'm developing and debugging, it's essential my page links to the dozens of original javascript files so I can trace errors back to the source files, and set breakpoints more easily. But I need a version of the HTML page which links only to the minified version of the javascript and css files.. For a while I did keep two versions of the HTML, but as the page got more complicated keeping two files up to date wasn't practical.

My solution was to implement a basic 'conditional complication' system - where I could mark parts of the HTML file as being debug-only, and other parts as release-only. I knocked together a build script that would take the original HTML, knock out the debug parts, and uncomment the release only sections.

<head>
    <!--DEBUG-->
    <script type="text/javascript" src="js/trees.js"></script>
    <script type="text/javascript" src="js/toast.js"></script>
    <script type="text/javascript" src="js/armchair.js"></script>
    <link rel="stylesheet" type="text/css" href="css/trees.css"/>
    <link rel="stylesheet" type="text/css" href="css/toast.css"/>
    <link rel="stylesheet" type="text/css" href="css/armchair.css"/>
    <!--ENDDEBUG-->

    <!--RELEASE
    <script type="text/javascript" src="code-min.js"></script>
    <link rel="stylesheet" type="text/css" href="style-min.css"/>
    ENDRELEASE-->
</head>
<body>
</body>


Here's an example. Note that the release section is commented out - this means I can open the page directly and be in 'debug mode' with full access to all the script and css files via my browser's inspector.

Then it's a case of running a simple regex script (I used 'jsc', the javascript script engine built into Safari) to make the release version.

var i = arguments[0]; // the first parameter - e.g. the content of the HTML file

// Replace debug block with nothing..
i = i.replace(/<!--DEBUG[\s\S]*?ENDDEBUG-->/g, ""); // /g ensures EVERY match is replaced, not just the first. *? means use the smallest match (default is largest)

// Remove comment around production
i = i.replace(/<!--RELEASE([\s\S]*?)ENDRELEASE-->/g, "$1");


Simple hey?

Syntax Highlighting in Blogger Posts

Code examples on web pages work so much better if you have syntax highlighting. Blogger.com doesn't have this feature enabled by default but it's easy to add.

Without..

function foo(a,b,c) {
return "Ugly!"
}


With..
function foo(a,b,c) {
return "Good hey?"
}


Here's the tutorial I used. Thanks Luka Marinko.

Equivalent to foreach in javascript

I happily developed in C# for a number of years during my time at Hewlett-Packard Labs. One of my favourite basic language features was the foreach function, for quickly iterating through the items in any type of collection.

foreach (MyObject ob in myObjects) {
ob.doSomething();
}


I started developing seriously in javascript a few months back, and eventually discovered a similar function..

var list = ["a","b","c"];
for( var i in list) {
console.log( list[i] );
}
// outputs a b c


Now it's not quite so neat as the C# version, as you get the index of the element in the array, rather than the element itself. This means you need to use the index to get the element out of the array - the array[i] part here.

You can also iterate all the properties of an object too.
var ob = {a:1,b:2,c:3};
for( var name in ob) {
alert( ob[name] );
}
// outputs 1 2 3

Run it here

However, there's a nasty gotcha with this approach. If someone anywhere in your javascript app, or any of the libraries it uses, or any code eval'd at runtime adds any properties or functions to 'Object' or 'Array' then your for in loop will return that property too!
This is of course, insane, but it's just how Javascript works. There's a great example of this problem if you use jsfiddle, a fab site for testing ideas using the 'holy trinity' of HTML, CSS, and Javascript. The libraries it uses add a whole bunch of stuff to Array including their own 'foreach' function.

Click here to see

Happily, there's a simple solution - just use the 'hasOwnProperty' method on an object to check that a property is not one inherited from the base class.

var foo = [1,2,3];
for( var i in foo) {
if (foo.hasOwnProperty(i)) {
alert( foo[i] );
}
}​


Click here to see

This is just one of the odd things in Javascript that can mess you around quite badly if you don't know about it!