Showing posts with label javascript. Show all posts
Showing posts with label javascript. Show all posts

Wednesday, 2 July 2008

Write out the fields of a JavaScript object

I've found myself writing this bit of code a few times now, so thought I'd dump it here for future copy-and-paste.

function writeObj(obj, message) {
  if (!message) { message = obj; }
  var details = "*****************" + "\n" + message + "\n";
  var fieldContents;
  for (var field in obj) {
    fieldContents = obj[field];
    if (typeof(fieldContents) == "function") {
      fieldContents = "(function)";
    }
    details += "  " + field + ": " + fieldContents + "\n";
  }
  console.log(details);
}

This just dumps all the object's fields into the debugging console provided by the Firebug extension for Firefox (the console.log(...) call). If you don't have Firebug, you can easily create a custom console object to provide an alternative log method. For example, put this snippet at the top of the script to use window.alert(...) when console is undefined:

if (!console) {
  var console = {log: function(details) { window.alert(details); }}
}

There are probably better ways of doing this, but I find this handy so I can quickly mash writeObj(myObject) into the console and get a list of all the object's fields. For more fun with simple JavaScript reflection have a look at my earlier post on the subject.

Thursday, 13 December 2007

Lexical scope for closures in JavaScript

Ayende has a post on working with closures in JavaScript. Basically, when looking at capturing variables in a closure, you need to be careful with lexical scope. In JS the scope is a function, so if you want to capture a variable that will change values during the scope of the function, you will need to reference it via another function.

From Ayende's example, where i is set in a loop, this:

if( nums[i] % 2 == 0)
    {
      var tmpNum = nums[i];
      alertLink.onclick = function() { alert('EVEN: '+ tmpNum ); };
      //tmpNum will be bound to its value when the loop exits.
    } ...

Needs to become this:

if( nums[i] % 2 == 0)
    {
      var act = function(tmpEVEN)
      {
        alertLink.onclick = function() { alert('EVEN: '+tmpEVEN); };
      };
      act(nums[i]);
      //tmpEVEN will be bound to nums[i] at the point where act(nums[i]) is called.
    } ...

Ayende notes that in C#, the first example will work as its lexical scope is the current block (I think :S). In JavaScript, we need to use a function to get the correct scope.

Friday, 30 November 2007

Reflecting on JavaScript objects

I have been doing some more playing around with JavaScript recently, and wanted to do some reflection over JavaScript objects to see what functions they had, and then execute some of those functions. This is made fairly easy for us due to JavaScript's use of associative arrays (from Wikipedia's exampleobj.x = 10; is the same as obj["x"] = 10;).

First let's embed some JavaScript into a local HTML file:

<script>
  var MyClass = function() {}
  MyClass.prototype = {
    aFunction: function() { return 1; },
    anotherFunction: function() { return 2; }        
  }
</script>

Here we have a basic MyClass class. We can use the for..in statement to enumerate through the items in this class:

for (var member in MyClass) {
  document.write(member + "<br/>");        
}

