Thursday, October 24, 2013

jQuery Tabs One Tab Bug

I needed to add tabs to my jQuery so, naturally, I selected jQuery Tabs.  It worked great!

Except for a gnarly jQuery bug.  If I had only one tab, the tab background (e.g. the ol/ul element) would be one pixel shorter than if I had two or more tabs.  Also, with only one tab, there was an "off by one" bug where the one and only tab would descend by extra one pixel below the tab background.  This "off by one" effect wasn't that noticeable, especially depending on the various colors involved, which is why it may have slipped by the jQuery Tab developers.

The visual effect would be that, when the user took some action that would add a second tab, the tab background would grow by one pixel and the "off by one" effect would be fixed for all tabs.

I spent a good number of hours hunting for the problem before finding it in jquery.ui.tabs.css.  It contained a rule like this:

.ui-tabs .ui-tabs-nav li.ui-tabs-active {
   margin-bottom: -1px;
   padding-bottom: 1px;
}

The margin-bottom style with a negative margin was the issue.  The tab background calculated its height according to the height of its child tab (e.g. the li element).  But, apparently, if negative margins are used, the height of the child element is reduced by the negative margin for parent calculation purpose.  For example, if a child element is 15 pixels high and has a margin of -2, the parent sees the child element as 13 pixels high.

The negative margin is only applied to the active tab.  If there is only one tab, the tab background uses only that tab for calculation purposes and is one pixel too short.  If a second tab is added, then, presto!, there is at least one inactive tab.  Inactive tabs don't get the CSS rule that applies the negative margin so the "negative margin discount" isn't applied.  The tab background grows by one pixel so it can contain the inactive tab and solves the issue for all tabs ... at least until there is one tab (which will be the active tab) again.

I confirmed the issue in two ways.  First, I changed the margin-bottom from -1 pixel to -10 pixels and saw the tab background shrink by 9 pixels.  But, of course, when a second tab was added, the height of the tab background jumped by 9 pixels.  Second, I created two tabs and then used the Inspector in my browser to hack in the ui-tabs-active CSS class to both tabs such that there were seemingly two active tabs and zero inactive tabs and was rewarded to see the tab background shrink.

But how to fix?

I tried hacking jQuery Tab JavaScript and modifying the CSS rule above.  Sometimes, I fixed it but then I'd break something else.  Finally, I realized that I needed a way to use the negative margin CSS rule only when there were two or more tabs (which worked already) and then apply a different CSS rule when there was one and only one tab.

CSS3 to the rescue!  As it turns out, there is a way to create a CSS rule that is only applied when an element contains only one child.

.ui-tabs .ui-tabs-nav li:first-child:nth-last-child(1) {
   margin-bottom: 0px;
   position: relative;
   top: 1px;
}

Rather than changing the CSS rule in jquery.ui.tabs.css, I modify it with my own CSS rule in my own stylesheet that is only applied when there is one child.  My own rule resets margin-bottom from -1 to 0 and then uses position: relative and top: 1px to accomplish the same "border overlap" effect that negative margins were trying to achieve.

Wednesday, September 18, 2013

Fetch a Single Backbone.js Object via REST


In a previous post, I described how to fetch a collection using Backbone.js RESTful Persistence.  But, this morning, I was a little befuddled as to how to fetch a single object without fetching the entire collection.

The trick is to create a new model with the id attribute and add an options object to the model constructor with the urlRoot property.

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title></title>
  <meta name="description" content="">
</head>
<body onload="onload();">
  <script src="jquery-1.10.1.js"></script>
  <script src="underscore.js"></script>
  <script src="backbone.js"></script>
  <script language="JavaScript"> function onload() {

// create a data model
var Person = Backbone.Model.extend({
  defaults: {
    name: '',
    age: 1
  }
});

// create a data model collection
var PersonCollection = Backbone.Collection.extend({
  model: Person,
  url: 'backbone.php'
});

var oneOff = new Person({id: 138}, {urlRoot: 'backbone.php'});
oneOff.fetch({
  success: function(model) {
    console.log(JSON.stringify(oneOff.toJSON()));
  }
});

}  </script>
</body>
</html>

But, of course, this required an improvement to PHP code in the "Backbone.js REST with PHP" post.  Until now, a GET request always returned the entire collection from backbone.php but I had to update it to check the request URI and, if an object was specified, to find and print that object only.

I've made over a dozen changes to the "Backbone.js REST with PHP" post since I originally published it; I just keep stumbling across new features and quirks which seem to only be covered in bits and pieces all over the Internet.

