Subscribe to GameFromScratch on YouTube Support GameFromScratch on Patreon


8. October 2012

 

As per this post I am currently going through the process of creating some simple game creation tools using HTML5, more specifically using the YUI 3 library as well as the EaselJS canvas library.

 

This post illustrates the very skeleton upon which we are going to create our app.  YUI3 provides a full MVC framework which you can use to create your application so I decided to make use of it.  The end result of this code is remarkably minimal, it just creates a single page web application with different views representing different portions of the UI.  Specifically, we will create a top zone where the menu will go, a left hand area where the level editing will occur, then a right hand panel which will change contextually.  I also created a very simple data class, to illustrate how data works within the YUI MVC environment.

 

First off, if you have never heard of MVC, it is the acronym of Model View Controller.  MVC is a popular design practice for separating your application in to logically consistent pieces.  This allows you to separate your UI from your logic and your logic from your data ( the last two get a little gray in the end ).  It adds a bit of upfront complexity, but makes it easier to develop, maintain and test non-trivial applications… or at least, that’s the sales pitch.

 

The simplest two minute description of MVC is as follows.  The Model is your application’s data.  The View is the part of your application that is responsible for displaying to the end user.  The Controller part is easily the most confusing part, and this is the bit that handles communications between the model and view, and is where you actual “logic” presides.  We aren’t going to be completely pure in this level in this example ( MVC apps seldom are actually ), as the Controller part of our application is actually going to be a couple pieces, you will see later.  For now just realize, if it aint a view and it aint a model, it’s probably a controller.

 

It is also worth clarifying that MVC isn’t the only option.  There is also MVVM ( Model-View-ViewModel ) and MVP ( Model-View-Presenter ), and semantics aside, they are all remarkably similar and accomplish pretty much the same thing.  MVC is simply the most common/popular of the three.

 

Put simply, it will look initially more complex ( and it is more complex ), but this upfront work makes life easier down the road, making it generally a fair trade off.

 

Alright, enough chatter, now some code!  The code is going to be split over a number of files.  A lot of the following code is simply the style I chose to use, and is completely optional.  It is generally considered good practice though.

 

image 

At the top level of our hierarchy we have a pair of files, index.html and server.js.  server.js is fairly optional for now, I am using it because I will (might?) be hosting this application using NodeJS.  If you are running your own web server, you don’t need this guy, and won’t unless we add some server-side complexity down the road.

 

index.html is pretty much the heart of our application, but most of the actual logic has been parted out to other parts of the code, so it isn’t particularly complex.  We will be looking at it last, as all of our other pieces need to be in place first.

 

Now within our scripts folder, you will notice two sub-folders models and views.  These predictable enough are where our models and views reside.  In addition, inside the views directory is a folder named templates. This is where our moustache templates are.  Think of templates like simple HTML snippets that support very simple additional mark-up, allowing for things like dynamically populating a form with data, etc.  If you’ve ever used PHP, ASP or JSP, this concept should be immediately familiar to you.  If you haven’t, don’t worry, our templates are remarkably simple, and for now can just be thought of as HTML snippets.  The .Template naming convention is simply something I chose, inside they are basically just HTML.

 

If you are basing your own product on any of this code, please be sure to check out here, where I refactored a great deal of this code, removing gross hacks and cleaning things up substantially!

 

Let’s start off with our only model person.js, which is the datatype for a person entry.  Let’s look at the code now:

 

person.js

YUI.add('personModel',function(Y){
    Y.Person = Y.Base.create('person', Y.Model, [],{
            getName:function(){
                return this.get('name');
            }
        },{
            ATTRS:{
                name: {
                    value: 'Mike'
                },
                height: {
                    value: 6
                },
                age: {
                    value:35
                }
            }
        }
    );
}, '0.0.1', { requires: ['model']});

