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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[{ | |
"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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// <reference path="//Microsoft.WinJS.1.0/js/base.js" /> | |
/// <reference path="//Microsoft.WinJS.1.0/js/ui.js" /> | |
(function () { | |
"use strict"; | |
WinJS.Namespace.define("Zoo", { | |
Animal: WinJS.Class.define( | |
//constructor | |
function () { | |
this._name = ""; | |
this._age = ""; | |
this._isHungry = false; | |
this._hoursSinceLastFeed = 0; | |
}, | |
//methods | |
{ | |
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 it has been 4 hours since last feed | |
//the animal is hungry for sure :) | |
if (newValue > 4) { | |
this._isHungry = true; | |
} | |
else { | |
this._isHungry = false; | |
} | |
}, | |
}, | |
//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 a Bindable object | |
return new WinJS.Binding.as(newAnimal); | |
}, | |
loadZoo: function (uri) { | |
//IMPORTANT TO RETURN THE PROMISE | |
return Windows.Storage.StorageFile.getFileFromApplicationUriAsync(uri) | |
.then(function (file) { | |
return Windows.Storage.FileIO.readTextAsync(file) | |
.then(function (textFromFile) { | |
var myParsedJsonData = JSON.parse(textFromFile); | |
//this will store all the new animals transferred to zoo | |
var zoo = new Array(); | |
if (myParsedJsonData) { | |
myParsedJsonData.forEach(function (newObject) { | |
var newAnimal = Zoo.Animal.buildAnimal(newObject); | |
zoo.push(newAnimal); | |
}); | |
} | |
return zoo; | |
}); | |
}); | |
} | |
})//end WinJS.Class.define | |
}); | |
})(); |
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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 a Bindable object | |
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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
loadZoo: function (uri) { | |
//IMPORTANT TO RETURN THE PROMISE | |
return Windows.Storage.StorageFile.getFileFromApplicationUriAsync(uri) | |
.then(function (file) { | |
return Windows.Storage.FileIO.readTextAsync(file) | |
.then(function (textFromFile) { | |
var myParsedJsonData = JSON.parse(textFromFile); | |
//this will store all the new animals transferred to zoo | |
var zoo = new Array(); | |
if (myParsedJsonData) { | |
myParsedJsonData.forEach(function (newObject) { | |
var newAnimal = Zoo.Animal.buildAnimal(newObject); | |
zoo.push(newAnimal); | |
}); | |
} | |
return zoo; | |
}); | |
}); | |
} |
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 2 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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
args.setPromise(WinJS.UI.processAll().then(function() { | |
//build up the URL for the file added to the project | |
var url = new Windows.Foundation.Uri("ms-appx:///zoo.json"); | |
//this will store the imported data | |
var myNewAnimals = new Array(); | |
//invoke the static method which loads the file | |
//and creates Animal objects from json data | |
//THE METHOD RETURNS A PROMISE!!! | |
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.