Wednesday, September 11, 2013

Finally, A Simple Backbone.js Router!

The Internet provides plenty of Backbone.js Router examples but not a simple, straightforward sample that can easily be demonstrated without a web server.

A good example needs to be complete, show a generic implementation that will be often used verbatim and can be easily extended and that will highlight concerns.  The following example shows how to implement a Backbone.js Router to access different pages while using a single HTML page and highlights when the page variables will be reset.

Here's some code that you can copy-and-paste into a file named router.html:

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Backbone.js Router Example</title>
  <meta name="description" content="">
</head>
<body>
<div id="page">
Welcome to Backbone.js routers!
</div>
<input type="button" value="About"
  onclick="pageRouter.navigate('about', {trigger: true});"></input>
<input type="button" value="Page 1"
  onclick="pageRouter.navigate('page/1', {trigger: true});"></input>
<input type="button" value="Page 2"
  onclick="pageRouter.navigate('page/2', {trigger: true});"></input>
<input type="button" value="Page 3"
  onclick="pageRouter.navigate('page/3', {trigger: true});"></input>
<script src="jquery-1.10.1.js"></script>
<script src="underscore.js"></script>
<script src="backbone.js"></script>
<script language="JavaScript">
var visited = 1;
var PageRouter = Backbone.Router.extend({
  routes: {
    'about': 'routeToAbout',
    'page/:num': 'routeToPage'
  },
  // one way to define a route handler
  routeToAbout: function() {
    $('#page').html('About, visited '+visited+' pages');
    ++visited;
  }
});

// create a router instance
var pageRouter = new PageRouter();
// a second way to define a route handler
pageRouter.on('route:routeToPage', function(num) {
  $('#page').html('Page '+num+', visited '+visited+' pages');
  ++visited;
});
Backbone.history.start();
</script>
</body>
</html>

Assuming that you have the Backbone.js infrastructure (i.e. jquery-1.10.1.js, underscore.js and backbone.js), you can load this into any popular browser without a web server.  For example, if you are using Windows and place your files in the C:\ folder, you can type the following directly into the location bar:

file:///C:/router.html

If you want to visit one of the routes, you can press the "About", "Page 1", "Page 2" or "Page 3" buttons on the web page.  Or, you can directly visit the routes by copy-and-pasting the following URLs into the location bar:

file:///C:/router.html#about
file:///C:/router.html#page/1
file:///C:/router.html#page/2
file:///C:/router.html#page/3

Notice how the global variable, visited, is maintained (usually) or reset (when the page is reloaded).  It is critical that your design take into account the circumstances under which page variables are reset.

Some other examples neglect to include a call to Backbone.history.start() and, without it, the page does nothing.  A confusing oversight!

Hopefully, this example lets you learn about Backbone.js Routers in a straightforward way without pausing to manipulate a web server.

Friday, September 6, 2013

Backbone.js REST with PHP


Why isn't there a simple example where Backbone.js RESTful Persistence uses PHP?

Backbone.js has Models and Collections which can be, in theory, easily connected to a REST backend so that adds, deletes and changes on the client are mirrored on the server.

The following sync.html file is a simple demo (or, at least, as simple as I could make it!) that uses a single PHP page, backbone.php, as a server.

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title></title>
  <meta name="description" content="">
