Debugging your JavaScript app using WebStorm

17. August 2012

 

In the past I mentioned and even recommended HTML5 developers give WebStorm a shot, this is the IDE I use personally when working in JavaScript, but I realized I never actually said why.  One of the big reasons is, its just a good solid text editor, with good project management and solid code completion, which is an area most tools fail at.  But one of the biggest reasons is, it gives a rock solid debugging experience… very similar to native code.  This is something that can make JavaScript development a WHOLE lot nicer.

EDIT: I should make it clear at this point, virtually nothing in this guide is specific to WebStorm, you can debug using the Chrome Developer Tools or using Firebug in FireFox. Webstorm however integrates the process directly within the IDE.  The process will change slightly ( different buttons/hotkeys ), but the instructions below basically apply to debugging in browser as well.

 

At the same time it dawned on me… if you don’t come from a compiled code background, you probably don’t even realize what you have been missing!  So, I have put this post together, it will hopefully make the developer experience better for at least a couple of you.  If you are currently debugging using a combination of alerts, console writes and breakpoints in the browser, listen up!

 

First off, you are going to need a project to debug.  I am going to use my most recent tutorial post on creating sprite sheets with Cocos2D if you want to download and follow along.  Of course, any project will work, but that particular project has an interesting… quirk that will come in useful.  Next, to get the most out of Webstorm debugging, you should also have Firefox installed.  If you are doing web development, I assume you do already anyways.  Alright, let’s get started.

 

Open the project up in WebStorm.

Now we want to create a debug profile.  In Webstorm, select Run->Edit Configurations…

image

 

In the resulting dialog, click the + icon:

image

 

Choose JavaScript Debug –> Local

image

 

On the left hand side, a new entry should appear.  Select it and file in the form like follows.  Navigate to your index.html or root HTML file, select Firefox from the browser dropdown and name it whatever you would like:

image

Click Apply then Ok when complete.

 

You are now ready to debug. 

Select Run-> Debug ‘Your App’ or press Shift + F9

image

 

Voila, or app should load up in our browser:

image

 

Now what we want to debug is debug the activity that occurs when the user presses a key.  Go back over to Webstorm, open MyFifthApp.js , locate the line with onKeyDown: function, and select the line right below it.  Here we want to set a breakpoint, which will cause our code to stop when the breakpoint is hit.  Set a breakpoint by hitting CTRL+F8 or selecting Run->Toggle Line Breakpoint.  You will see a red dot once your breakpoint is set, like so:

image

 

With the breakpoint set, now flip back to Firefox and hit a key.  This will immediately trigger the breakpoint, pausing your programs execution.  Now if you head back to Webstorm, there will be a wealth of information available to you:

 

image

 

There is quite a bit going on here.  Our code is currently paused executing on our breakpoint.  You can’t see it in the screenshot, but I am hovering my mouse over the e parameter, and it is popping up a tooltip showing its current value.  When debugging, you can interact with your code in a lot more detail.  Now lets take a closer look at the different bits of the interface here.

 

At the bottom left you have a very important window:

image

 

The toolbar on the left hand side can be used to resume program execution, stop completely, or to run from the beginning ( currently grayed out ).  When you are done with a breakpoint, you hit the resume button to continue your program execution.  The two buttons below on the toolbar are for managing breakpoints ( you can end up with a lot of them quickly! ), as well as to mute a breakpoint, which causes it not to fire DURING THIS DEBUGGING SESSION.  The next time you run your program, your breakpoint will be re-enabled unless you remove it.

 

To the right is something traditionally called a callstack, and it’s extremely useful.  Basically, it’s a hierarchy of the last functions called.  So in this case, you can see that our current ( anonymous) method inside MyFifthApp, was called by an anonymous method in CCKeyboardDispatcher, which was called by a different method in the same file.  Clicking the value in the callstack will immediately bring you to the line of code, where you can inspect the current value of any variables there.  Often the error actually occurs further up the callstack, so being able to trace backwards through a programs execution is incredible handy.

 

