Tuesday, December 25, 2012

Reading and Writing a File in Javascript for Win 8

So one of the most difficult tasks facing someone who is looking to create a Win 8 UI App using Javascript is how to persist their data. I really don't think this SHOULD be the case, but almost all the examples out there either presume you are using one of the dialects of C (# or ++ primarily), or point you to the "File Picker". The problem with the File Picker is that you lose control over what the file is named and where it is placed. So that the user can gum things up beyond redemption without meaning to do so, but will surely give you the "wonderful" review afterward.

What few examples I could find out there for Javascript that did otherwise all pointed to the use of the "documentLibrary" capability to store the file in your "My Documents" directory. I personally think this is the most sensible location, but have since discovered that Microsoft will not allow an app that is not submitted by a "Company" account to use this capability.

So what is left for the individual app programmer? The LocalFolder option. On the plus side, it lets you still write the save file programmatically and therefore control the name and contents. On the minus side, if you have to ever debug... well, good luck figuring out where it might be on an RT device.

In any case, let's look at some code examples!

First off, let's setup an array with our game's variables:

   var myStats = new Array();

Pretty standard Javascript so far. Each of the items will be a game variable that is stored and updated throughout the game. The array needs to get those values before saving them, so let's setup a function for that:
   function doPopSave() {
    myStats[0] = myTitle;
    myStats[1] = currPage;
    myStats[2] = currStep;
    myStats[3] = currTune;
    myStats[4] = musicon;
    myStats[5] = gameComplete;
...
    myStats[23] = myDisplay;
   }

Now that we have the array with our current game state, we need to write it. One thing to keep in mind with JS in Win8 is that your reads and writes are Asynchronous. Which means that main thread of your program can resume running even while the read or write thread is still working. So you have to make sure you are doing your read or write within the same function. So let's look at a write first:

                Windows.Storage.ApplicationData.current.localFolder.createFileAsync("Mysave01.sav", Windows.Storage.CreationCollisionOption.replaceExisting).done(
                    function (file) {
                        save01File = file;
                        Windows.Storage.FileIO.writeLinesAsync(save01File, myStats).done(
                            function () {
                            },
                        function (error) {
                            WinJS.log && WinJS.log(error, "sample", "error");
                            errCount++;
                        });
                    },
                    function (error) {
                        WinJS.log && WinJS.log(error, "sample", "error");
                    });
                if (errCount == 0) {
                    fileMenuTitle.text = "File Saved - Click Cancel";
                    fileMenuTitle.text = fileMenuTitle.text + "\nto Return";
                    fileMenuTitle.color = "green";
                    save01Exists = 2;
                }
                else {
                    fileMenuTitle.text = "There was an error saving that file.";
                    fileMenuTitle.text = fileMenuTitle.text + "\nPlease Make Another Selection.";
                    fileMenuTitle.color = "firebrick";
                }

So what we are doing above is that we are writing a file called "Mysave01.sav" into the same folder where our application was installed. We have also specified that if the file already exists, that we want to overwrite it. We then use "Windows.Storage.FileIO.writeLinesAsync(save01File, myStats).done" to write out our array as a series of lines into the file we created. The rest of the code after that is designed to log any errors to the default log, as well as to use a string that we display later to let the user know if there was an issue.

OK, so we have our file saved, and later on we decide we want to read it in. So that looks like:
                    Windows.Storage.FileIO.readLinesAsync(save01File).done(
                        function (lines) {
                            lines.forEach(function (line) {
                                myStats[tmpCount] = line;
                                if (tmpCount == 23) {
                                    doPopLoad();
                                }
                                tmpCount++;
                            });
                        },
                        function (error) {
                            errCount++;
                            WinJS.log && WinJS.log(error, "sample", "error");
                        });
                    if (errCount == 0) {
                        fileMenuTitle.text = "File Loaded";
                        fileMenuTitle.text = fileMenuTitle.text + "\nClick Cancel to Return or \nSelect Another File \nto Load";
                        fileMenuTitle.color = "green";
                    }
                    else {
                        fileMenuTitle.text = "There was an error loading that file.";
                        fileMenuTitle.text = fileMenuTitle.text + "\nPlease Make Another Selection.";
                        fileMenuTitle.color = "firebrick";
                    }

The key here is that I know how many values are in my array, and so when I have read the last line of the file, I call a function that will update the array. This is complicated by the fact that when I write the file, I am writing an ASCII representation of the values in the array. Here are a couple portions of that function to show you what I mean:
    function doPopLoad() {
        myTitle = myStats[0];
        currPage = +myStats[1];
        currStep = +myStats[2];
        if (myStats[3] == "AS-Acoustic2.mp3") {
            currTune = LindaTheme;
        }
        else if (myStats[3] == "AT Theme -Back To The Wood.mp3") {
            currTune = GameTheme;
        }

Since "myTitle" is a string anyway, I can just assign directly. However currPage and currStep are numbers, so I have to force the conversion using the "+" operator. Finally, I have a variable that stores the actual name of my music files. However my game logic is looking for "LindaTheme" rather than "AS-Acoustic2.mp3". So I need to make sure I set things up to use the right representation in my load logic.

Hopefully the above will help a few other folks looking to go the Javascript route to create Win 8 Apps.

Post a Comment