</head>
<body onload="onload();">
  <script src="jquery-1.10.1.js"></script>
  <script src="underscore.js"></script>
  <script src="backbone.js"></script>
  <script language="JavaScript"> function onload() {

// optional since these are the defaults
Backbone.emulateHTTP = false;
Backbone.emulateJSON = false;

// create a data model
var Person = Backbone.Model.extend({
  defaults: {
    name: '',
    age: 1
  }
});

// create a data model collection
var PersonCollection = Backbone.Collection.extend({
  model: Person,
  url: 'backbone.php'
});

// store a collection first for nice demo
var people = new PersonCollection([
  new Person({"name": "Bob", "age": 20}),
  new Person({"name": "Bill", "age": 25}),
  new Person({"name": "John", "age": 30})
]);
// jQuery promise deals with async issues
var promise = $.Deferred();
people.forEach(function(person) {
  promise.then(function() {
    console.log('saving '+person.get('name'));
    person.save();
  });
});
promise.then(function() {
  // fetch the collection via HTTP GET
  people = new PersonCollection();
  people.fetch({ success: function(collection) {
    // pretty print the people who were fetched
    console.log(JSON.stringify(collection.toJSON()));
    // another promise
    var promise = $.Deferred();
    // change Bob's age and store him via HTTP PUT
    collection.filter(function(person) {
      return person.get('name') === 'Bob';
    }).forEach(function(person) {
      promise.then(function() {
        console.log('changing Bob\'s age');
        collection.get(person.id).set('age', 40).save();
      });
    });
    // change Bill's age and store him via HTTP PUT
    collection.filter(function(person) {
      return person.get('name') === 'Bill';
    }).forEach(function(person) {
      promise.then(function() {
        console.log('changing Bill\'s age');
        person.set('age', 45).save();
      });
    });
    // change Bill's properties via HTTP PATCH
    collection.filter(function(person) {
      return person.get('name') === 'Bill';
    }).forEach(function(person) {
      promise.then(function() {
        console.log('patching Bill');
        person.save({name: 'Billy', age: 55} , {patch: true});
      });
    });
    // create Tim via HTTP POST
    promise.then(function() {
      console.log('creating Tim');
      collection.create({
        name: 'Tim', age: 50
      }, {
        wait: true,
        success: function(resp) {
          // delete John via HTTP DELETE
          collection.filter(function(person) {
            return person.get('name') === 'John';
          }).forEach(function(person) {
            console.log('destroying John');
            person.destroy({ success: function(resp) {
              // pretty print the people in the collection
              console.log(JSON.stringify(collection.toJSON()));
            }});
          });
        }
      });
    });
    promise.resolve();
  }});
});
promise.resolve();

}  </script>
</body>
</html>

This page creates the initial collection, then executes a series of modifications, then shows the final collection.  The modifications are as follows: (1) Bob's age is changed from 20 to 40; (2) Bill's age is changed from 25 to 45; (3) Bill's name is patched to "Billy" and his age is patched to 55; (4) Tim is created and added; and (5) John is deleted from the collection.

saving Bob
saving Bill
saving John
[{"id":41,"name":"Bill","age":25},{"id":978,"name":"Bob","age":20},{"id":726,"name":"John","age":30}]
changing Bob's age
changing Bill's age
patching Bill
creating Tim
destroying John
[{"id":41,"name":"Billy","age":55},{"id":978,"name":"Bob","age":40},{"name":"Tim","age":50,"id":689}]  

It works by first creating a Backbone.js model called Person.

It then creates a Backbone.js collection called PersonCollection which, like a model, is a "kind" of object, not an object instance.  By default, the url property assigns backbone.php as the server URL that will handle Backbone.js RESTful Persistence.

Then, a collection instance with model instances is created and stored in the people variable.

So far, no REST calls have been made.  Backbone.js REST calls are asynchronous so, to avoid race conditions, it is helpful to use a feature like the jQuery promise APIs.  The promise APIs allow the developer to make REST calls (or other function calls) to only let the next REST call start only after the previous REST call has finished.  The three Person objects in the collection are stored as promises so they can be saved to the backend in order.

A final promise is added to execute the remaining operations.  The people variable is replaced with an empty collection and the previously saved models will be load into the collection using Backbone.js fetch() API as a test.  A new promise variable is created and promises are made to change Bob's and Bill's ages.  Finally, Tim is created, John is destroyed/deleted and the models left in both the client-side (i.e. the collection variable) and the server-side (i.e. store.txt) are the same.

The resolve() function on the promise variable kicks off the actual execution of the promises.

Although I had hoped to avoid the complication of using the jQuery promise APIs in this example, the asynchronous nature of REST APIs make it necessary, even in this simple case.

The only backend is a backbone.php page.  It usually works but it has to use some hacks and tricks which aren't kosher.  It uses a single store.txt file for a data store in the service of clarity but at the expense of reliability; a database would be better.  It also might not work on every PHP server.  That said ...

... it will give you a good idea how Backbone.js RESTful Persistence works.