Next is an incredibly valuable toolbar, that controls your program’s execution while debugging.

 

image

These three are the most commonly used.

Step Over will cause your code to jump to the next line of code in the same file.

Step Into will jump into the code that you are currently on.  If it is a function for example, it will jump into the code for that function, even if it is in a different file.

Step Out backs you out, think of it like undoing a step into, you basically jump back into the calling code.

You use these 3 buttons ( or their hotkey/menu equivalents ) to run through your code as you debug.  There is also an option of run to cursor, which will run to whatever line of code your cursor is currently active on ( clicked on, not just hovering over ).

 

The program will update as it executes, so keep in mind, variable values will often be garbage, until the line they are allocated on is executed.

 

Next up is the Variables window.  Often called “Locals” in other IDEs:

image

 

This is a list of all locally declared variables.  Notice how indexAsString and prevPrefix are both shown, but valued as void?  This is because they haven’t been executed yet.  Lets take a closer look at that.  In WebStorm, locate the line this.removeChild(this.sprite), left click it to focus your cursor, then select run to cursor from the toolbar we just discussed:

image

 

As you can see from the blue line, or program execution continued to that point.  More importantly, if you look in the Variables window, they will now have values, since they have executed in the debugger:

image

 

You may also notice the + sign next to this.  That is because it isn’t a single value variable, so it is composed of other values.  Clicking the + icon will give you more details about the contents of that object:

image

 

Of course, often you want particular information about a specific variable.  This is where watches come in.  Say we want to see more details about this.sprite. In your code window, select all of this.sprite, right click and select Add To Watches.

image

 

Now details of that variable will be available in the watches window, at least, when it is in scope it will:

image

 

You can watch a number of variables at the same time.  Watches are especially useful for watching long lasting variables, like Director or when working in a browser, document.  Just like in the variables list, you can drill down to get more information on child values of the watched variable.

 

Another incredibly handy ability is the expression evaluator, invoked with this button:

image

 

The expression evaluator allows you to run any arbitrary code and see the value.  You can used it for example, to see what a function value would return.  Here for example, I am examining the results of calling this.sprite.getPosition().  Nicely, the expression evaluator has full code hints.

 

image

 

 

 

Finally, sometimes you want to debug code that is running from a server.  This is especially true if you are doing a number of AJAX or socket requests.  You can do this with Webstorm as well, with a few requirements.  First, you need to have a copy of the code installed locally, next you need to use Firefox for this part.

 

Let’s try it with our sample project, which exists on my servers at http://www.gamefromscratch.com/downloads/cocos2d/run/MyFifthApp/index.html while you can download the project source code at http://www.gamefromscratch.com/downloads/Cocos2d/zips/MyFifthApp.zip.

 

First we need to create a new remote debugging session.  In WebStorm, select Run->Edit Configurations…  Hit the + icon and selection JavaScript Debug->Remote.

image

 

Fill the resulting dialog out like so:

image

 

Click Apply then OK. Now everything else works exactly like it did before, with one exception.  Select Debug MyFifthApp Remote.

When you run your code and it hits a breakpoint, it will need you to tell it what file on the remote server matches the file the breakpoint is in.  Since we have a breakpoint set in MyFifthApp.js, when we run it, we will see:

 

image

 

Simply click the Set link, and a dialog will appear:

image

Simply type the URL of the remote file.  You only need to perform this once per file you set a breakpoint into.  You of course can perform this setup step in advance if you want.

 

Now you can locally debug code that is actually running on a remote server.

 

I hope you’ve found this guide handy.  In IDE debugging can make life a thousand times easier. 

General, Programming , , ,




Cocos2D HTML Tutorial 6: Spritesheets

14. August 2012

 

