Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at cf.Objective() 2009 (Minneapolis, MN) with:

How To Create GStrings In Javascript By Extending Core Data Types

By Ben Nadel on

This morning, I was looking at a tutorial on how to extend built-in Javacript objects by altering the Prototype of native Javacript classes. In doing this, you can add your own methods to Javascript String and Array instances (for example). The down side to this approach is that this affects every new and existing instance of the altered data types (all instances of a given type share a common prototype). This got me thinking about Javascript object extension in general, and I wondered, rather than altering the core classes, could we create new classes that simply extend the core classes.

 
 
 
 
 
 
 
 
 
 

In Groovy, there is something called a GString.

 
 
 
 
 
 
Creating Groovy-Like GStrings In Javascript Using String Class Extension. 
 
 
 

... no, not like that (get your mind out of the gutter)! A GString is a Groovy Object that acts like a String but has delayed evaluation of its content variables. Basically, you can create a string that is composed of variable place holders. Then, when you evaluate the string, those place holders get substituted for their string literal values. So, for instance, you can create the Groovy GString:

"Hey there, $name"

... where "$name" is a variable whose value is not substituted into the string until it is absolutely necessary.

So my thought was, can we extend the native Javascript String class to create our own Javascript GString-like implementation? After a little bit of trial and error, this is what I came up with:

  • <!DOCTYPE HTML>
  • <html>
  • <head>
  • <title>Creating A GString In Javascript</title>
  • <script type="text/javascript" src="jquery-1.4.1.js"></script>
  • <script type="text/javascript">
  •  
  • // I am the GString object - I allow delayed evaluation of
  • // a string based on a colleciton of name-value bindings.
  • // Ala Groovy.
  • function GString( value, bindings ){
  • // Pass the arguments off to the parent constructor
  • // in case there is any magic that the String
  • // constructor is going.
  • String.apply( this, arguments );
  •  
  • // Store the value of our string.
  • this.value = value;
  •  
  • // Set up the bindings container (this is where we
  • // will map variables names to value for our GString).
  • this.bindings = {};
  •  
  • // Add any initial bindings.
  • this.addBindings( bindings );
  • }
  •  
  •  
  • // When creating our GString, we want to extend the
  • // built-in String object. NOTE: When doing this, we
  • // have to override the toString() and valueOf()
  • // methods.
  • GString.prototype = jQuery.extend(
  •  
  • // Extend a STRING object instance.
  • new String(),
  •  
  • // Define class functions.
  • {
  •  
  • // I add the given binding to the colleciton.
  • addBinding: function( name, value ){
  • this.bindings[ name ] = value;
  •  
  • // Return this object for method chaining.
  • return( this );
  • },
  •  
  •  
  • // I add the given collection of bindings to the
  • // internal collection.
  • addBindings: function( collection ){
  • // Loop over the given collection.
  • for (var name in collection){
  •  
  • // Add each key to the current bindings.
  • this.addBinding(
  • name,
  • collection[ name ]
  • );
  •  
  • }
  •  
  • // Return this object for method chaining.
  • return( this );
  • },
  •  
  •  
  • // I apply the bindings to the current string
  • // and its placehoders.
  • applyBindings: function(){
  • var self = this;
  •  
  • // Replace the inline bindings with the ones
  • // we have stored in the bindings collection.
  • var result = this.value.replace(
  • new RegExp( "\\$\\{([\\w_.-]+)\\}", "g" ),
  • function( $0, $1 ){
  •  
  • // Check to see if the binding name
  • // exists in our binding collection.
  • if ($1 in self.bindings){
  •  
  • // Return the binding value.
  • return( self.bindings[ $1 ] );
  •  
  • } else {
  •  
  • // The binding was not found, so
  • // just return the original match.
  • return( $0 );
  •  
  • }
  • }
  • );
  •  
  • // Return the evaluted string.
  • return( result );
  • },
  •  
  •  
  • // I reset the binding collection.
  • clearBindings: function(){
  • this.bindings = {};
  •  
  • // Return this object for method chaining.
  • return( this );
  • },
  •  
  •  
  • // I return the value of the given binding.
  • getBinding: function( name ){
  • return( this.bindings[ name ] );
  • },
  •  
  •  
  • // I return the raw value (before evaluation).
  • getValue: function(){
  • return( this.value );
  • },
  •  
  •  
  • // I check to see if the given binding exists.
  • hasBinding: function( name ){
  • return( name in this.bindings );
  • },
  •  
  •  
  • // I update the stored value.
  • setValue: function( value ){
  • this.value = value ;
  •  
  • // Return this object for method chaining.
  • return( this );
  • },
  •  
  •  
  • // I convert the object to a string representation.
  • // This needs to evaluate the string because it
  • // gets used in things like the replace() method.
  • toString: function(){
  • return( this.valueOf() );
  • },
  •  
  •  
  • // I get the value of the string (in its fully
  • // evaluated format with integrated bindings).
  • valueOf: function(){
  • return( this.applyBindings() );
  • }
  •  
  • }
  • );
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // Create a new instance of the gstring string.
  • var message = new GString(
  • "Hello ${name}, you are looking ${adj} today!"
  • );
  •  
  • // Add the bindings for the GSTring.
  • message.addBindings({
  • name: "Tricia",
  • adj: "wicked hot"
  • });
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // Log the message as it's own object.
  • console.log( message );
  •  
  • console.log( "..." );
  •  
  • // Log the message in a way that requires it to be
  • // converted into a string (we are going to concatentate
  • // it with another string).
  • console.log( "Message: " + message );
  •  
  • // Use core string methods.
  • console.log( message.toUpperCase() );
  •  
  • </script>
  • </head>
  • <body>
  •  
  • <h1>
  • Creating A GString In Javascript
  • </h1>
  •  
  • </body>
  • </html>