<?php
$lock = fopen('log.txt', 'a+');
$floc = flock($lock, LOCK_EX);
fwrite($lock, $_SERVER['REQUEST_METHOD']." ");
fwrite($lock, $_SERVER['REQUEST_URI']." lock\r\n");
if (!$floc) {
  fwrite($lock, "flock() failed\r\n");
}
if ($_SERVER['REQUEST_METHOD'] == 'GET') {
  // see if fetching a single object
  $id = explode('/', $_SERVER['REQUEST_URI']);
  $id = array_pop($id);
  if (strpos(strrev($id), strrev('.php')) === 0) {
    // select (fetch) the collection
    $json = file_get_contents('store.txt');
    if ($json === false) {
      $json = "[\n]";
    }
    fwrite($lock, $json."\r\n");
    print $json;
  } else {
    // select (fetch) the object
    fwrite($lock, "GET $id\r\n");
    if ((string)(int)$id != $id) {
      $id = '"'.$id.'"';
    }
    // get the item from store.txt
    $json = file_get_contents('store.txt');
    if ($json === false) {
      fwrite($lock, "[\n]\r\n");
      fwrite($lock, "{}\r\n");
      print "{}";
    } else {
      fwrite($lock, $json."\r\n");
      $found = false;
      $json = explode("\n", $json);
      $json = array_slice($json, 1, -1);
      for ($p=0; $p < count($json); ++$p) {
        if (strpos($json[$p], '"id":'.$id) !== false) {
          $person = rtrim($json[$p], ', ');
          $found = true;
          break;
        }
      }
      if ($found) {
        fwrite($lock, $person."\r\n");
        print $person;
      } else {
        fwrite($lock, "{}\r\n");
        print "{}";
      }
    }
  }
} else {
  // set flags for actions and options
  $emulateHTTP = false;
  $emulateJSON = false;
  $insert = false;
  $update = ($_SERVER['REQUEST_METHOD'] == 'PUT');
  $patch = ($_SERVER['REQUEST_METHOD'] == 'PATCH');
  $delete = ($_SERVER['REQUEST_METHOD'] == 'DELETE');
  if ($update || $patch) {
    // get the new value
    $person = file_get_contents('php://input');
    // don't trust $_SERVER['CONTENT_TYPE']
    if ((substr($person, 0, 1) != '{')
      && (substr($person, 0, 1) != '[')) {
      $emulateJSON = true;
    }
    if ($emulateJSON) {
      $person = urldecode($person);
      $person = explode('=', $person);
      $person = $person[1];
    }
  } elseif ($_SERVER['REQUEST_METHOD'] == 'POST') {
    // get emulated action and value
    $emulateHTTP = true;
    if (isset($_POST['_method'])) {
      if ($_POST['_method'] === 'PUT') {
        $update = true;
        $person = $_POST['model'];
      } elseif ($_POST['_method'] === 'PATCH') {
        $patch = true;
        $person = $_POST['model'];
      } elseif ($_POST['_method'] === 'DELETE') {
        $delete = true;
      }
    } elseif (isset($_POST['model'])) {
      $person = urldecode($_POST['model']);
      $insert = true;
    } else {
      $person = file_get_contents('php://input');
      if ($person === '') {
        $delete = true;
      } else {
        $id = explode('/', $_SERVER['REQUEST_URI']);
        $id = array_pop($id);
        if (is_numeric($id)) {
          if (preg_match('/"id":(.*?)[,}]/', $person) === 1) {
            $update = true;
          }
          $patch = !$update;
        } else {
          $insert = true;
          $emulateHTTP = false;
        }
      }
    }
  }
  if ($delete) {
    // delete the object from store.txt
    $id = explode('/', $_SERVER['REQUEST_URI']);
    $id = array_pop($id);
    fwrite($lock, "DELETE $id\r\n");
    if ((string)(int)$id != $id) {
      $id = '"'.$id.'"';
    }
    // remove the item in store.txt
    $json = file_get_contents('store.txt');
    if ($json === false) {
      fwrite($lock, "[\n]\r\n");
      fwrite($lock, "[\n]\r\n");
    } else {
      fwrite($lock, $json."\r\n");
      $found = false;
      $json = explode("\n", $json);
      $json = array_slice($json, 1, -1);
      for ($p=0; $p < count($json); ++$p) {
        if (strpos($json[$p], '"id":'.$id) !== false) {
          $person = rtrim($json[$p], ', ');
          array_splice($json, $p, 1);
          array_push($json, rtrim(array_pop($json), ', '));
          $found = true;
          break;
        }
      }
      if ($found) {
        $json = implode("\n", $json);
        $json = "[\n".$json."\n]";
        file_put_contents('store.txt', $json);
        print $person;
      }
      fwrite($lock, $json."\r\n");
    }
  } else {
    // insert (create) or update the item in store.txt
    $verb = $update? 'PUT ': 'PATCH ';
    fwrite($lock, ($insert? 'POST ': $verb)."$person\r\n");
    if ($insert) {
      $id = rand(0, 999);
      $person = '{"id":'.$id.','.substr($person, 1);
    } elseif ($patch) {
      $id = explode('/', $_SERVER['REQUEST_URI']);
      $id = array_pop($id);
      $person = '{"id":'.$id.','.substr($person, 1);
      // PATCH is broken but use JSON to merge $person
      //  with the value in store.txt
    }
    $json = file_get_contents('store.txt');
    if ($json === false) {
      fwrite($lock, "[\n]\r\n");
      $json = "[\n".$person."\n]";
    } else {
      fwrite($lock, $json."\r\n");
      preg_match('/"id":(.*?)[,}]/', $person, $id);
      $id = $id[1];
      $found = false;
      $json = explode("\n", $json);
      $json = array_slice($json, 1, -1);
      // replace the item with the new value
      if ($update || $patch) {
        for ($p=0; $p < count($json); ++$p) {
          if (strpos($json[$p], '"id":'.$id) !== false) {
            $last = ($p == count($json) - 1);
            $json[$p] = $person.($last? '': ',');
            $found = true;
          }
        }
      }
      $json = implode("\n", $json);
      // insert the new value
      if (!$found) {
        $json .= ",\n".$person;
      }
      $json = "[\n".$json."\n]";
    }
    fwrite($lock, $json."\r\n");
    file_put_contents('store.txt', $json);
    print $person;
  }
}
fwrite($lock, $_SERVER['REQUEST_METHOD']." unlock\r\n");
flock($lock, LOCK_UN);
fclose($lock);
?>

