As I have been looking around in Windows Store and checking out what kind of apps exist I saw that there a lot of apps which have their content installed on the machine and are not loading it from the web. So even if you don't have internet access you can fully use the app. I though to write a blog post about loading a file in a Windows 8 app and creating objects from the data stored in the file. I will focus on json data, because it's the most commonly used format.
I will present a very simple project (it will not have any change in the UI), but I will try to present a structural guideline how the data and objects can be logically grouped in a Windows 8 WinJS app.
You can find the sample project on github.
Let's say we have our zoo, and some animals were transferred from another zoo. All the data about the animals is sent via a
zoo.json file, which looks like this:
[{
"name" : "King",
"age" : 5,
"hoursSinceLastFeed" : 3
},
{
"name" : "Geeko",
"age" : 2,
"hoursSinceLastFeed" : 12
},
{
"name" : "Nerdy",
"age" : 12,
"hoursSinceLastFeed" : 1
},
{
"name" : "Goocy",
"age" : 4,
"hoursSinceLastFeed" : 6
}]
This is the most simple set of information about an animal, Name, Age and the hours passed since the last feeding - probably this is the most important
one :). This zoo.json file is which we would like to import. So, what I have done was, created a new WinJS application from the Blank Template:
|
Create a new Blank WinJS project |
The next step was, that I added a new JavaScript file, called Animal.js.
(function () {
"use strict";
WinJS.Namespace.define("Zoo", {
Animal: WinJS.Class.define(
function () {
this._name = "";
this._age = "";
this._isHungry = false;
this._hoursSinceLastFeed = 0;
},
{
getName: function () { return this._name; },
setName: function (newValue) { this._name = newValue; },
getAge: function () { return this._age; },
setAge: function (newValue) { this._age = newValue; },
isHungry: function () { return this._isHungry; },
getHoursSinceLastFeed: function () { return this._hoursSinceLastFeed; },
setHoursSinceLastFeed: function (newValue) {
this._hoursSinceLastFeed = newValue;
if (newValue > 4) {
this._isHungry = true;
}
else {
this._isHungry = false;
}
},
},
{
buildAnimal: function (model) {
var newAnimal = new Zoo.Animal();
if (model.hasOwnProperty("name")) {
newAnimal.setName(model.name);
}
if (model.hasOwnProperty("age")) {
newAnimal.setAge(model.age);
}
if (model.hasOwnProperty("hoursSinceLastFeed")) {
newAnimal.setHoursSinceLastFeed(model.hoursSinceLastFeed);
}
return new WinJS.Binding.as(newAnimal);
},
loadZoo: function (uri) {
return Windows.Storage.StorageFile.getFileFromApplicationUriAsync(uri)
.then(function (file) {
return Windows.Storage.FileIO.readTextAsync(file)
.then(function (textFromFile) {
var myParsedJsonData = JSON.parse(textFromFile);
var zoo = new Array();
if (myParsedJsonData) {
myParsedJsonData.forEach(function (newObject) {
var newAnimal = Zoo.Animal.buildAnimal(newObject);
zoo.push(newAnimal);
});
}
return zoo;
});
});
}
})
});
})();
In this js file I created the object creation and parsing logic. This is the main file which this blog is about. I started the whole process
with declaring a self executing function (if you don't know what are those, here are the
first
second
and third
hints which Google gave back when searching for JavaScript self executing functions). I added the "use strict" line,
see my previous blog posts why this should be used.
After that comes the interesting part. I said,
I will create a new object, inside the namespace Zoo, and that object contains one property, Animal. The Animal is defined as a WinJS.Class
using the WinJS.Class.define() method.
As it can be seen in the code, this method takes 3 arguments, the first one is a function, this serves as the constructor for the class.
The second and the third parameters are objects. The object defined as the second parameter defines the methods which the newly created
Animal objects will have:
As you can see, Visual Studio 2012 offers a very good intellisense support for JavaScript code. In the constructor I assigned some members
to this(so later I can access them). After the definition of the constructor, I defined the methods which will help
me in maintaining data integrity and encapsulation for this javascript object. There are defined get and set methods for each property.
The isHungry member is treated separately, this has some logic behind the scenes. I said, that if more than
4 hours passed since the last feeding the animal is hungry. All this "complex" logic is implemented inside
the setHoursSinceLastFeed() method.
In the third parameter of the WinJS.Class.define() method I specified 2 static methods:
buildAnimal: function (model) {
var newAnimal = new Zoo.Animal();
if (model.hasOwnProperty("name")) {
newAnimal.setName(model.name);
}
if (model.hasOwnProperty("age")) {
newAnimal.setAge(model.age);
}
if (model.hasOwnProperty("hoursSinceLastFeed")) {
newAnimal.setHoursSinceLastFeed(model.hoursSinceLastFeed);
}
return new WinJS.Binding.as(newAnimal); },
The buildAnimal function is a simple one. All it does is, checks the model/data passed in as parameter, creates a new Animal object, tries to set it's values from the model passed to the function and returns a bindable object with the help of WinJS.Binding.as() method.
The loadZoo function contains all the code which this blog post is created for. Here is the code:
loadZoo: function (uri) {
return Windows.Storage.StorageFile.getFileFromApplicationUriAsync(uri)
.then(function (file) {
return Windows.Storage.FileIO.readTextAsync(file)
.then(function (textFromFile) {
var myParsedJsonData = JSON.parse(textFromFile);
var zoo = new Array();
if (myParsedJsonData) {
myParsedJsonData.forEach(function (newObject) {
var newAnimal = Zoo.Animal.buildAnimal(newObject);
zoo.push(newAnimal);
});
}
return zoo;
});
});
}
The purpose of this function is, to load the file with the json data and create Animal type objects from it. First thing to notice, the function starts with the return statement, this is important and I'll get back to it. The function receives a URL to the file which contains the data. The line
Windows.Storage.StorageFile.getFileFromApplicationUriAsync()
accesses the file asynchronously. The method getFileFromApplicationUriAsync()
returns a promise, which is chained with the
then()
method. The
.then()
method can have 3 parameters (more specifically functions). The first one should be the one which is called when the async operation is finished. The second function parameter stands in the role of an error handler. It is not mandatory to specify this parameter. If you have multiple .then() methods chained, if an error occurs this is escalated till in one of the
then()
methods an error handler is passed to.
Another scenario is when the invoke chain ends with a
.done()
, this can also have an error handler as second parameter. If for some reason the error handler function is not specified at all and an error occurs the Windows 8 app will crash (of course if no other error handling mechanism is added). The third parameter of the
then()
method serves as a progress indicator, more on this in a future post. The getFileFromApplicationUriAsync()
returns a promise, the result (file object) of this promise is passed to the function inside the
then()
method. This object is then accessed and the method WinJS.Storage.FileIO.readTextAsync()
reads all the text from the file. This method on it's own returns another promise, and the text read from the file is passed to the function specified for the
onCompleted
parameter of the then() method. This text is then parsed with the
JSON.parse()
method. This creates objects from the text passed to it, we can say, that "deserializes" the data. Afterwards a new array is built for results. For each item returned by the
JSON.parse()
method a new Animal object is created using the buildAnimal
static method.
That was all the "science"! We know have a new, fully functional JavaScript class, which implements the factory design pattern and has the feature to load it's own datatype from files and build up it's own objects.
There remained
two things to mention. The first is related to the URL passed to the loadZoo
function. The URL has to be created with Windows.Foundation.Uri()
method, see the code below:
args.setPromise(WinJS.UI.processAll().then(function() {
var url = new Windows.Foundation.Uri("ms-appx:///zoo.json");
var myNewAnimals = new Array();
Zoo.Animal.loadZoo(url).done(
function (result) {
myNewAnimals = result;
myNewAnimals.forEach(function (animal) {
console.log("Name: " + animal.getName() + ", Age: " +
animal.getAge() + ", IsHungry: " + animal.isHungry() +
", Hours since Last feed: " + animal.getHoursSinceLastFeed());
});
},
function (error) {
var messDialog = new Windows.UI.Popups.MessageDialog(error);
messDialog.showAsync();
});
}));
The parameter given to the Windows.Foundation.Uri()
method is a little strange. I will not go in-depth why
this has to be written like this, in a future post I will try to present this also. You can figure out that the last / from
the ms-appx:///zoo.json points to the root of the local project and zoo.json is the name of the processed file.
So if you search for ms-appx:// on the web you'll get a lot of details.
Remember the loadZoo
method started with a return statement? That was done like that,
because all the operations done were executed asynchronously, so I had to be able to chain my logic to the async operations
and had to execute it after all the other async logic was executed. So, when the
Zoo.Animal.loadZoo(url)
finished it's execution in the done() method I write the processed data to the Visual Studio console and it looks like this:
You can do anything with these objects, bind them to controls on UI, serve for contracts (Search or Share) and so on.
Thanks for reading the post, hope you enjoyed it.