This writes "prototype" (plus line break) to the page in FireFox (not in IE7, but haven't looked into why), which is the only thing defined for MyClass. You can also enumerate over MyClass.prototype to see what's there. In my case I wanted to check and run methods from an instance of MyClass (which is initialised from the prototype):

var instanceOfMyClass = new MyClass();
for (var member in instanceOfMyClass) {
 document.write(member);
 document.write(": " + instanceOfMyClass[member]());
 document.write("<br/>");
}                 

Which displays the following (in both FireFox and IE7):

aFunction: 1
anotherFunction: 2

Here we access the functions in instanceOfMyClass by looking up the value associated with each member. One quick gotcha: the for..in enumeration in this case returns strings, not pointers to the member itself (so you can't use member() to evaluate it).

Another option is to use the eval function to execute the method, but that isn't quite as pretty as it involves building up strings (eval("instanceOfMyClass." + member + "()")).

Note we aren't accounting for argument lists here. You might want to look at the Function class for getting more data about functions and their arguments. We also are not catering for members that are not functions. You can use typeof to filter out other values.

Friday, 5 October 2007

Passing an arbitrary number of arguments to a JavaScript function

In C# you can specify that an arbitrary number of arguments / parameters can be passed to a function using the params keyword. This looks something like this:

bool areAllChecked(params CheckBox[] checkBoxes) {
  foreach (CheckBox checkBox in checkBoxes) {
    if (!checkBox.Checked) return false;
  }
  return true;
}
...
bool areAllChecked = areAllChecked(checkBox1, checkBox2, ..., checkBoxN);

Unsurprisingly (being a dynamic language and all), you can do this in JavaScript as well using the arguments object:

function areAllChecked() {
  for (var i=0; i<arguments.length; i++) {
    if (!arguments[i].checked) return false;
  }
  return true;
}
var areAllChecked = areAllChecked(checkBox1, checkBox2, ..., checkBoxN);

Wednesday, 12 September 2007

Dustin Diaz's JavaScript, CSS and XHTML site

Found a number of useful articles on Dustin's site, which is dedicated to web usability and accessibility. Dustin works for Google (and previously for Yahoo!) and is a published author on the subject, so he is probably worth a read. :)

An older article of his shows a nice implementation of the dollar function $(), which is typically used in many libraries (including the ASP.NET AJAX client libraries) as a shortcut for document.getElementById(). Dustin's implementation handles multiple arguments, and can accept string IDs and object references.

Friday, 20 July 2007

JavaScript essentials for ASP.NET developers

Scott Allen has an excellent article on What ASP.NET Developers Should Know About JavaScript. He covers JSON and Object Oriented JavaScript programming. Over the past year or some my opinion of JavaScript has dramatically improved. I never thought I'd see the day when I actually enjoyed using JavaScript :). If you are still harbouring old war-wounds and grievances against the language, make sure you take a look at Scott's and others' relevant articles, then give it another go.

Thursday, 12 July 2007

Test driving JavaScript code

A while ago I was developing some JavaScript code that would be used by a custom ASP.NET web control. It was fairly straightforward code -- it just had to validate a particular format of dates. For anyone who has done custom controls before, they are not the sort of thing you want to debug when you are done. You need to recompile, redeploy, clear any VS.NET or web application caches... you get the idea. So I was keen to really test the script before I let it loose on the world.

There are several JavaScript unit test frameworks around, including two flavours of JSUnit (1, 2). I wanted to work a bit differently, so I decided to roll my own. It was surprisingly fun and surprisingly successful. First was a JavaScript file (JsUnitTests.js) for the unit testing framework code:

//
// Global test functions and state
//
var CurrentTestContext;

function assertEquals(expected, actual, message) {        
  if (!(expected==null && actual==null) && expected != actual) {
  throw "Expected: " + expected + ", Got: " + actual + ", Message: " + message;
  }
}

function assertDateEquals(expected, actual, message) {  
  if (expected.getDate() != actual.getDate() ||
    expected.getMonth() != actual.getMonth() ||
    expected.getFullYear() != actual.getFullYear()) {
  throw "Expected: " + expected + ", Got: " + actual + ", Message: " + message;
  }
}

//
// Test class
//
Test = function(testName, testFunction) {
  this.testName = testName;
  this.testFunction = testFunction;
  this.result = null;
  this.assertionMessage = null;
  
}
Test.prototype = {
  run: function() {
    CurrentTestContext = this;
    try {
      this.testFunction();
      this.result = true;
    } catch (ex) {
      this.result = false;
      this.assertionMessage = ex;
    }        
    return this.result;
  }
}

//
// Test fixture
//
TestFixture = function(fixtureName) {
  this.name = fixtureName;
  this.tests = new Array();
  this.onTestRun = new Object();
}

TestFixture.prototype = {
  testRunner: null,  
  setUp: function(){ return; },
  tearDown: function(){ return; },
  tests: null,
  addTest:
  function(testName, testFunction) {
    this.tests[this.tests.length] = new Test(testName, testFunction);   
  },
  runTests:
  function() {
    for (var i=0; i<this.tests.length; i++) {
      this.setUp();
      this.tests[i].run();
      this.tearDown();
      this.onTestRun(this.tests[i], this.testRunner);
    }
  } 
}


//
// Test runner
//
TestRunner = function() {    
  this.initialiseTestOutput();   
}

TestRunner.prototype = {
  fixtures: new Array(),
  addFixture: 
  function(fixture) {
    this.fixtures[this.fixtures.length] = fixture;
    fixture.testRunner = this;
  },
  initialiseTestOutput:
    function() {
      if (this.getTestResults() == null) {
        document.writeln("<h1>Test Results</h1>");
    document.writeln("<div id='testResults'></div>");
      }         
    },
  getTestResults:
    function() {
      return document.getElementById("testResults");
    },
  addTestResult:
    function(element) {
      this.getTestResults().appendChild(element);
    },  
  addFixtureHeading:
    function(fixture) {
      var element = document.createElement("h2");
      element.appendChild(document.createTextNode(fixture.name));
      this.getTestResults().appendChild(element);
    },    
  clearTestResults:
    function() {
      var testResultsNode = this.getTestResults();
      while (testResultsNode.childNodes.length > 0) {
        testResultsNode.removeChild(testResultsNode.firstChild);
      }
    },    
  run:
    function() {
      this.clearTestResults();
      for (var i=0; i<this.fixtures.length; i++) {
        this.addFixtureHeading(this.fixtures[i]);
    this.fixtures[i].onTestRun = this.testRun;
    this.fixtures[i].runTests();
      }      
    },
  testRun:
    function(test, testRunner) {     
      var testReport = test.testName + ((test.result) ? " OK " : " FAIL ")
      var testMessage = (test.assertionMessage==null) ? "" : test.assertionMessage;
      var element = document.createElement("div");
      element.style.color = (test.result) ? "green" : "red";
      element.appendChild(document.createTextNode(testReport)); 
      if (testMessage.length > 0) {
        element.appendChild(document.createElement("br"));
        element.appendChild(document.createTextNode(" |--- " + testMessage));
      }
      testRunner.addTestResult(element);        
    } 
}
Next was to setup an HTML page to run the tests:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
  <head>
    <title>Date Validation Unit Tests</title>
    <script language="JavaScript" src="JsUnitTests.js"></script>
    <script language="JavaScript" src="DateValidation.js"></script>
    <script language="JavaScript" src="StandardDateParserFixture.js"></script>
    <script language="JavaScript" src="DateValidatorFixture.js"></script>
  </head>
  <body>
    <script language="javascript">  
    var testRunner = new TestRunner();  
    testRunner.addFixture(StandardDateParserFixture);    
    testRunner.addFixture(DateValidatorFixture);
    testRunner.run();
    </script>    
</html>
The JsUnitTests.js file is the first file in this post. The DateValidation.js is the code being tested. The StandardDateParserFixture.js and DateValidatorFixture.js are the tests (in test fixture classes) themselves. The tests call the DateValidator.js code and use assertion methods in the JsUnitTests.js file. The rest of the file sets up the test runner (var testRunner = new TestRunner();), adds the relevant fixtures and runs the test suite whenever the page is loaded. Let's have a look at an extract from one of the fixtures:
var StandardDateParserFixture = new TestFixture("Standard Date Parser");    
var parser;

StandardDateParserFixture.setUp = function() {
  parser = new StandardDateParser();
}
StandardDateParserFixture.tearDown = function() {
  parser = null;
}

StandardDateParserFixture.addTest(
  "Parse day",
  function() {
  parser.parseDate("21 April 2007");
  assertEquals(21, parser.getDay());
  parser.parseDate("4 April 2007");
  assertEquals(4, parser.getDay());
  parser.parseDate("04 April 2007");
  assertEquals(4, parser.getDay());
  }
);

StandardDateParserFixture.addTest(
  "Parse valid but incorrect day",
  function() {  
  assertEquals(true, parser.parseDate("32 Jan 2007"));
  assertEquals(32, parser.getDay());
  }
); 

StandardDateParserFixture.addTest(
  "Parse invalid day",
  function() {
  var invalidDayParts = ["ab", "40", "-1", "w"];      
  for (var i=0; i<invalidDayParts.length; i++) {        
    var stringToParse = invalidDayParts[i] + " dec 2007";
    assertEquals(false, parser.parseDate(stringToParse));
    assertEquals(null, parser.getDay(), "Error parsing '" + stringToParse + "'");
  }     
  }
);
  
StandardDateParserFixture.addTest(
  "Get month index from 'April'",
  function() {
  assertEquals(3, parser.getMonthIndexFromString("April"));
  }
);    

StandardDateParserFixture.addTest(
  "Get month index from 'Apr'",
  function() {
  assertEquals(3, parser.getMonthIndexFromString("Apr"));
  }
);    

StandardDateParserFixture.addTest(
  "Get month index from 'Aprr' should be null",
  function() {
  assertEquals(true, parser.getMonthIndexFromString("Aprr")==null);
  }
);

StandardDateParserFixture.addTest(
  "Get month index for valid month name with any casing (upper/lower)",
  function() {
  var monthInputs = ["dec", "DEC", "deC", "december", "DECEMBER"];
  for (var i=0; i<monthInputs.length; i++) {
    assertEquals(11, parser.getMonthIndexFromString(monthInputs[i]), "Failed on " + monthInputs[i]);
  }
  }
);

StandardDateParserFixture.addTest(
  "Parse month index",
  function() {
  assertEquals(true, parser.parseDate("21 April 2007"));
  assertEquals(3, parser.getMonthIndex());
  }
);

StandardDateParserFixture.addTest(
  "Parse year",
  function() {
  assertEquals(true, parser.parseDate("21 April 2007"));
  assertEquals(2007, parser.getYear());
  }
);

StandardDateParserFixture.addTest(
  "Parse invalid year",
  function() {
  assertEquals(false, parser.parseDate("21 April 07"));
  assertEquals(null, parser.getYear());
  }
);
This code initialises a test fixture object, creates the object under test using the setUp function prototype, and then adds tests (which consist of a test name and function). You can then run the tests by opening the HTML page in your browser, reloading whenever you add a test. This is also really helpful for testing JavaScript implementations across different browsers. The main limitation of this approach is it does not do well for testing existing pages. For my purposes though, testing simple, isolated code modules that would be called by a control, it was invaluable. I wrote all the tests first, then added the relevant functionality into the class under test to make it pass. To complete this example, here is the finished code in DateValidation.js:
// Parser for dates specified in dX[MMM|MMMM]Xyyyy format, where X is a non-alphanumeric separator.
// This does not check for date correctness, just for the correct format.
//
// class StandardDateParser {
StandardDateParser = function() {}

StandardDateParser.prototype = {
  monthNames: ["january","february","march","april","may","june","july","august","september","october","november","december"],
  dateRegex: new RegExp("^([0-3]{0,1}\\d)\\W([A-Za-z]{3,})\\W(\\d{4})$"),
  datePositionsInRegexMatch:  { full: 0, day: 1, month: 2, year: 3 },
  datePartMatches: null,
  getDatePart:
    function(datePart) {
      if (this.datePartMatches == null) return null;
      return this.datePartMatches[this.datePositionsInRegexMatch[datePart]];
    },
  getMonthIndexFromString:
    function(s) {
      s = s.toLowerCase();
      for (var i=0; i&lt;this.monthNames.length; i++) {
        if (s == this.monthNames[i] || s == this.monthNames[i].substr(0, 3)) {
          return i;
        }
      }
      return null;      
    },    
  getDay:
    function() {
      return this.getDatePart("day");
    },
  getMonthIndex:
    function() {
      var month = this.getDatePart("month");
      if (month == null) { return null; }
      return this.getMonthIndexFromString(month);
    },
  getYear:
    function() {
      return this.getDatePart("year");
    },    
  parseDate:
    function(s) {
      this.datePartMatches = this.dateRegex.exec(s);
      return this.datePartMatches != null;
    }
  
}
// }

// Validator for dates passed via string. Uses a parser object to parse date parts
// from the initial string, then checks for the correctness of the date itself.
//
// class DateValidator {
DateValidator = function() {}

DateValidator.prototype = {
  dateParser: new StandardDateParser(),  
  getDate:
    function(s) {     
      if (this.dateParser.parseDate(s) != true) return null;
      return new Date(this.dateParser.getYear(), this.dateParser.getMonthIndex(), this.dateParser.getDay());           
    },
  isValid:
    function(s) {
      var parsedDate = this.getDate(s);
      if (parsedDate == null) return false;      
      return parsedDate.getDate() == this.dateParser.getDay() &&
              parsedDate.getMonth() == this.dateParser.getMonthIndex() &&
              parsedDate.getFullYear() == this.dateParser.getYear();
    }
}
// }

The final test output rendered to the HTML page looks something like this:

Test Results

Standard Date Parser

Parse day OK
Parse valid but incorrect day OK
Parse invalid day OK
Get month index from 'April' OK

...

Thursday, 26 April 2007

A re-introduction to JavaScript

A re-introduction to JavaScript by Simon Willison (original article) describes the basic yet commonly misunderstood features of the JavaScript language, including some nice code examples.

Sunday, 4 March 2007

Javascript Closures article

A must-read article on closures in Javascript.

Thursday, 21 December 2006

Namespaces and JavaScript

Michael Schwarz has a post on how to register namespaces in JavaScript. There is a decent amount of information out there (like this post), but Michael's post has a nice, simple implementation.

IBM DevWorks introduction to JS language features

I've been doing a bit of work with classes in JavaScript lately and have been surprised at how powerful it can be. This article outlines some of the frequently overlooked features of JavaScript.

Thursday, 14 December 2006

Private class members in Javascript

Douglas Crockford has some information on class member access and information hiding in Javascript (and some other JS information and links).