When the sync.html is loaded, it uses backbone.php to synchronize the models from the client-side to the server-side via REST.  By setting the url parameter on the Backbone.js Collection in sync.html, Backbone.js RESTful Persistence is activated and directed to backbone.php on the server.

The PHP REQUEST_METHOD is used to separate the HTTP actions into GET, PUT, PATCH, POST and DELETE.  The people.fetch() call in sync.html corresponds to the GET action in backbone.php.  The person.save() call in sync.html corresponds to the PUT or PATCH actions in backbone.php.  The collection.create() call in sync.html corresponds to the POST action in backbone.php.  The person.destroy() call in sync.html corresponds to the DELETE action in backbone.php.

The implementations of GET, PUT, PATCH, POST and DELETE all manipulate a store.txt file on the server that stores JSON data for the collection.  It is complicated in places to avoid reliance on a JSON library in PHP but the end result is the same: adding, changing and deleting objects from a JSON array which is stored in store.txt.

I wish that the PHP flock() function calls were not necessary but they are a solid but not wholly successful way to handle the race conditions that can occur on PHP platforms.  Race conditions can occur when several REST calls are executed simultaneously.  Backbone.js does not wait from one REST action to finish before executing another one and, to keep sync.html simple, I've ignored this on the client and used PHP flock() to control this on the server.  The log.txt file, which is used with PHP flock(), also serves as a log.

So, finally, here's something to get you started with Backbone.js and PHP.

Thursday, August 22, 2013

Backbone.js and Two Dumb Idioms

I've been learning Backbone.js which is MVC (or MV*) for client-side JavaScript.

In Backbone.js, you define a model (the "M") by doing this:

var Model1 = Backbone.Model.extend({
});

You create an instance of a model by doing this:

var model1 = new Model1();

To define a Backbone.js view (the "V"), you can do something like this:

var View1 = Backbone.View.extend({
  events: {
    click: function(e) {
      alert('Clicked!');
    }
  }
});

To create an instance of the view, you can do this:

<a id="a1" href="#"><span>My Link</span></a>
...
var view1 = new View1({el: '#a1'});

Now, here comes Dumb Idiom #1.  You can do this:

alert(view1.$el.css('display'));

Say what?  What's view1.$el?  It's a dumb shorthand that saves two characters.  It means $(view1.el) in regular old jQuery but instead of typing those two parentheses, you can use a shortcut that only works in Backbone.js and easily throws everybody, including yourself, for a loop.

But, wait, there's more.  Here comes Dumb Idiom #2.  Instead of using jQuery find(), you can do this:

alert(view1.$('span').html());

What is this?  In Backbone.js but not standard jQuery, you can use view1.$('span') as a shorthand for $(view1.el).find('span').  We save a few more characters here (i.e. nine) but pay for it with the need to memorize yet another idiom.

Backbone.js, stick to your knitting!  You aren't jQuery and shouldn't be inventing unnecessary and non-standard idioms for jQuery.

Wednesday, July 10, 2013

More Simple JavaScript Inheritance

