About Me

Curriculum Vitae

A brief list of my current skill set

Bloggybits

Scripting in Node.js AKA How to Watch for Olympic Tickets Using a Script
Tuesday, 7th August 2012, 23:37

Let Node refresh the webpage so you don't have to!

A Javascript Confirm/Alert Replacement jQuery Plugin
Monday, 6th August 2012, 23:06

Much prettier than the horrible alert and confirm dialogs

A Few Node.js Essential Modules
Friday, 3rd August 2012, 14:40

As essential as they can be anyway

Retro Coding Corner: Loading ZX Spectrum Snapshots off Microdrives - Part 2
Tuesday, 31st July 2012, 18:20

It's like juggling 8 balls with one hand behind your back

One Layout To Rule Them All
Monday, 30th July 2012, 16:03

Designing a layout to fit every screen

Retro Coding Corner: Loading ZX Spectrum Snapshots off Microdrives - Part 1
Thursday, 26th July 2012, 16:57

From emulation to super fast tape

Upgrading Libxml2 on CentOS 5.8 fixes Libxmljs
Wednesday, 25th July 2012, 11:33

Fixes strange compile errors and everyfin

Installing Node.js 0.8.x under CentOS 5.8
Tuesday, 24th July 2012, 14:13

It's just an upgrade of Python away

Node.js and Object Oriented Programming
Sunday, 22nd July 2012, 23:46

Make pages inherit pages

MinnaHTML.js - A HTML Web Framework For Node.js
Saturday, 14th July 2012, 22:53

Make webpages the OOP way

Everyone Should Learn to Debug Without a Debugger
Monday, 11th June 2012, 22:42

Because one day they'll all get eaten

Geeky Things What I Wrote Elsewhere
Wednesday, 2nd May 2012, 17:51

A few links to geeky articles I posted on another website

How to Get TypeKit to Working Inside TinyMCE
Monday, 2nd April 2012, 20:36

It's all about the setup baby

When to Use and When Not to Use Frameworks
Saturday, 17th March 2012, 13:59

Frameworks make you stupid

Projects and Sillyness

MAME Cabinet Diary

How I built my own arcade cabinet

Loading Screen Simulator

I don't miss the ZX Spectrum, I still use it!

The Little Guy Chat Room

It's a Pitfall inspired chat room

GPMad MP3

A fully featured MP3 player what I wrote

GP Space Invaders

My first little emulator

GP32 Development Page

Some info and links about this cute little handheld

Disney Nasties

Uncensored images, you must be 18 to view them

Diary of a Hamster

Learn about how hamsters think, first hand

Utilities

Time Calculator

A simple little online utility for working out how many hours to bill a client

A Few Links

Node.js and Object Oriented Programming
Sunday, 22nd July 2012, 23:46

Javascript is so nearly a good OOP language, it just lacks classes and inheritance. Luckily you can simulate most of that with a simple function that copies prototypes between objects. I'm not going to cover that ground with a How-To when it has been done well already, so pop over to SitePoint and read the article by Harry Fuecks.

Also note that whilst I use the MinnaHTML.js library here to make the actual HTML code, there is nothing to stop you using your own poison of choice.