The starting syntax may be a bit jarring and you will see it a lot going forward.  The YUI.add() call is registering ‘personModel’ as a re-usable module, allowing us to use it in other code files.  You will see this in action shortly, and this solves one of the biggest shortcomings of JavaScript, organizing code.

 

The line Y.Person = Y.base.create() is creating a new object type in the Y namespace, named ‘person’ and inheriting all of the properties of Y.Model.  This is YUI’s way of providing OOP to a relatively un-OOP language.  We then define a member function getName and 3 member variables name, height and age, giving each of the three default values… just cause.  Of course, they aren’t really member variables, they are entries in the object ATTRS, but you can effectively think of them as member variables if you are from a traditional OOP background.  Next we pass in a version stamp ( 0.0.1 ), chosen pretty much at random by me.  Next is a very important array named requires, which is a list of all the modules ( YUI, or user defined ) that this module depends on.  We only need the model module.  YUI is very modular and only includes the code bits you explicitly request, meaning you only get the JavaScript code of the classes you use.

 

So that is the basic form your code objects are going to take.  Don’t worry, it’s nowhere near as scary as it looks.  Now let’s take a look at a view that consumes a person model.  That of course would be person.View.js.  Again, the .View. part of that file name was just something I chose to do and is completely optional.

person.View.js

YUI.add('personView',function(Y){
        Y.PersonView = Y.Base.create('personView', Y.View, [], {
        initializer:function(){
            var that=this,
                request = Y.io('/scripts/views/templates/person.Template',{
                    on:{
                        complete:function(id,response){
                            var template = Y.Handlebars.compile(response.responseText);
                            that.get('container').setHTML(template(that.get('model').getAttrs(['name','age','height'])));
                        }
                    }
                });
        },
        render:function(){
            return this;
        }
    });
}, '0.0.1', { requires: ['view','io-base','personModel','handlebars']});

Just like with our person model, we are going to make a custom module using YUI.add(), this one named ‘personView’.  Within that module we have a single class, Y.PersonView, which is to say a class PersonView in the Y namespace.  PersonView inherits from Y.View and we are defining a pair of methods, initializer() which is called when the object is created and render() which is called when the View needs to be displayed.

 

In initializer, we perform an AJAX callback to retrieve the template person.Template from the server.  When the download is complete, the complete event will fire, with the contents of our file in the response.responseText field ( or an error, which we wrongly do not handle ).  Once we have our template text downloaded, we “compile” it, which turns it into a JavaScript object. The next line looks obscenely complicated:

that.get('container').setHTML(template(that.get('model').getAttrs(['name','age','height'])));

A couple things are happening here.  First we are using that because this is contextual in JavaScript.  Within the callback function, it has a completely different value, so we cached the value going in.  Next we get the property container  that every Y.View object will have, and set it’s HTML using setHTML().  This is essentially how you render a view to the screen.  The parameter to setHTML is also a bit tricky to digest at first.  Essentially the method template() is what compiles a moustache template into actual HTML.  A template, as we will see in the moment, may be expecting some data to be bound, in this case name, age and height which all come from our Person model.  Don’t worry, this will make sense in a minute.

 

Our render method doesn’t particularly do anything, just returns itself.  Again we specify our modules dependency in the requires array, this time we depend on the modules view, io-base, personModel and handlebars.  As you can see, we are consuming our custom defined personModel module as if it was no different than any of the built-in YUI modules.  It is a pretty powerful way of handling code dependencies.

 

Now let’s take a look at our first template.

person.Template

<div style="width:20%;float:right">
    <div align=right>
        <img src=http://www.gamefromscratch.com/image.axd?picture=HTML-5-RPG_thumb_1.png 
alt="GameFromScratch HTML5 RPG logo" />
    </div>
    <p><hr /></p>
    <div>
        <h2>About {{name}}:</h2>
        <ul>
            <li>{{name}} is {{height}} feet tall and {{age}} years of age.</li>
        </ul>
    </div>
</div>