This tutorial covers the process of creating and using a spritesheet.  A spritesheet is a collection of individual sprites grouped together into a single image.  I created the spritesheet for this tutorial using TexturePacker.  I already documented the process in this tutorial, the only difference is you set the Data Format to Cocos2D instead of Generic XML.  The spritesheet and all of the sprites that went in to making it are included in the archive at the end of this tutorial.

 

The cocos2d.js and main.js files are relatively unchanged:

 

cocos2d.js:

(function () {
    var d = document;
    var c = {
        COCOS2D_DEBUG:2,
        box2d:false,
        showFPS:false,
        frameRate:60,
        tag:'gameCanvas', 
        engineDir:'../cocos2d/',
        appFiles:['MyFifthApp.js'],
        mainFile:'main.js'
    };
    window.addEventListener('DOMContentLoaded', function () {
        var s = d.createElement('script');
        s.src = c.engineDir + 'platform/jsloader.js';
        d.body.appendChild(s);
        s.c = c;
        s.id = 'cocos2d-html5';
    });
})();

The only real different here is the file name passed in the appFiles array.

 

main.js:

var cocos2dApp = cc.Application.extend({
    config:document.querySelector('#cocos2d-html5')['c'],
    ctor:function (scene) {
        this._super();
        this.startScene = scene;
        cc.COCOS2D_DEBUG = this.config['COCOS2D_DEBUG'];
        cc.setup(this.config['tag']);
        cc.Loader.shareLoader().onloading = function () {
            cc.LoaderScene.shareLoaderScene().draw();
        };
        cc.Loader.shareLoader().onload = function () {
            cc.AppController.shareAppController().didFinishLaunchingWithOptions();
        };

        cc.Loader.shareLoader().preload([
            {type:"plist",src:"spritesheet.plist"}
        ]);
    },
    applicationDidFinishLaunching:function () {
        var director = cc.Director.getInstance();
        director.setDisplayStats(this.config['showFPS']);
        director.setAnimationInterval(1.0 / this.config['frameRate']);
        director.runWithScene(new this.startScene());

        return true;
    }
});
var myApp = new cocos2dApp(MyFifthAppScene);

 

There are only two changes here.  First we added spritesheet.plist to the preloader list.  plist files are short for Property List is a configuration format used in Mac/iOS development, but at the end of the day, it’s just data in an XML format.  This file was generated for us by TexturePacker and it describes the details and locations of each sprite within the spritesheet, indexed by the original file name.  The other difference is we pass MyFifthAppScene to our new cocos2dApp call.

 

Let’s take a look at ( a portion of ) the file spritesheet.plist:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
    <dict>
        <key>frames</key>
        <dict>
            <key>Walk_left00.png</key>
            <dict>
                <key>frame</key>
                <string>{{0,0},{128,96}}</string>
                <key>offset</key>
                <string>{0,0}</string>
                <key>rotated</key>
                <false/>
                <key>sourceColorRect</key>
                <string>{{0,0},{128,96}}</string>
                <key>sourceSize</key>
                <string>{128,96}</string>
            </dict>
            <key>Walk_left01.png</key>
            <dict>
                <key>frame</key>
                <string>{{128,0},{128,96}}</string>
                <key>offset</key>
                <string>{0,0}</string>
                <key>rotated</key>
                <false/>
                <key>sourceColorRect</key>
                <string>{{0,0},{128,96}}</string>
                <key>sourceSize</key>
                <string>{128,96}</string>
            </dict>
            <key>spritesheet.png</key>
            <dict>
                <key>frame</key>
                <string>{{0,960},{512,1024}}</string>
                <key>offset</key>
                <string>{0,0}</string>
                <key>rotated</key>
                <false/>
                <key>sourceColorRect</key>
                <string>{{0,0},{512,1024}}</string>
                <key>sourceSize</key>
                <string>{512,1024}</string>
            </dict>
        </dict>
        <key>metadata</key>
        <dict>
            <key>format</key>
            <integer>2</integer>
            <key>realTextureFileName</key>
            <string>spritesheet.png</string>
            <key>size</key>
            <string>{512,2048}</string>
            <key>smartupdate</key>
            <string>$TexturePacker:SmartUpdate:873706d2c1ad9f5b33d90eefda3a7084$</string>
            <key>textureFileName</key>
            <string>spritesheet.png</string>
        </dict>
    </dict>
