How To Create GStrings In Javascript By Extending Core Data Types

Posted February 8, 2010 at 3:17 PM by Ben Nadel

Tags: Javascript / DHTML

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

Feb 8, 2010 at 3:37 PM // reply »
4 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!


Feb 8, 2010 at 3:51 PM // reply »
10,743 Comments

@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.


Feb 8, 2010 at 4:43 PM // reply »
5 Comments

Sick stuff Ben!

Ironically there is an interesting project called fusebox that takes on the idea of "safely" extending JavaScript.

http://github.com/jdalton/fusebox


Feb 8, 2010 at 4:47 PM // reply »
10,743 Comments

@Garrett,

Very interesting. I'll have to give that a look. I don't think I have seen that before.


Feb 8, 2010 at 7:27 PM // reply »
43 Comments

You had me at "How To Create GStrings..."


Feb 9, 2010 at 4:05 PM // reply »
1 Comments

@ben, it's pretty refreshing to see people posting things like this. I think it's important to understand the consequences of modifying default JS objects and unfortunately, most people don't.

I explored this concept a bit in a blog post recently and spoke about it too. I wish I had this as an example back then!

http://www.lovemikeg.com/blog/2010/01/21/responsible-javascript-prototype-modification/


Feb 9, 2010 at 6:15 PM // reply »
13 Comments

Very slick Ben.


Jul 29, 2010 at 4:00 PM // reply »
1 Comments

@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.


Aug 1, 2010 at 7:24 PM // reply »
10,743 Comments

@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?


Mar 6, 2011 at 9:32 PM // reply »
1 Comments

Very useful ideas! Thank you!

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


Jun 6, 2011 at 3:39 PM // reply »
1 Comments

Misogyny does not make for good examples.


Jun 6, 2011 at 3:50 PM // reply »
10,743 Comments

@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.


Post A Comment

Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.

Please review the following issues:

Author Name:


Author Email:

Author Website:

Comment:

Supported HTML tags for formatting: <strong>bold</strong>   <em>italic</em>   <code>code</code>







  • Help Wanted - Find Your Next ColdFusion Job
InVision App - Prototyping Made Beautiful With Prototyping Tools Ben Nadel's Company - Epicenter Consulting Recent Blog Comments
May 16, 2012 at 8:18 PM
Best Of ColdFusion 10 Contest Entry - HTML Email Utility
Just found this, looks good! I'm trying to run it on local, it's the 64bit version and I'm experiencing horrible lag. On average the generate.cfm processes the content change in 60-90 seconds. I've ... read »
May 16, 2012 at 6:40 PM
Maintaining Sessions Across Multiple ColdFusion CFHttp Requests
I am trying to integrate this CFHTTPsession into an application that will log into zeekrewards.com to post ads and I am not having any luck. The code works perfectly for logging into other websites, ... read »
May 16, 2012 at 2:44 PM
Creating A Sometimes-Fixed-Position Element With jQuery
Thank you, very useful technique! Worked like a charm. ... read »
May 16, 2012 at 1:58 PM
Movies As A Religious Experience
Acting can, in a way, ruin the movie-goer's experience. I used to be able to get so caught up in movies and their plots, and totally engaged. But lately, I haven't been able to as much with a lot o ... read »
May 16, 2012 at 1:52 PM
The Science Of Optimal Post-Exercise Nutrition
children of this age eat very less vegetables so u can opt for salads they will like it also carrot ,cucumber,onion and as far as pulses are concerned u can boil them ,give him along with mashed rice ... read »
May 16, 2012 at 1:34 PM
Strange ColdFusion JRUN Stack Overflow Error
Hey, Recently I updated my jrun4 using the latest updater 7 and now i am having memory issues :(:(:( any help is appreciated ... read »
May 16, 2012 at 9:56 AM
ColdFusion 10 Beta, Apache Tomcat, And Symbolic Links On Mac OSX
Hi, Now that ColdFusion 10 is out I have stumbled over this as well and I cannot figure out the proper solution. We're running virtual hosts via Apache2; the ColdFusion-applications store their fil ... read »
May 15, 2012 at 6:03 PM
Movies As A Religious Experience
@Ben, I don't know whether you'd consider this a religious observation, but it seems to me, in a sense, movies multiply how many lives we get to have. Each movie is like a little extra life we get ... read »