As you can see, a template is pretty much just HTML, with a few small exceptions.  Remember a second ago when we passed data in to the template() call, this is where it is consumed.  The values surrounded by {{ }}  ( thus the name moustache! ) are going to be substituted when the HTML is generated.  Basically it looks for a value by the name within the {{ }} marks and substitutes it into the HTML.  For example, {{name}}, looks for a value named name, which it finds and substitutes it’s value mike in the results.  Using templates allows you to completely decouple your HTML from the rest of your application.  This allows you to source out the graphic work to a designer, perhaps using a tool like DreamWeaver, then simply add moustache markup for the bits that are data-driven.

 

What you may be asking yourself is, how the hell did the PersonView get it’s model populated in the first place?  That’s a very good question.

 

In our application, our view is actually going to be composed of a number of sub-views.  There is a view for the area the map is going to be edited in, a view for the context sensitive editing will occur ( currently our person view ), then finally a view where our menu will be rendered.  However, we also have a parent view that holds all of these child views, sometimes referred to as a composite view. This is ours:

editor.View.js

YUI.add('editorView',function(Y){
    Y.EditorView = Y.Base.create('editorView', Y.View, [], {
        initializer:function(){

            var person = new Y.Person();
            this.pv = new Y.PersonView({model:person});
            this.menu = new Y.MainMenuView();
            this.map = new Y.MapView();
        },
        render:function(){
            var content = Y.one(Y.config.doc.createDocumentFragment());
            content.append(this.menu.render().get('container'));

            var newDiv = Y.Node.create("<div style='width:100%'/>");
            newDiv.append(this.map.render().get('container'));
            newDiv.append(this.pv.render().get('container'));

            content.append(newDiv);
            this.get('container').setHTML(content);
            return this;
        }
    });
}, '0.0.1', { requires: ['view','io-base','personView','mainMenuView','mapView','handlebars']});

The start should all be pretty familiar by now.  We again are declaring a custom module editorView. This one also inherits from Y.View, the major difference is in our initializer() method, we create a Y.Person model, as well as our 3 custom sub-views, a PersonView, a MainMenuView and a MapView ( the last two we haven’t seen yet, and are basically empty at this point ).  As you can see in the constructor for PersonView, we pass in the Y.Person person we just created.  This is how a view gets it’s model, or at least, one way.

 

Our render() method is a bit more complicated, because it is responsible for creating each of it’s child views.  First we create a documentFragment, which is a chunk of HTML that isn’t yet part of the DOM, so it wont fire events or cause a redraw or anything else.  Basically think of it as a raw piece of HTML for us to write to, which is exactly what we do.  First we render our MainMenuView, which will ultimately draw the menu across the screen.  Then we create a new full width DIV to hold our other two views.  We then render the MapView to this newly created div, then render the PersonView to the div.  Finally we append our new div to our documentFragment.  Finally we set our view’s HTML to our newly created fragment, causing all the views to be rendered to the screen.

 

Once again, we set a version stamp, and declare our dependencies.  You may notice that we never had to include personModel, this is because personView will resolve this dependency for us.

 

Lets quickly look at each of those other classes  ( mainMenuView and mapView ) and their templates, although all of them are mostly placeholders for now.

 

mainMenu.View.js

YUI.add('mainMenuView',function(Y){
    Y.MainMenuView = Y.Base.create('mainMenuView', Y.View, [], {
        initializer:function(){
            var that=this,
                request = Y.io('/scripts/views/templates/mainMenu.Template',{
                    on:{
                        complete:function(id,response){
                            var template = Y.Handlebars.compile(response.responseText);
                            //that.get('container').setHTML(template(that.get('model').getAttrs(['name','age','height'])));
                            that.get('container').setHTML(template());
                        }
                    }
                });
        },
        render:function(){
            return this;
        }
    });
}, '0.0.1', { requires: ['view','io-base','handlebars']});

mainMenu.Template

