Client-side Data Structures Using the G Framework

As we’ve mentioned, Quixey uses a JavaScript-centric approach. Our server-side code generates no HTML whatsoever, except for simple stuff we show while the JS-generated DOM tree is loading.

In addition to our class inheritance framework, and of course jQuery, we’ve created our own set of modules that we call The G Framework. Why G? Well, we didn’t want our JS variables polluting the global environment — we wanted to subordinate them all under a single namespace, and we wanted to give it a short name that was easy to type. And if you’re going to type a short namespace name whenever you need to use a variable, then you obviously want to type it with your index finger. So “G” was the obvious choice.

One feature of the G Framework is a solution for keeping track of database data passed in from the server (either through AJAX, or dumped into a script tag that gets evaluated on page load). The basic idea is to have client-side classes that roughly map to database entities, and create a centralized client-side key-based hash of all the data we’ve downloaded and cached.

It’s all powered by the G.data module, which you can see at http://quixey.com/static/js/data.js. It defines a base class, called G.Data, which all other data classes (e.g. G.data.App, G.data.User) inherit from. Check out some of the functionality that is common to G.data objects:

  • Instantiation: All data objects get instantiated with an optional “fields” object parameter. So for example, if I’m working on the page where developers can submit their apps, I can put their input into a new G.data.App({"name": name, "author": author, ...}). For almost all data classes, it is common for the server to send the client an object instance taken from the database. That’s why most data classes define a static method called fromServer.
  • Indexing: All data classes have a key() method that returns a uniquely-identifying key (often taken directly from the the corresponding database entity). When the object is instantiated, it is put into a master hash of G.data objects of the same type:
    G.dataDict[_this.dataType][_this.key()] = _this;
  • Strong Typing: When you try to change the value of a data object’s field by calling set() (defined in the G.Data base class), it compares the value argument’s data type with the field’s previous data type, and throws an exception if they don’t match. So a poor man’s way to declare a field member’s data type within a G.data class is to define it in the class constructor as a dummy instance of that data type.

All these features allow us to write some really elegant code to manage our client-side data. Take a look at this excerpt from the definition of our mission-critical App class:

 5   G.defineData("App", {
 6     _init: function() {
 7       _this.createFields({
 8         "id": Number(),
 9         "urlet": String(),
 10        "name": String(),
 11        "author": new G.data.Author(),
 12        "platform": new G.data.Platform(),
 13        "category": new G.data.AppCategory(),
 14        "url": String(),
 15        "price": new G.data.Money(),
 16        "shortDesc": String(),
 17        "description": String(),
 18        "screenshots": Array(),
 19        "tags": Array(),
 20        "reviews": Array(),
 21        "userRating": Number(),
 22        "avgRating": Number()
 23      });
 24    },

. . .

 79  }, {
 80    fromServer: function(data) {
 81      var screenshots = [];
 82      $.each(data.screenshots, function(i, screenshotData) {
 83        screenshots.push(G.data.Screenshot.fromServer(screenshotData));
 84      });
 85
 86      var tags = [];
 87      $.each(data.tags, function(i, tagData) {
 88        tags.push(G.data.Tag.fromServer(tagData));
 89      });
 90
 91      var reviews = [];
 92      $.each(data.reviews, function(i, reviewData) {
 93        reviews.push(G.data.Review.fromServer(reviewData));
 94      });
 95
 96      return new G.data.App({
 97        "id": data.id,
 98        "urlet": String(data.urlet),
 99        "name": data.name,
 100       "author": G.data.Author.fromServer(data.author),
 101       "platform": G.constants.platforms[data.platform_id],
 102       "category": G.constants.appCategories[data.category_id],
 103       "price": G.data.Money.fromServer(data.price),
 104       "url": data.url,
 105       "shortDesc": data.short_desc,
 106       "description": data.desc,
 107       "screenshots": screenshots,
 108       "tags": tags,
 109       "reviews": reviews,
 110       "userRating": data.user_rating,
 111       "avgRating": data.avg_rating
 112     });
 113   }
 114 });