Background
Gone are the days when user’s only access their software from one device. Everyone has a smart phone, tablet, and full machine (laptop or desktop), or at least it feels that way. One thing is sure, if a user has a Windows 8 PC and a Windows 8 tablet, they will be accessing your apps from more than one piece of hardware. And they expect to be able to change application settings on one machine and have it (magically) appear on all of the other devices.
Prior to Windows 8/8.1, if you wanted to enable users to save settings across machines, you needed to work some magic (or at least expend some elbow grease). Of course the multitude of cloud providers makes this easier, but you still need to create the cloud service and the sync framework between a user’s machines. Fortunately, Windows 8 introduced the ApplicationData container that exposes Local and Roaming Settings. Local Settings are applied only to the current device. Roaming Settings are carried from device to device as long as the same user logs into their Live account on each device.
Related Posts:
This is part three of a series:
- Setting HTML Styles with Binding in Windows 8 WinJS Apps
- Creating Custom Application Events in WinJS
- Store and Share Settings in Windows 8 HTML/WinJS Apps (this post)
The Windows 8 Settings Framework
As mentioned above, Windows 8 introduced a settings framework for storing small bits of data for your application. This framework is not meant to replace application data storage, but is ideal for storing items such as user preferences and other application settings. Ironically, the namespace that contains this framework is Windows.Storage.ApplicationData, as we shall soon see.
Using the Settings Framework
To get started using the Settings Framework, add a local variable for Windows.Storage. Additionally, add a local variable for the settings themselves. There are two choices, “roamingSettings” and “localSettings”. In Listing 1, I have selected to use the roaming settings.
var storage = Windows.Storage;
var settings = storage.ApplicationData.current.roamingSettings;
Listing 1 – Local variables for Windows Storage and Roaming Settings
From a developer perspective, they are both used the same. They are comprised of a simple dictionary, each name limited to 255 characters, each setting value limited to 8K bytes. There are also composite settings (in the Windows.Storage namespace) that are essentially dictionaries themselves and can be used to store complex values as a single setting. Composite settings are limited to 64K bytes in size. If you are going to use composite settings, it’s best to use two separate variables since the composite type is in the Windows.Storage namespace.
To save a setting, simple assign the value to the settings dictionary as in Listing 2
settings.values["textWeight"] = "bold";
Listing 2 – Storing the “textWeight” setting
When getting the value back out, it’s a good idea to code defensively in case the setting doesn’t exist in the dictionary yet. In Listing 3, the textDecoration property on the model is being set to the “textDecoration” setting if it exists, or “normal” if it isn’t defined.
Model.textSettings.textDecoration = settings.values["textDecoration"] || “
normal
”;
Listing 3 – Retrieving the settings
That’s all you need to do, as Windows 8 and Microsoft Live takes care of the rest.
Roaming Settings From a User’s Perspective
From a user perspective, local vs. roaming settings behave very differently. Local settings are only stored on the current hardware. Roaming settings sync through the cloud (based on the user’s Live ID) every few minutes (the exact timing is up to Microsoft, but my anecdotal evidence has shown it takes anywhere from 10 to 20 minutes for the settings to sync.
This timing is perfect for most scenarios, since normal users typically don’t use multiple machines running the same app at the same time (that’s the domain for geeks like me). A user spends some time in your app, changes some settings (which then get synced to the cloud). The user then closes the app, goes home (or to work or where ever), opens up your app on a different device, and the settings are updated on the new device.
Updating the Sample App
To save the text settings in our sample from the previous posts, we only need to update one file – our model. Open uiSettings.js and after the “use strict” statement, add a single variable for the roamingSettings (since I am not using the composite setting container), as in Listing 4.
var settings = Windows.Storage.ApplicationData.current.roamingSettings;
Listing 4 – Adding the roamingSettings local variable
Saving the Settings
The next step is to add (or update) the settings when the model is updated. The updated functions for setTextIsBold, setTextIsUnderlined, and setTextIsItalic is shown in Listing 5.
setTextIsBold: function (val) {
if (val) {
settings.values["textWeight"] = Model.textSettings.textWeight = bold;
}
else {
settings.values["textWeight"] = Model.textSettings.textWeight = normal;
}
WinJS.Application.queueEvent({ type: "TextSettingsChanged" });
},
setTextIsUnderlined: function (val) {
if (val) {
settings.values["textDecoration"] = Model.textSettings.textDecoration = underline;
}
else {
settings.values["textDecoration"] = Model.textSettings.textDecoration = normal;
}
WinJS.Application.queueEvent({type:"TextSettingsChanged"});
},
setTextIsItalic:function(val) {
if (val) {
settings.values["textFontStyle"] = Model.textSettings.textFontStyle = italic;
}
else {
settings.values["textFontStyle"] = Model.textSettings.textFontStyle = none;
}
WinJS.Application.queueEvent({ type: "TextSettingsChanged" });
},
Listing 5 – Saving the settings when the model is updated.
Loading the Settings
Create a function named “getSettings” in the UISettings namespace. In this function, set the properties of the model based on the settings, also supplying default values in case the settings don’t exist yet. This is shown in Listing 5.
getSettings: function () {
Model.textSettings.textDecoration = settings.values["textDecoration"] || normal;
Model.textSettings.textFontStyle = settings.values["textFontStyle"] || none;
Model.textSettings.textWeight = settings.values["textWeight"] || normal;
},
Listing 6 – Update the model based on the settings
Finally, call the getSettings function at the end of the self execution function to make sure the getSettings function gets called. The entire uiSettings.js file is shown in Listing 6.
/// <reference path="//Microsoft.WinJS.1.0/js/ui.js" />
/// <reference path="//Microsoft.WinJS.1.0/js/base.js" />
(function () {
"use strict";
var settings = Windows.Storage.ApplicationData.current.roamingSettings;
var normal = "normal";
var none = "none";
var bold = "bold";
var italic = "italic";
var underline = "underline";
WinJS.Namespace.define("Model", WinJS.Binding.as({
textSettings: {
textWeight: normal,
textFontStyle: normal,
textDecoration: none,
},
}));
WinJS.Namespace.define("UISettings", {
setTextIsBold: WinJS.Utilities.markSupportedForProcessing(function (val) {
if (val) {
settings.values["textWeight"] = Model.textSettings.textWeight = bold;
}
else {
settings.values["textWeight"] = Model.textSettings.textWeight = normal;
}
WinJS.Application.queueEvent({ type: "TextSettingsChanged" });
}),
setTextIsUnderlined: WinJS.Utilities.markSupportedForProcessing(function (val) {
if (val) {
settings.values["textDecoration"] = Model.textSettings.textDecoration = underline;
}
else {
settings.values["textDecoration"] = Model.textSettings.textDecoration = normal;
}
WinJS.Application.queueEvent({ type: "TextSettingsChanged" });
}),
setTextIsItalic: WinJS.Utilities.markSupportedForProcessing(function (val) {
if (val) {
settings.values["textFontStyle"] = Model.textSettings.textFontStyle = italic;
}
else {
settings.values["textFontStyle"] = Model.textSettings.textFontStyle = none;
}
WinJS.Application.queueEvent({ type: "TextSettingsChanged" });
}),
getSettings: function () {
Model.textSettings.textDecoration = settings.values["textDecoration"] || normal;
Model.textSettings.textFontStyle = settings.values["textFontStyle"] || none;
Model.textSettings.textWeight = settings.values["textWeight"] || normal;
},
});
UISettings.getSettings();
})();
Listing 6 – uiSettings.js
Run the app and turn on all three text properties. Close the app and run it again. This time all of the text shows up Bold, Italic, and Underlined. However, the ToggleSwitches load in the “Off” position. To fix this, we need to bind the Checked property of
Binding the Toggle Switch Values
We can’t bind the ToggleSwitched directly to the model, since checked value of the ToggleSwitch control is a boolean value, and the model values are strings that represent the different style values. To bind the different values, there are a couple of options. The first is to create a BindingConverter. The other is to take advantage of the WinJS binding capabilities and update boolean values on the model when the text settings properties change. For this sample, I’m going to show how to use the binding framework.
Expanding the Model
Open uiSettings.js and add three properties to the model – textIsBold, textIsItalic, and textIsUnderlined. Adding them to our existing model makes them observable. The new model is shown in Listing 7.
WinJS.Namespace.define("Model", WinJS.Binding.as({
textSettings: {
textWeight: normal,
textFontStyle: normal,
textDecoration: none,
textIsBold: false,
textIsItalic: false,
textIsUnderlined: false,
},
}));
Listing 7 – The updated model
Watching for Updates on the Model
In order to use these boolean properties, they need to in sync with the values on the model that they represent. We can certainly add code to the methods that change the model, but that is a fragile solution that is very susceptible to future bugs. To keep the values consistent without a lot of code, we use the WinJS binding framework.
The bind function allows us to attach a function to a property on an object, and may time the property changes, the function gets executed. The code to do this for all three properties is shown in Listing 8.
Model.textSettings.bind("textWeight", function (val) {
Model.textSettings.textIsBold = val === bold;
})
Model.textSettings.bind("textFontStyle", function (val) {
Model.textSettings.textIsItalic = val === italic;
})
Model.textSettings.bind("textDecoration", function (val) {
Model.textSettings.textIsUnderlined = val === underline;
})
Listing 8 – Updating the related model properties when changed
Binding the ToggleSwitches
The final step is to databind the ToggeSwitches to the new properties on the model. This code is very straightforward, and is shown in Listing 9.
<div id="boldSwitch"
data-win-control="WinJS.UI.ToggleSwitch"
data-win-bind="winControl.checked:textIsBold"
data-win-options="{onclick:UISettings.setTextIsBold,title:'Bold'}"></div>
<div id="italicSwitch"
data-win-control="WinJS.UI.ToggleSwitch"
data-win-bind="winControl.checked:textIsItalic"
data-win-options="{onclick:UISettings.setTextIsItalic,title:'Italic'}"></div>
<div id="underlineSwitch"
data-win-control="WinJS.UI.ToggleSwitch"
data-win-bind="winControl.checked:textIsUnderlined"
data-win-options="{onclick:UISettings.setTextIsUnderlined,title:'Underline'}"></div>
Listing 9 – Databinding the ToggleSwitches
Now when you run the app, the ToggleSwitches will start in the correct position.
Summary
The settings framework in Windows 8 makes it extremely simple to store small amounts of data for you app, such as application settings and user preferences. The RoamingSettings will sync the settings to any other device that the user runs your app on, improving the user experience greatly.
About the author
Philip Japikse
|
Philip Japikse is an international speaker, a Microsoft MVP, INETA Community Champion, MCSD, CSM/ CSP, and a passionate member of the developer community, Phil has been working with .Net since the first betas, developing software for over 20 years, and heavily involved in the agile community since 2005. Phil works as a Principal Developer Advocate for Telerik, co-hosts the Hallway Conversations podcast (http://www.hallwayconversations.com), and is the Lead Director for the Cincinnati .Net User’s Group (http://www.cinnug.org). You can follow Phil on twitter via www.twitter.com/skimedic read his Telerik blog at http://blogs.telerik.com/skimedic and his personal blog at www.skimedic.com/blog. |