<div style="width:100%">This is the area where the menu goes.  It should be across the entire screen</div>

 

map.View.js

YUI.add('mapView',function(Y){
    Y.MapView = Y.Base.create('mapView', Y.View, [], {
        initializer:function(){
            var that=this,
                request = Y.io('/scripts/views/templates/map.Template',{
                    on:{
                        complete:function(id,response){
                            var template = Y.Handlebars.compile(response.responseText);
                            that.get('container').setHTML(template());
                            //that.get('container').setHTML(template(that.get('model').getAttrs(['name','age','height'])));
                        }
                    }
                });
        },
        render:function(){
            return this;
        }
    });
}, '0.0.1', { requires: ['view','io-base','handlebars']});

map.Template

<div style="width:80%;float:left">
    This is where the canvas will go
</div>

Now, we let’s take a quickly look at server.js.  As mentioned earlier, this script simply provides a basic NODEJS based HTTP server capable of serving our app.

server.js

var express = require('express'),
    server = express();

server.use('/scripts', express.static(__dirname + '/scripts'));

server.get('/', function (req, res) {
    res.sendfile('index.html');
});

server.listen(process.env.PORT || 3000);

 

I wont really bother explaining what’s going on here.  If you are going to use Node, there is a ton of content on this site already about setting up a Node server.  Just click on the Node tag for more articles.

 

Finally, we have index.html which is the heart of our application and what ties everything together and this is the file that is first served to the users web browser, kicking everything off.

index.html

<!DOCTYPE html>

<html>
<head>
    <title>GameFromScratch example YUI Framework/NodeJS application</title>
</head>
<body>

<script src="http://yui.yahooapis.com/3.5.1/build/yui/yui-min.js"></script>
<script src="/scripts/models/person.js"></script>
<script src="/scripts/views/person.View.js"></script>
<script src="/scripts/views/map.View.js"></script>
<script src="/scripts/views/mainMenu.View.js"></script>
<script src="/scripts/views/editor.View.js"></script>

<script>
    YUI().use('app','editorView', function (Y) {

        var app = new Y.App({
            views: {
                editorView: {type: 'EditorView'}
            }
        });

        app.route('/', function () {
            this.showView('editorView');//,{model:person});
        });

        app.render().dispatch();
    });
</script>


</body>
</html>

 

This sequence of <script> tags is very important, as it is what causes each of our custom modules to be evaluated in the first place.  There are cleaner ways of handling this, but this way is certainly easiest.  Basically for each module you add, include it here to cause that code to be evaluated.

 

Next we create our actual Y function/namespace.  You know how we kept adding our classes to Y., well this is where Y is defined.  YUI uses an app loader to create the script file that is served to your clients browser, which is exactly what YUI.use() is doing.  Just like the requires array we passed at the bottom of each module definition, you pass use() all of the modules you require, in this case we need the app module from YUI, as well as our custom defined editorView module.

 

Next we create a Y.App object.  This is the C part of MVC.  The App object is what creates individual views in response to different URL requests.  So far we only handle one request “/”, which causes the editorView to be created and shown.  Finally we call app.render().dispatch() to get the ball rolling, so our editorView will have it’s render() method called, which will in turn call the render method of each of it’s child views, which in turn will render their templates…

 

Don’t worry if that seemed scary as hell, that’s about it for infrastructure stuff and is a solid foundation to build a much more sophisticate application on top of.

 

Of course, there is nothing to say I haven’t made some brutal mistakes and need to rethink everything! Smile

 

Now, if you open it up in a browser ( localhost:3000/ if you used Node ), you will see:

image

 

Nothing too exciting as of yet, but as you can see, the menu template is rendered across the top of the screen, the map view is rendered to the left and the Person view is rendered on the right.  As you can see from the text, the data from our Person model is compiled and rendered in the resulting HTML.

 

You can download the complete project archive right here.

Design, Programming , , ,

blog comments powered by Disqus

Month List

Popular Comments