</plist>

 

The details of the file aren’t important, Cocos2D does all of the heavy lifting for us.  Just be aware that it describes the general layout and name of each sprite within your spritesheet. Now let’s take a look at MyFifthApp.js where the majority of our changes occurred:

 

var MyFifthApp = cc.LayerColor.extend({
    sprite:null,
    spriteFrameNamePrefix:"Walk_left",
    spriteFrameIndex:0,
    init:function()
    {
        this._super();
        this.initWithColor(new cc.Color4B(0,0,0,255));
        var size = cc.Director.getInstance().getWinSize();

        var cache = cc.SpriteFrameCache.getInstance();
        cache.addSpriteFrames("spritesheet.plist", "srcSprites/spritesheet.png");

        this.sprite = cc.Sprite.createWithSpriteFrameName(this.spriteFrameNamePrefix + "00.png");
        this.sprite.setPosition(new cc.Point(300,300));
        this.sprite.setScale(3);
        this.addChild(this.sprite);

        this.setKeyboardEnabled(true);
        return this;
    },
    onKeyUp:function(e){},
    onKeyDown:function(e){
        if(e == cc.KEY.left || e == cc.KEY.right){
            var prevPrefix = this.spriteFrameNamePrefix;
            if(e == cc.KEY.left)
                this.spriteFrameNamePrefix = "Walk_left";
            else
                this.spriteFrameNamePrefix = "Walk_right";
            if(prevPrefix !== this.spriteFrameNamePrefix)
                this.spriteFrameIndex = 0;


            if(this.spriteFrameIndex > 18)
                this.spriteFrameIndex = 0;
            var indexAsString;
            if(this.spriteFrameIndex < 10)
                indexAsString = "0" + this.spriteFrameIndex.toString();
            else
                indexAsString = this.spriteFrameIndex.toString();

            this.removeChild(this.sprite);
            this.sprite  = cc.Sprite.createWithSpriteFrameName(
                this.spriteFrameNamePrefix + indexAsString + ".png"
            );

            this.sprite.setPosition(new cc.Point(300,300));
            this.sprite.setScale(3);
            this.addChild(this.sprite);
            this.spriteFrameIndex++;
        }
    }
});


MyFifthAppScene = cc.Scene.extend({
    onEnter:function(){
        this._super();
        var layer = new MyFifthApp();
        layer.init();
        this.addChild(layer);
    }
});

 

I wont bother going in to detail on MyFifthAppScene, we’ve done that enough times already.  In MyFifthApp we declare a sprite variable to point at the currently active sprite, then declare spriteFrameNamePrefix and spriteFrameIndex.  We access the sprites within the sprite sheet using the keys defined in the plist ( the file name ), of which there are 18 frames for walking left and right:

Walk_left00.png –> Walk_left18.png

Walk_right00.png –> Walk_right18.png

 

We will be building this key dynamically as we switch between sprites.

 

I am only going to cover the new stuff here, the rest should be pretty familiar at this point.  Cocos2D has a built in cache for Sprite frames ( in cocos2D a SpriteFrame is an individual sprite within the spritesheet, while a spritesheet is a SpriteFrames (<- note, plural ) ), the singleton SpriteFrameCache.  We add a new spritesheet to it with the call addSpriteFrames, passing in the name of the plist we preloaded earlier in main.js, as well as the path to the spritesheet graphic.

 

 

Speaking of which, this is spritesheet.png that TexturePacker generated:

spritesheet

We then get our sprite reference from the spritesheet with a call to createWithSpriteFrameName, passing in the key of the sprite specified in the plist.  In this case we are starting with Walk_left00.png.  We then position the sprite at 300,300, scale it to 3x it’s size and add it to our layer.  Finally we tell Cocos we want to handle keyboard events with a call to setKeyboardEnabled().

 