I like John Resig's Simple JavaScript Inheritance ... except that I hate having to refactor my JavaScript code to use it.

As John suggests, suppose that I have some code:

function Person(isDancing){
  this.dancing = isDancing;
}

Person.prototype.dance = function(){
  return this.dancing;
};

function Ninja(){
  this.dancing = false;
}

Ninja.prototype.dance = function(){
  return this.dancing;
};

Ninja.prototype.swingSword = function(){
  return true;
};

That's normal JavaScript object code.  Now, I decide to use John Resig's "Simple JavaScript Inheritance".  John Resig suggests that I rewrite my code like this:

var Person = Class.extend({
  init: function(isDancing){
    this.dancing = isDancing;
  },
  dance: function(){
    return this.dancing;
  }
});

var Ninja = Person.extend({
  init: function(){
    this._super( false );
  },
  dance: function(){
    // Call the inherited version of dance()
    return this._super();
  },
  swingSword: function(){
    return true;
  }
});

Changes are in red.  The implementations stay the same but the code syntax is different.  It's a bit of a hassle if you have little bit of code and, for a lot of code, it's even more of a hassle.

Why can't I reuse my code as-is with only a few modifications?  Something like this:

function Person(isDancing){
  this.dancing = isDancing;
}

Person.prototype.dance = function(){
  return this.dancing;
};

Person = Class.extend(Person);

function Ninja(){
  this._super( false );
}

Ninja.prototype.dance = function(){
  // Call the inherited version of dance()
  return this._super();
};

Ninja.prototype.swingSword = function(){
  return true;
};

Ninja = Person.extend(Ninja);


Instead of rewriting the code into a new code syntax, I only add an extend() call at the end of each new JavaScript class.  Once I've done that, if I want, I can call this._super() where ever I need to.


I make this possible using a slight modification to John Resig's script.  By checking to see if the argument passed to extend() is a function, instead of an object, the script can handle both syntaxes: John Resig's original syntax and the traditional JavaScript object syntax.

/* Simple JavaScript Inheritance
 * By John Resig http://ejohn.org/
 * Init-by-function modification
 * By Daniel Howard http://www.svexpertise.com/
 * MIT Licensed.
 */
// Inspired by base2 and Prototype
(function(){
  var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;

  // The base Class implementation (does nothing)
  this.Class = function(){};

  // Create a new Class that inherits from this class
  Class.extend = function(prop) {
    var _super = this.prototype;
    if ( typeof prop == 'function' ) {
      prop.prototype.init = prop;
      for (var name in prop)
        prop.prototype[name] = prop[name];
      prop = prop.prototype;
    }
   
    // Instantiate a base class (but only create the instance,
    // don't run the init constructor)
    initializing = true;
    var prototype = new this();
    initializing = false;
   
    // Copy the properties over onto the new prototype
    for (var name in prop) {
      // Check if we're overwriting an existing function
      prototype[name] = typeof prop[name] == "function" &&
        typeof _super[name] == "function" && fnTest.test(prop[name]) ?
        (function(name, fn){
          return function() {
            var tmp = this._super;
           
            // Add a new ._super() method that is the same method
            // but on the super-class
            this._super = _super[name];
           
            // The method only need to be bound temporarily, so we
            // remove it when we're done executing
            var ret = fn.apply(this, arguments);        
            this._super = tmp;
           
            return ret;
          };
        })(name, prop[name]) :
        prop[name];
    }
   
    // The dummy class constructor
    function Class() {
      // All construction is actually done in the init method
      if ( !initializing && this.init )
        this.init.apply(this, arguments);
    }
   
    // Populate our constructed prototype object
    Class.prototype = prototype;
   
    // Enforce the constructor to be what we expect
    Class.prototype.constructor = Class;

    // And make this class extendable
    Class.extend = arguments.callee;
   
    return Class;
  };
})();

When a function is detected, instead of an object, the function is first saved to the init property.  Then, all enumerable function properties (which will behave pretty much like static variables in classical class-based systems) are saved to the function's prototype object.  Finally, and here's the magic pixie dust, the function's prototype object is used as the argument which the rest of the code generates the JavaScript class from.  John Resig's code behaves the same but the function's prototype object becomes "the class object" instead of being directly passed as the original argument.

Person = Class.extend(Person);
...
Ninja = Person.extend(Ninja);

The extend() calls above take the JavaScript constructor functions and return the new JavaScript class.  By assigning the return value to the function (variable), the new JavaScript class replaces the traditional JavaScript object creation function.

