Good Better Best

Formatting Strings the Correct Way using an old Friend “printf”

In developing an app you will need to format a string at least once – even if you do not realise that is what you are doing.  You may only ever need the most basic of formatting abilities – but chances are after time your needs will expand and the complexity of your string formatting will increase.

So what are the problems and how can we improve the situation?

Most people are still here

This is a very simple example of string;

var str = "Hello Malcolm Hollingsworth";
Ti.API.info(str);
// Hello Malcolm Hollingsworth

As I say; that was a very simple example, however most of the time you will need to combine a string with a variable before displaying it.

var strName = "Malcolm Hollingsworth";
var str = "Hello " + strName;
Ti.API.info(str);
// Hello Malcolm Hollingsworth

So now we have a variable added to the end of a fixed string – success. As I said before though things often get more complicated so we will use a variation on an Appcelerator example to make it a little more real world.

var forename = 'Malcolm';
var number = 13;
var message = 'Welcome, ' + forename + '! You are visitor number ' + number + ');
Ti.API.info(message);
// Welcome, Malcolm you are visitor number 13

So that was a little more complicated, not much but you can see how things will become more complicated as your app becomes moves forwards.

You might to think about using this

So how can we make this easier? Titanium has a built in String Format method; I will change the code we just used so it using the String.format method.

var forename = 'Malcolm';
var number = 13;
var message = String.format('Welcome, %s! You are visitor number %d', forename, number);
Ti.API.info(message);
// Welcome, Malcolm you are visitor number 13

That is the same end result – but the code appears a bit cleaner. It uses %s and %d which we have not seen before, so I will take a moment to explain what they are there for.  Notice the use of the variables we had defined within the method itself.

%s represents a string and %d represents a numerical value (digits). You thought that was going to be hard didn’t you?

However chances are – your app will end up needing to format more complicated string than those we have seen so far, so where do we go from here?

var forename = 'Malcolm';
var number = 13;
var prize = 'Balloon';
var message = String.format('Welcome, %s! You are visitor number %d as visitor number %d you get a %s', forename, number, number, prize);
Ti.API.info(message);
// Welcome, Malcolm! You are visitor number 13 as visitor number 13 you get a balloon

So we get the expected output we want – but for the ones with a keen eye you will notice there appears to be something redundant right there in the middle.

  • As we used the visitor number twice within the string – we have to add the value in twice.
  • If we change the order of the string – those variables will also need to be reordered to match the new string order.

So now we have something designed to make our life easier that will often make it harder – I am not a fan of that so we need a better solution.

This is what you should be doing

So here are a list of goals our better solution must provide;

  • Display a string with variables
  • Be able to use variables more than once is required

That seems easy enough – but how do we achieve it? The solution is to go back to a time when other languages had what we need built in and re-build it. The great news – I am NOT going to tell you how this works inside – as it really does not matter. It works and that is all you need to worry about.

String.prototype.printf = function (obj) {
    var useArguments = false;
    var _arguments = arguments;
    var i = -1;
    if (typeof _arguments[0] == "string") {
        useArguments = true;
    }
    if (obj instanceof Array || useArguments) {
        return this.replace(/\%s/g, function (a, b) {
            i++;
            if (useArguments) {
                if (typeof _arguments[i] == 'string') {
                    return _arguments[i];
                } else {
                    throw new Error("Arguments element is an invalid type");
                }
            }
            return obj[i];
        });
    } else {
        return this.replace(/{([^{}]*)}/g, function (a, b) {
            var r = obj[b];
            return typeof r === 'string' || typeof r === 'number' ? r : a;
        });
    }
};

In short; this adds a new method to all string you create and use within your app. To use it though we need to place this code somewhere so that your whole app can take advantage of it.

  • Alloy
    {appname}/app/alloy.js
  • Non-Alloy
    {appname}/resources/app.js

Once pasted in the correct file – our app can then use the method with any string. So here are those examples again – but using the new method;

Simple previous example

var strName = "Malcolm Hollingsworth";
var str = "Hello {fullname}";
Ti.API.info(str.printf({
  fullname: strName
}));

More detailed previous example

var forename = 'Malcolm';
var number = 13;
var prize = 'Balloon';
var message = "Welcome, {name}! you are visitor number {visitor} as visitor number {visitor} you get a {prize}";
Ti.API.info(message.printf({
  name: forename,
  visitor: number,
  prize: prize
}));
// Welcome, Malcolm! you are visitor number 13 as visitor number 13 you get a Balloon

You will notice the formatting is performed right at the end and passed as a very standard JSON associative array.

message.printf({
  name: forename,
  visitor: number,
  prize: prize
})

With message already being assigned the string we wish to display the object of values can be passed through printf to swap out the place-holders. You will hopefully note that the key name matches the place-holder and the value (variable) provides the value to swap out. Those with a keen eye will notice we no longer need to provide the variable number twice even though it is still used twice within the string.

Some other cool things;

// assume the previous code example variables exist here

// the order does NOT matter
Ti.API.info(message.printf({
  prize: prize,
  visitor: number,
  name: forename
}));

// you can define the object before using it
var data = {
  prize: prize,
  visitor: number,
  name: forename
};
Ti.API.info(message.printf(data));

// you can build the object up over time - very useful for strings
// that are determined based on several factors within a block of code
// you can define the object before using it
var data = {};
data.prize = 'Balloon';
data['visitor'] = 13;
data.name = 'Malcolm';
Ti.API.info(message.printf(data));

What now?

Go have fun and see how you can simplify string formatting in your own app.

Owner of Core 13 Ltd a UK based mobile app development agency. Malcolm is affectionately known by Appcelerator and the big wide world as "The Oracle". With 25 years commercial software development experience from desktop apps to web sites for single users to large enterprises.\n You may have seen some of the many contributions provided within the Titanium Q&A.


Comments

  • axmontgomery

    I can’t say I’ve ever seen a printf that accepts named placeholders before. Leaving aside the lack of stream output, Traditional printf formatting works exactly like your String.format example.

    That’s not so say that the idea of named placeholders isn’t a good one… curly bracket-based substitution is common (URI Templates, Alloy Bindings…) and should feel immediately comfortable to many, but it is certainly not what I would expect from a method named printf.

  • Malcolm Hollingsworth Post author

    Sorry that you find the article lacking. Whilst originally printf only used basic substitution; many languages have evolved the choices available in the value parameters and what variable types can be used.

    The routine above does actually support each of these options.

    “{f} {b}”.printf({f: “foo”, b: “bar”});
    “%s %s”.printf([“foo”, “bar”]);
    “%s %s”.printf(“foo”, “bar”);

    However as this article is designed to assist people by understanding how to solve the issues referred to in the article itself. I did say “…I am NOT going to tell you how this works inside…”.

    So whilst I welcome your comment; printf has evolved in different languages – some of which have added this ability. I could have called the method “bob” but that would not have been correct.

    Given the object nature of JavaScript the article shows exactly how to take advantage of those two things.

    Again – I am sorry this article disappointed you.

  • Sebastian

    Thanks. I Love you for that!

  • tujoworker

    Thanks, works fine for translations as well: L(‘my_string’).printf({bar: ‘bar’}) and in strings.xml -> Foo {bar}