The quick idea here is to respresent a website using an object hierarchical framework, which gives you a lot of interesting possibilities. So lets just dive straight in with a modified version of the copyPrototype function from the aforementioned website:

    function inheritObject(descendant, parent, pagename) {  
var sConstructor = parent.toString();
var aMatch = sConstructor.match( /\s*function (.*)\(/ );
if ( aMatch != null ) { descendant.prototype[aMatch[1]] = parent; }
for (var m in parent.prototype) {
descendant.prototype[m] = parent.prototype[m];
}
if (pagename)
wsBase.prototype.pages[pagename] = descendant;
};

This is almost identical bar the function name, the additional argument and the last additional two lines. What this essentially does is add an entry to an associative array called pages, which belongs to the wsBase object prototype. It associates the provided pagename argument with the object.

More on this later.

The Top Level Parent Object

We'll go straight in here and create a root generic website object which all pages will inherit from.

    // Generic website constructor
function wsBase()
{
this.hPage = new mh.Html();
this.hHead = new mh.Head(this.hPage);
this.hBody = new mh.Body(this.hPage);
this.rendered = false;
this.status = 200;
this.contentType = "text/html";
}

// Prepare the website page pulling any required data
wsBase.prototype.preparePage = function(callback)
{
// We don't actually do anything in the base class here but call the callback
// This should be overloaded but called from inherited objects
callback();
}

// Render the website page to a function
wsBase.prototype.renderPage = function(callback)
{
if (this.rendered)
throw("Page rendered twice");

this.rendered = true;

this.hPage.whenReady(callback);
}

// These parts of the object define the page definitions
wsBase.prototype.pages = new Object();

// Call this (or the alias) to create a page by name
wsBase.prototype.makePage = function(pagename)
{
var objNewPage;

if (wsBase.prototype.pages[pagename])
objNewPage = new wsBase.prototype.pages[pagename]();
else
objNewPage = new wsBase.prototype.pages["404"]();

return objNewPage;
};
var wsMakePage = wsBase.prototype.makePage;

The last function makePage(), it's alias and the pages prototype are going to be used later on, in combination with the modified inheritObject().

Now, let's make a page which contains a few default features like a header and a footer which inherits from the above:

    // Basic website constructor
function wsWebsiteBasic()
{
// Always call parent constructor
this.wsBase();

// Add a standard title
new mh.Title(this.hHead).data.content = "Robertsworld.org.uk";

// Add style sheet, favicon, charset and jquery and script
new mh.Link(this.hHead, null, { rel: "shortcut icon", type: "image/ico", href: "/favicon.ico" } );
new mh.Meta(this.hHead, null, { "http-equiv": "content-type", content: "text/html", charset: "utf-8" } );
new mh.StyleSheet(this.hHead).href = "/styles/standard.css";
new mh.Script(this.hHead).src = "/scripts/jquery172.js";
new mh.Script(this.hHead).src = "/scripts/general.js";

// Create a div for the header and logo
this.header = new mh.Div(this.hBody, "header");
new mh.Image(this.header, "banner", { src: "/images/headerlogo.png" });

// Create our main content div
this.mainbody = new mh.Div(this.hBody, "maincontent");

// Add our standard footer
this.footer = new mh.Div(this.hBody, "footer");
new mh.Paragraph(this.footer).data.content = "I Made This!";
}

// Inherit from base website object
inheritObject(wsWebsiteBasic, wsBase);

Note that we inherit from the base object here but are not providing a page name argument. And to demonstrate how you actually make a page with this, here is a 404 page derived from the above:

    function wsFourOhFour()
{
// Always call parent constructor
this.wsWebsiteBase();

this.title = "Page Not Found";
this.errorDiv = new mh.Div(this.mainbody, "errorbox");

new mh.Heading1(this.errorDiv, "errorheading").data.content = "404 Error";
new mh.Paragraph(this.errorDiv, "errordescription").data.content = "Page not found!";
new mh.Paragraph(this.errorDiv, "errorpath");
this.status = "404";
}

inheritObject(wsFourOhFour, wsWebsiteBase, "404");

// Prepare the website page pulling any required data
wsFourOhFour.prototype.preparePage = function(callback)
{
// We have to do this here because the request object won't assigned to this until after the constructor is called
this.errorDiv.errorpath.data.content = "The webpage <b>" + this.request.url + "</b> was not found on this server :(";
this.errorDiv.errorpath.data.content += "<br/><br/>" + util.inspect(this.request.urlBreakdown);

wsWebsiteBase.prototype.preparePage.apply(this, arguments);
}

This should be easy to follow, we have created a wsFourOhFour object which inherits the wsWebsiteBasic object, where we set standard layout options that dictate what all our pages look like. This in turn inherits from the wsBase object which does nothing more than set up some really basic elements common to all webpages.

Now, assuming we are using Connect, a common middleware framework, we can do something like the following:

    var server = connect()
.use(connect.cookieParser("the one ring"))
.use(connect.bodyParser())
.use(connect.session({ secret: "gollum" }))
.use(connect.query())
.use(siteMain)
.listen(8080);

function siteMain(request, response)
{
var strPageWanted = request.url.split("/")[0];

if (strPageWanted == "")
var wsWebsite = wsMakePage("home");
else
var wsWebsite = wsMakePage(strPageWanted.toLowerCase());

// Add some useful members to this object
wsWebsite.request = request;
wsWebsite.response = response;

// Prepare the page by grabbing any data, etc
wsWebsite.preparePage(function() {
// Render the page when its ready
wsWebsite.renderPage(function(html) {
response.writeHead(wsWebsite.status, { "Content-Type": wsWebsite.contentType });
response.end(html);
});
});
}

Hopefully you can see now how this all works. Clearly I've chosen a very simple (and quick) method for routing URL requests to pages and this may not be ideal for you. But hopefully you get the basic idea here.

Any request to http://this_node_app.com/something will result in wsMakePage trying to create a webpage object that we registered in the pages array that belongs to the wsBase prototype as "something". If we don't specify anything in the URL path we'll get an attempt to create a webpage object called "home".

Obviously we haven't made any of those objects yet, all we've done is create a 404, which is happily the webpage object that gets created if nothing else exists.

Note that we can attach useful members to the webpage object inside siteMain that it might want to use for the purposes of reading/writing cookies, checking form data or query strings and so on. This is probably not the best way to do it, looking back it might be better to pass them as arguments to the constructor.

But the general principle is pretty sound, and I think probably quite efficient, the way V8 works anyway. Creating an object should be quite quick and for most applications the overhead of doing so barely noticeable.

In return you get a lot more options, you can do things like make a webpage object which displays a basic login form, and then derive subobjects off it which extend the functionality. You can also use this method to create AJAX pages, returning JSON rather than HTML.

Comments

Add Your Own Comment