Here, we are creating our GString class. When defining the class, we use an instance of the native String class as our GString prototype. In this way, we can extend the String class without permanently altering it. Using jQuery's extend() method, we then add additional functions to the prototype to override some of the core String functions. In particular, we need to override the valueOf() and the toString() methods to get our GString object to look and act like an actual string.

In our valueOf() method override, notice that before we return the string value, we are applying the cached bindings. This will take whatever bindings have been defined and try to merge them into the string. In this way, we can put off evaluating the string until it is absolutely necessary.

The only caveat here with creating new classes rather than altering the existing String class is that you have to explicitly create new instances; you cannot make use the implicit string constructor. But, since we are dealing with specialized objects, I don't see this as being an issue at all. And, when we run the above code, we get the following console output:

Message: Hello Tricia, you are looking wicked hot today!

HELLO TRICIA, YOU ARE LOOKING WICKED HOT TODAY!

Notice that in the first output, "Tricia" and "wicked hot" were successfully substituted into the string when it was being concatenated. Also notice that the GString class instance was able to use the core string method, toUpperCase(). Works like a charm!

Before this, I had never consciously tried to use a core object as a Class prototype (Object itself is a core class). It's awesome that this worked, although there's no real reason is shouldn't have. I like the idea of being able to create our own classes, that can act like core Javascript classes, without having to alter the core class definitions. Something like this could create a very nice API for HTML templates that need to be populated with Javascript and AJAX.




Reader Comments

This is some impressive solution. I've to try it myself to understand the concept better and say more about it, but nice job!

PS: Wicked pictures!

@Robert,

Thanks my man. This is the first time I have ever tried to extend a core Javascript object. Technically, I have extended the "Object" class (pretty much how all prototypal inheritance works to some degree); but, this is the first time I've ever tried to extend something else.

@Ben... what about the "length" property of strings? From what I can tell, checking the length property of a GString will always return 0 because the underlying implementation of String.length does not know about the GString's value. This is the biggest problem I've run into so far, but it seems like an important one.

@Lowell,

You bring up a good point; that had never occurred to me. Since we are creating our own objects here, we could add a length object. The question is, what length do you want? The length before OR after the evaluation?

Very useful ideas! Thank you!

PS. It's cool to see another software developer with a strong libido. :)

@Hans,

"Misogyny" is a dislike or a hatred of women; I would certainly never use that kind of stuff in my code samples! I am a huge fan of women.