I had been looking at improving my JavaScript skills and learning WinJS at the same time. One of the key things I wanted to do was work better with larger projects and larger numbers of JavaScript files.
RequireJS is great for that, but I had considerable trouble getting it to work with WinJS. When I did get it working I then had trouble getting it to work properly.
RequireJS is expected to be initialised with an include reference in the page html with a data-main tag, but this doesn't really work with the WinJS app lifecycle, so it took a bit of work to get it behaving the way I expected.
Eventually I got it working, and working with async initialisation code which was a key part of the initialisation process in my sample app.
So the basics of getting RequireJS working in WinJS is the following - I have probably made a million big JavaScript no-no's here, but as I said, still learning.
default.html - add
<script src="/js/require.js" ></script>
before default.js
default.js - initialise requireJS and include whatever dependencies you want to use in your startup code.
app.addEventListener("activated", function (args) {
if (args.detail.kind === activation.ActivationKind.launch) {
if (args.detail.previousExecutionState !== activation.ApplicationExecutionState.terminated) {
// TODO: This application has been newly launched. Initialize
// your application here.
} else if (args.detail.previousExecutionState !== activation.ApplicationExecutionState.closedByUser) {
// TODO: This application has been launched after a requested shutdown. Initialize
// your application here.
} else {
// TODO: This application has been reactivated from suspension.
// Restore application state here.
}
if (app.sessionState.history) {
nav.history = app.sessionState.history;
}
//RequireJS Initialisation here
require.config({
baseUrl: '/js'
});
//this creates a promise that calls complete() when the initAsync promises finish
//all this code happens within the require context so we are guaranteed that the dependencies are resolved before the promise is resolved
//the initAsync methods return promises that load data from internal json data files
//persistence.loadState is synchronous code so doesn't return a promise
var loadPromise = new WinJS.Promise(function (complete) {
require([
'persistence',
'game/staticdata/itemClassStore',
'game/staticdata/itemStore',
'game/gamestate/world',
'game/gamestate/characters',
'game/classes/character'
], function (persistence, itemClassStore, itemStore, world, characters, character) {
itemClassStore.initAsync()
.then(function () { return itemStore.initAsync(); })
.then(
function () {
var data = persistence.loadState();
world.fromJson(data);
complete();
}
);
});
});
//args.setPromise is a function on the activate event which allows you to wait for promises to complete before continuing
args.setPromise(
loadPromise
.then(function () { return WinJS.UI.processAll(); })
.then(
function () {
if (nav.location) {
nav.history.current.initialPlaceholder = true;
return nav.navigate(nav.location, nav.state);
} else {
return nav.navigate(Application.navigator.home);
}
}
)
);
}
});
xPage.js - resolve your dependencies in the page ready function with a call to require(), and perform your page initialisation as per normal.
///
///
///
///
///
(function () {
"use strict";
WinJS.Namespace.define("CharacterSelect", {
CharacterSelectViewModel: WinJS.Class.define(
//the viewmodel constructor takes the injected requireJS dependency from the page ready code.
function (world) {
this._charactersModule = world.characters;
this._itemsDataSource = new WinJS.Binding.List(this._charactersModule.characters);
this._inventoryDataSource = new WinJS.Binding.List(null);
},
{
_charactersModule: null,
_itemsDataSource: null,
_inventoryDataSource: null,
_selectedCharacterId: 0,
selectedCharacterId: {
get: function () {
return this._selectedCharacterId;
},
set: function (value) {
this._selectedCharacterId = value;
if (value == 0) {
this._inventoryDataSource = new WinJS.Binding.List(null);
} else {
this._inventoryDataSource = new WinJS.Binding.List(this._charactersModule.get(value).inventory.items);
}
this._getObservable().notify("selectedCharacterId", value);
this._getObservable().notify("inventoryDataSource", this._inventoryDataSource);
}
},
listDataSource: {
get: function () {
return this._itemsDataSource;
}
},
inventoryDataSource: {
get: function () {
return this._inventoryDataSource;
}
},
addNew: function () {
WinJS.Navigation.navigate("/pages/newCharacter/newCharacter.html");
},
deleteCharacter: function (that) {
that._charactersModule.deleteCharacter(this.selectedCharacterId);
that._itemsDataSource = new WinJS.Binding.List(this._charactersModule.characters);
that._getObservable().notify("listDataSource", that._itemsDataSource);
}
},
{}),
});
WinJS.UI.Pages.define("/pages/characterSelect/characterSelect.html", {
// This function is called whenever a user navigates to this page. It
// populates the page elements with the app's data.
ready: function (element, options) {
require(
[
'persistence',
"appbar",
"game/gamestate/world"
],
function (persistence, appbar, world) {
//normal page initialisation code - all dependency modules are initialised at this point
//create view model (passing in resolved dependencies) and bind to relevant events
//(WinJS only has 1-way bindings and can't declaratively bind to button events that i can see)
var section = element.querySelector("section");
var viewModel = new CharacterSelect.CharacterSelectViewModel(world);
var observableviewModel = WinJS.Binding.as(viewModel);
WinJS.Binding.processAll(section, observableviewModel);
document.getElementById("cmdCreate").addEventListener("click", observableviewModel.addNew, false);
document.getElementById("cmdDeleteCharacter").addEventListener(
"click",
function(){
observableviewModel.deleteCharacter(observableviewModel);
persistence.saveState();
}
,
false
);
document.getElementById("characters").winControl.onselectionchanged = function (ev) {
var selection = document.getElementById("characters").winControl.selection;
if (selection.getItems()._value.length > 0) {
viewModel.selectedCharacterId = selection.getItems()._value[0].data.id;
appbar.characterSelectSelected();
} else {
viewModel.selectedCharacterId = 0;
appbar.characterSelectDeSelected();
}
};
appbar.characterSelectInit();
}
);
},
unload: function () {
// TODO: Respond to navigations away from this page.
},
updateLayout: function (element, viewState, lastViewState) {
///
// TODO: Respond to changes in viewState.
}
});
})();