Next in the onKeyDown handler we check to see if the user has pressed ( and released ) the left or right arrow.  Depending on which direction we are walking, that determines which prefix to use for accessing the sprite sheet, left or right.  Next we check if the direction has changed since the last frame, if so, we start the frame index back to 0 to start the animation over.

 

Next we check to see if we’ve exceed our 18 frames, if we have we roll over to 0.  Next we check to see if our index is a single digit ( 0..9 ), in which case we pad it with a 0, so 1 becomes 01.  This is simply because that is how the source frames were named when I imported them to TexturePacker.  We remove our current ( about to be replaced ) animation frame from the layer. We then put our prefix, index and “.png” together to get the key to the next frame of animation, we access with using createWithSpriteFrameName again.  We position and scale the new sprite frame and add it to the scene to replace the previous frame.  Finally we advance to the next frame index.

 

 

Here is our game in action.  Be sure to click in the window to focus keyboard events, then use Left and Right arrows to walk:



This code wouldn’t run when I first deployed it live to my IIS server but worked fine in Apache!  The XML loader in CCSAXParser.js was failing, and it turned out to be a permissions issue.  When you deploy a new file extensions ( like plist ) to IIS, you need to be sure to add it as an allowed mime type!

 

For the record, there are actually better ways to handle animation that we will cover in a later tutorial. This tutorial does however illustrate how to load a sprite sheet and access individual sprites. You can download the entire archive here.

Programming , ,




Cocos2D HTML5 Tutorials source code updated to newest beta

11. August 2012

 

I just finished updating all of the cocos2D source code to the newest beta code ( as of today anyways ).  You can read about what was involved here or you can download the source code below.  I will be updating the text of each tutorial shortly, but in the meanwhile, you should be able to puzzling things out using just the source, the process stays pretty similar over all, even though there were extensive changes to the code.

 

Project Source Code

 

I will post again when the text of the tutorials are updated.

 

A quick question to all of you, is there any value in keeping the old tutorials and link to the new ones, or should I just overwrite the existing tutorials?  I can’t really think of a use for outdated tutorials, unless of course someone wanted to keep working with their old code base I suppose.

Programming, General , ,




Cocos2D HTML–Porting to the most recent version

10. August 2012

 

I received a comment from a Cocos2D-HTML developer saying that the API is complete for the upcoming beta release, so that I could update the tutorials on this site.  So today I jumped in to see exactly what changed and the answer is quite a bit!  In the process of updating the code to work with the newest Cocos2D code, I went through a great deal of pain tracking down those changes.  This post is to illustrate what has changed, so you don’t have to go through the same process.

 

The code being ported is the example code from this tutorial.

Changes:

 

Filename changes

 

I encountered a few filename changes ( which JavaScript can make exceedingly annoying to find ).  First off, a few files have had their casing changed, which you won’t notice you are working in Windows, but under Linux you will.  Mostly it’s a matter of files being renamed from cc to CC.  Additionally the folder keypad_dispatcher is now keyboard_dispacter, and CCKeypadDelegate.js and CCKeypadDispatcher.cs have been renamed to CCKeyboardDelegate.js and CCKeyboardDispatcher.cs respectively.

 

Function changes

 

ccp is now Point

ccc2/3/4/ is now Color2B,Color3B,Color4B

Layer.setIsRelativeAnchorPoint has been removed.

Director.sharedDirector() is now Director.getInstance();

setIsTouchEnabled() is simply isTouchEnabled()

setIsKeypadEnabled() is now isKeyboardEnabled()

 

Events have been renamed, for example:

ccTouchesEnded is now onTouchesEnded

 

The Keyboard delegate have renamed events from:

keyDown and keyUp to onKeyDown and onKeyUp

cc.key.[name] is now cc.KEY.[name]