With this modification and technique, you can write traditional JavaScript object code and, if you later want to use John Resig's "Simple JavaScript Inheritance", you can add it with ease.

Monday, June 3, 2013

Effective XML by Elliotte Rusty Harold (2004)


I finished reading Effective XML by Elliotte Rusty Harold over the weekend.  It's an old book, almost 10 years old, published in 2004.  Still, it is interesting and I wanted to summarize the book for myself and anybody else who is interested.

It is divided into 4 parts: Syntax, Structure, Semantics and Implementation.  Total, there are 50 suggestions to improving your XML.

Syntax

#1: Include an XML declaration.

Like “<?xml version="1.0" encoding="utf-8" standalone="yes" ?>” for example.

#2: Mark up with ASCII if possible.

Don’t use Chinese characters as tag names.

#3: Stay with XML 1.0.

XML 1.1 makes several inadvisable things possible, like tag names in obscure, non-alphabetic languages.

#4: Use standard entity references.

Prefer named references (e.g. &Ecaron;) to character references (e.g. &#x011B;).  Don’t invent your own names if somebody else already has.

#5: Comment DTDs liberally.

DTDs are hard to understand.  Use lots of comments so they can be understood.

#6: Name elements with camel case.

CamelCase instead of camel-case is easier to map to variables.

#7: Parameterize DTDs.

You can build all kinds of flexibility into DTDs using parameters, including conditionals and changing namespaces.

#8: Modularize DTDs.

You can split DTDs into multiple files for more flexibility.

#9: Distinguish text from markup.

The title isn’t very good but what he’s saying is that, once you delimit XML tags and use them as text, they aren’t accessible to the parser.  They are just text.

#10: White space matters.

White space, such as newlines and indentation, will be included in a text node when it’s read in.  In other cases, like DTDs, it is irrelevant.

Structure

#11: Make structure explicit through markup.

Avoid mini-formats where the developer has to parse a text node or attribute value to separate it further.  Use tags instead of embedding spaces or commas in text or values.

#12: Store metadata in attributes.

The content/text should be the data itself; attributes should give metadata about the data.  The content/text should contain what people normally want to see with attributes hiding away less important info.

#13: Remember mixed content.

A single sentence isn’t necessarily a single text node.  It might be multiple text nodes, separated by child tags enclosing certain fragments.  Don’t assume that the XML is flat or expect a rigid schema.

#14: Allow all XML syntax.

Don’t invent an XML format or XML parser that forbids XML processing directives, comments or other standard XML features.

#15: Build on top of structures, not syntax.

Don’t try to differentiate between things that the XML parser says are the same.  Don’t write applications that do something different if it is a named reference vs a character reference.  Or do something different if it is an empty element versus an element with the empty string.

#16: Prefer URLs to unparsed entities and notations.

DTDs can be used to define unparsed entities and notations but it’s better to avoid them and just stick the value right in the XML itself.

#17: Use processing instructions for process-specific content.

XML processing instructions, like “<?xml-stylesheet ?>”, are not returned by parsers as easily but have their uses, especially for data that cuts across parent-child relationships.  Use them where appropriate.

#18: Include all information in the instance document.

Avoid using DTD and other XML features that modify the XML data from outside, such as default attributes.  The XML should contain all the data, even if the parser doesn’t read the DTD or other linked documents.

#19: Encode binary data using quoted printable and/or Base64.

Binary data can be encoded and inserted in XML.  If you need it, do that.

#20: Use namespaces for modularity and flexibility.

Namespaces look like URLs but they are just IDs.  Choose and use a namespace for the data.  Don’t avoid it just because you don’t understand it or don’t care.

#21: Rely on namespace URIs, not prefixes.

Don’t use the “svg:” prefix without setting it to “http://www.w3.org/2000/svg”.  Don’t use namespaces without defining the namespace URI.

#22: Don’t use namespace prefixes in element content and attribute values.

It is very confusing when XML prefixes are used other places than as a tag name.  Instead, require the full namespace URL to be used, maybe as an attribute that modifies the unprefixed tag name.  For example, instead of <element xmlns:ex=”…” type=”ex:year”>, do <element type=”year” typens=”…”>.

#23: Reuse XHTML for generic narrative content.

Use XHTML to content that is paragraphs of text instead of inventing your own schema or restricting the content to unformatted text.

#24: Choose the right schema language for the job.

You have a choice between DTDs and XML Schema.  There is also RELAX NG, Schematron or even using Java.

#25: Pretend there’s no such thing as the PSVI.

PSVI is XML data annotated with its schema information produced by advanced XML parsers.  Some libraries can read the PSVI and produces a memory objects automatically from XML data, like Hibernate does for SQL databases.  PSVI is a nice theory but not practical.

#26: Version documents, schemas and stylesheets.

Add version numbers to XML and its related documents because it will change over time.  You can use dates or major/minor versions.  Don’t assume that your data, schemas and stylesheets will never need revision.

#27: Mark up according to meaning.

Put XML tags around things according to what they are, not just how they are formatted.  For example, italics has several different uses so be more specific with the XML tag.

Semantics

#28: Use only what you need.

XML has lots of parts: XML 1.0, well-formedness, DTDs, Namespaces, XPath, Schemas, XLinks (Simple and Extended), XPointers, XInclude, Infoset, PSVI, XML 1.1, Namespaces 1.1, SVG, MathML, RDF, OWL, CSS, XSLT, XSL-FO, XQuery and so on.  Use what you need.  Don’t feel that you have to understand and use it all.

#29: Always user a parser.

Don’t try to write your own XML parser using regular expressions or something.  Use an off-the-shelf XML parser.

#30: Layer functionality.

Feel free to process XML is whatever order works best for you.  Feel free to do validation before and/or after other processing.  Creating a processing chain that gets you to your final result.

#31: Program to standard APIs.

Write code so it is easy to swap in a new parser.

#32: Choose SAX for computer efficiency.

SAX is an event-based, streaming parser.  Efficiency isn’t usually needed so you normally don’t need SAX.

#33: Choose DOM for standards support.

DOM is a solid standard with lots of implementations.  Many developers understand it.  It is weird in some places, though.

#34: Read the complete DTD.

If standalone is set to “no” in the XML declaration, the DTD is required.  Skipping a required DTD may result in parsing errors.  Be flexible in accepting XML data by reading DTDs.

#35: Navigate with XPath.

Doing “//name” with XPath is easier and less error prone than crawling the DOM tree using getChildNode().  It’s hard to write getChildNode() code that doesn’t rely on tag parent-child relationships, tag order, number of tags and other variations in XML data.

#36: Serialize XML with XML.

Don’t convert XML into an opaque binary format for no reason.  Leave XML as XML.

#37: Validate inside your program with schemas.

Validate XML data using schemas rather than just breaking/crashing.  The point of validation is to detect invalid data.

Implementation

#38: Write in Unicode.

Use UTF-8.  ASCII is a subset of UTF-8.  If using Japanese, Chinese or similar languages, use UTF-16.  Don’t use obsolete ASCII formats.  Do Unicode correctly with normalization and sorting.

#39: Parameterize XSLT stylesheets.

Use xsl:variable (like a constant) and xsl:param to make it easy to change fonts, sizes and other stuff in XSLT.

#40: Avoid vendor lock-in.

Avoid tools that have binary XML formats, unclear tag names, proprietary XML parsers and proprietary APIs.

#41: Hang on to your relational database.

XML does not replace SQL databases but you can use XML with them.

#42: Document namespaces with RDDL.

Namespaces are just IDs but people still try to use them as URLs.  RDDL is an XML schema for a web page that is posted at a namespace “URL” that can provide resources, natures and purposes (such as DTDs) that might be useful.

#43: Preprocess XSLT on the server side.

For speed and consistency, preprocess and cache XSLT transformations on the server side using web server plugins.

#44: Serve XML+CSS to the client.

Browser clients can style using XSLT.  CSS can be applied conditionally, depending on the display type.

#45: Pick the correct MIME media type.

Have your web server serve up application/xml instead of text/xml.  Use more official, more accurate mime types, like application/xml+svg, if appropriate.

#46: Tidy up your HTML.

Converting HTML to XHTML will uncover bugs which are worth fixing.  Do validation and fix any bugs that are found.  Really old browsers don’t support some XHTML constructs.

#47: Catalog common resources.

XML Catalogs, used with parsers, allow you locally cache remote resources like DTDs and schemas.  Instead of getting the file from a remote site, the request is redirected to the local machine.

#48: Verify documents with XML digital signatures.

You probably don’t need it but there is a standard for doing digital signatures in XML.

#49: Hide confidential data with XML encryption.

You probably don’t need it but there is a standard for doing encryption in XML.

#50: Compress if space is a problem.

XML doesn’t waste that much space but, if needed, you can compress it.