cc.Touch ( the value passed to touch handlers ) has changed from .locationInView() to .getLocation()

cc.AudioManager.sharedEngine() is now cc.AudioEngine.getInstance();

cc.Log is now cc.log

 

Bootstrap change

 

This is where the biggest changes occurred.  The startup process is completely different.  First of all, library loading is no longer the responsibility of the  application, it is instead handled by jsloader.js.

 

This changes cocos2d.js massively:

cocos2d.js

(function () {
    var d = document;
    var c = {
        COCOS2D_DEBUG:2, //0 to turn debug off, 1 for basic debug, and 2 for full debug
        box2d:false,
        showFPS:true,
        frameRate:60,
        tag:'gameCanvas', //the dom element to run cocos2d on
        engineDir:'../cocos2d/',
        appFiles:['MyFirstApp.js']
    };
    window.addEventListener('DOMContentLoaded', function () {
        //first load engine file if specified
        var s = d.createElement('script');
        s.src = c.engineDir + 'platform/jsloader.js';
        d.body.appendChild(s);
        s.c = c;
        s.id = 'cocos2d-html5';
        //else if single file specified, load singlefile
    });
})();

If you haven’t seen this syntax before, it is basically an anonymous function that will execute at the end of declaration ( because of the () it ends in ).  The majority of what is going on here is the creation of a configuration file ‘c’, where a number of settings are, um, set.  tag is the name of the canvas element in your html file, engineDir is where you installed cocos2D to, appFiles are additional files you write ( in addition to your main.js ).  Basically every file you create needs to be added to this array.  It then adds an event that will be fired once the DOM has been populated, which will then create a new <SCRIPT> tag in your HTML document, then pass in the path to the jsloader, which will then load all of the cocos2D libraries, as well as anything your specified in the appFiles array.  Finally it stores the configuration settings in ‘c’ in the newly created script tag.

 

In a change I disagree with, the framework now forces you to add a file named main.js, which will be called by jsloader at the end of the loading process.  IMHO, this should be added as part of the configuration you set above.  This file replaced AppDelegate.js from the tutorials.  Let’s take a look at the new main.js:

var cocos2dApp = cc.Application.extend({
    config:document.querySelector('#cocos2d-html5')['c'],
    ctor:function (scene) {
        this._super();
        this.startScene = scene;
        cc.COCOS2D_DEBUG = this.config['COCOS2D_DEBUG'];
        cc.setup(this.config['tag']);
        cc.Loader.shareLoader().onloading = function () {
            cc.LoaderScene.shareLoaderScene().draw();
        };
        cc.Loader.shareLoader().onload = function () {
            cc.AppController.shareAppController().didFinishLaunchingWithOptions();
        };
        cc.Loader.shareLoader().preload([
        ]);
    },
    applicationDidFinishLaunching:function () {
        // initialize director
        var director = cc.Director.getInstance();
        director.setDisplayStats(this.config['showFPS']);
        director.setAnimationInterval(1.0 / this.config['frameRate']);
        director.runWithScene(new this.startScene());

        return true;
    }
});
var myApp = new cocos2dApp(MyFirstAppScene);

Things have changed a fair bit here, but the process is pretty much the same.  First thing to notice is it pulls the configuration data ‘c’ and stores it in the variable config, which is then used to setup cocos2D.  The next major thing to notice is we now create an instance of our app, and pass in the scene to start with. 

 

Now let’s take a look at the changes to MyFirstApp.cs:

var MyFirstApp = cc.Layer.extend({
    init:function()
    {
        this._super();

        var s = cc.Director.getInstance().getWinSize();

        var layer1 = cc.LayerColor.create(new cc.Color4B(255, 255, 0, 255), 600, 600);
        //layer1.setPosition(new cc.Point(s.width/2,s.height/2));
        //layer1.setIsRelativeAnchorPoint(true);
        layer1.setAnchorPoint(new cc.Point(0.5,0.5));

        
        var helloLabel = cc.LabelTTF.create("Hello world", "Arial", 30);
        helloLabel.setPosition(new cc.Point(s.width/2,s.height/2));
        helloLabel.setColor(new cc.Color3B(255,0,0));
        var rotationAmount = 0;
        var scale = 1;
        helloLabel.schedule(function()
            {
                this.setRotation(rotationAmount++);
                if(rotationAmount > 360)
                    rotationAmount = 0;
                this.setScale(scale);
                scale+= 0.05;
                if(scale > 10)
                    scale =1;
            });

        layer1.addChild(helloLabel);
        this.addChild(layer1);

        
        return true;
    }

});

var MyFirstAppScene = cc.Scene.extend({
    onEnter:function(){
        this._super();
        var layer = new MyFirstApp();
        layer.init();
        this.addChild(layer);
    }
})

 

Things have changed a bit here.  First we now extend a scene to create a new object MyFirstAppScene.  This in turn creates a layer and adds it to itself.  The onEnter event is called when the scene is activated ( via RunWithScene ).  Otherwise all of our changes are those described above ( function renames, removal, etc… ).

 

 

I hope that helps you port your existing code to the newest codebase.  All of the changes are improvements ( except perhaps the whole forced main.js thing, I still don’t like that ), so it is worth the pain.  I hope to get the tutorials all updated shortly, but unfortunately, these changes more or less will result in complete re-writes of 3 tutorials, so it might take a bit! Sad smile

Programming , ,




JavaScript Toddler Game Part 5: Adding a database

31. July 2012

 

I had intended to end this series on part 4, all I had left to do was add a layer of persistence because Heroku didn’t keep files for more than a few hours, which was a pretty heavy limitation.  So I looked into various storage options, and I ended up going with CouchDB.  The process was a bit more involved ( and interesting ) than I suspected, so I decided to add another post in the series.  This part covers setting up cloud based database, and changing our code to persist to the database.

 

First off, I went to IrisCouch.com and signed up for a free database.  They provide (free!) cloud hosted CouchDB installs, including a full Futon management system, pictured below:

image

 

You can use this interface to create new databases, configure security etc.  It’s a bit tricky at times to navigate, but it gets the job done.

 

I created a new database called firstthis, then immediately secured it ( by default your website is publically accessible to everyone! ).  CouchDB works by storing information in documents instead of tables in a traditional database.  Instead of updating individual fields, you update the data in your document then replace the entire thing.  Tracking the newest revision becomes incredibly important when working with CouchDB.  Perhaps most key of all, CouchDB adds two fields to your data, _id and _rev.  _rev represents the newest revision, while _id represents the unique key.  In our case, for our user settings, we are going to use their email address as the key.  We simply store the files variable from our script to the server.  Here is a sample of files stored in CouchDB ( shown in the iriscouch admin page ):

 

image

 

As you can see, its simply our JavaScript variable, with an _id and _rev added. 

 

When you sign up for IrisCouch, you are given a URL where your database server is located, in the form of yoursite.iriscouch.com.

 

Now let’s take a look at the code differences.  It is pretty thoroughly documented ( combined with the above explaination ), so I won’t go into a lot of detail.  CouchDB is accessed using REST requests, but I instead used a node library Nano for access.  This was a bit of a double edged sword, as it took away a great deal of the complexity and grunt work, however it also removed me a step away from the well documented CouchDB. 

 

All of the code changes are in server.js

var express = require('express'), server = express.createServer(), im = require('imagemagick'), nano = require('nano')('http://Serapth:meat32ball@gfs.iriscouch.com'), db_name = "firstthis", db = nano.use(db_name), userEmail = 'mike@gamefromscratch.com', fs = require('fs'), files = { files:{}}; // Helper functions for getting and inserting docs in couchDB using nano function get_doc(docName,res){ db.get(docName,function(err,body){ if(!err) { res(body); } }); }; // There is no update in CouchDB. Just inserts, inserts and more inserts. // If you don't have the most current rev ( or it isn't a new insert ), an error (HTTP409) will occur. // TODO: Real error handling, attempt to get latest file, get it's rev, then try inserting again function insert_doc(doc,docname, tried) { db.insert(doc,docname, function (error,val,newval) { if(error) { return console.log(error); } // The insert will result in an updated rev, update our local files var to the most current rev. return files._rev = val.rev; }); } // Setup server static paths server.use('/cocos2d', express.static(__dirname + '/cocos2d') ); server.use('/cocosDenshion', express.static(__dirname + '/cocosDenshion') ); server.use('/classes', express.static(__dirname + '/classes') ); server.use('/resources', express.static(__dirname + '/resources') ); // Install the bodyParser middleware, which enables form data to be parsed when an upload occurs. server.use(express.bodyParser()); // Handle requests for / by returning index.html server.get('/', function(req,res){ res.sendfile('index.html'); console.log('Sent index.html'); }); // Handle requests for /settings by returning settings.html server.get('/settings',function(req,res){ res.sendfile('settings.html'); console.log('Send settings.html'); }); // Handle requests for images will be the form of site.com/image/imagename.png // Fetchs the image data from CouchDB and returns to user server.get('/image/:name', function(req,res){ if(files.files[req.params.name]) { res.contentType(files.files[req.params.name].contentType); db.attachment.get(userEmail + "/" + files.files[req.params.name].name,files.files[req.params.name].name).pipe(res); } }); // Uses ImageMagick to get image dimensions and return them as JSON data // This is to work around the requirement for Cocos2D sprites to know dimensions before loading image server.get('/imageSize/:name',function(req,res){ im.identify(files.files[req.params.name].path,function(err,features){ if(err) throw err; else res.json({ "width":features.width, "height":features.height }); }); }); // This gets the photo data, which is contained in our files variable. Simply return it JSON encoded server.get('/getPhotos', function(req,res){ res.json(files.files); }); // Erase all images : TODO: Remove images from database as well!!!! server.get('/clearAll', function(req,res){ files.files = {}; res.statusCode = 200; res.send(""); }) // Unfortunately there is no easy way to tell when a multi file upload is complete on the server, On('end') isnt called // Therefore we call /doneUpload from the client once we are done uploading. // Once we are done uploading files, we save our updated Files var up to couchDB, then get it again so it again immediately to have the most current rev server.get('/doneUpload', function(req,res){ insert_doc(files,userEmail,files._rev,0); get_doc(userEmail,function(res) { files = res; }); res.statusCode = 200; res.sendfile('settings.html'); }) server.post('/upload',function(req,res){ // This method is going to be called for each attached file. // Add the file details to files.files[] with the key set to the filename files["files"][req.files.Filedata.name] = { "name":req.files.Filedata.name, "path":req.files.Filedata.path, "size":req.files.Filedata.size, "contentType":req.files.Filedata.type, "description":req.body.description }; // Now read the file from disk and insert the file data in our CouchDB as an attachment named "emailAddress/filename.png" fs.readFile(req.files.Filedata.path,function(err,data){ if(!err){ db.attachment.insert(userEmail + "/" + req.files.Filedata.name,req.files.Filedata.name,data,req.files.Filedata.type , {}, function(err,body){ console.log(body); res.statusCode = 200; res.send(""); }); } }); }); server.listen(process.env.PORT || 3000); // Check the couchDB for an entry keyed to the users email address. If one exists, copy it into our files var get_doc(userEmail,function(results){ if(results){ files = results; } });

 

Now when you upload images, they will be stored to your CouchDB database on IrisCouch.  When you restart Node, it will automatically grab and populate files from the database. Note, the application from parts 1-4 aren’t updated to use this new code.

Keep in mind, this code isn’t production ready… error handling is sparse, there is no authentication, it would be easy to exploit with a DoS attack for example.  However, if you are interested in storing data from your Node App in a CouchDB database, I hope this sample was useful.

Programming , , ,