Published: 30 May 2007
By: Speednet .

SettingsManager is a JavaScript library that allows Windows Vista Sidebar gadgets to persist common settings that all gadget instances have access to.

Introduction

Sidebar gadgets are one of the gems of the Windows Vista operating system. For developers, they open some innovating, exciting possibilities, and provide one more reason why being a developer is fun. But wouldn't you know it? Microsoft left a few important features out of version 1, and this article will solve one of them: the ability to share locally-stored data between gadget instances.

In this article we'll look at a solution to that problem that uses the time-tested .ini file format first used in Windows 1.0 back in 1985.

Now, some people are aghast at the thought of using anything other than XML to save settings in a file. I did in fact consider using XML, but ultimately decided to go with the .ini format for a few key reasons:

  • Simplicity. The majority of gadgets will have relatively modest needs, and will likely need to store string values. XML's main advantage is its ability to handle more complex storage requirements, but at the expense of simplicity (at least when compared to an .ini file).
  • User-editable. Let's face it: manually editing an XML file is a place where only geeks will go. .ini files, on the other hand, are simple to understand and edit - even for a newbie.
  • Readable. .ini files are much easier and quicker to read, and inserting comments is simple and painless.
  • Version-proof code. To parse the .ini file, the code in this article uses basic RegExp notations and string manipulations, things that will likely execute unchanged many years from now, whereas XML support in JavaScript is only for advanced coders, and is constantly changing.

Plainly speaking, an .ini file is just right for the job at hand, and although the same thing can be accomplished using XML, it was my sense that using XML would be overkill.

Lastly, as you'll see below, Windows Vista itself uses the .ini file format for storing global gadget settings, so by doing so ourselves, we are remaining consistent with the operating system.

Note: It also would have been possible to store settings in the Windows registry, but that would limit the portability and ease-of-use of the settings, so it was quickly ruled out.

Gadget settings

Gadgets behave differently than regular programs, and in fact are not programs in the traditional sense. The only program to speak of is the Sidebar application itself, which by default is set to execute when Windows boots. The Sidebar is basically a kind of Web browser, and the gadgets are Web pages that are open and displayed simultaneously.

When you add a gadget to the Sidebar, the Sidebar application creates persistent storage for that gadget in a file located in the current user's personal folder. The exact path of the Sidebar storage file is C:\Users\<user name>\AppData\Local\Microsoft\Windows Sidebar\Settings.ini.

In the Sidebar's Settings.ini file, the newly-added gadget is assigned an integer identifying number (for example, "5"), and all the settings for that gadget are stored in a group using that number (for example, [Section 5]). Within each group, settings are stored as key/value pairs in typical .ini fashion: <key>=<value>.

The Sidebar saves some basic settings for each gadget currently being displayed, such as the gadget's path, its docked/undocked status, the opacity setting, etc. The key names for all of the Sidebar-managed settings start with the string, PrivateSetting_. For example, the opacity setting is stored using the key name, PrivateSetting_GadgetOpacity.

As a developer, you can add settings to the .ini file too, using the built-in System.Gadget.Settings JavaScript object.

Table 1: Windows Vista built-in gadget settings model

Method Action
read(strKey) Read the stored setting that corresponds to strKey, and attempt to guess the correct data type to convert the value to. Because this can produce rounding errors with numeric values, as well as other data conversion errors, readString() should generally be used instead of this method.
readString(strKey) Read the stored setting that corresponds to strKey, and return the value as a character string. In most cases, this method should be used, rather than the read() method.
write(strKey, anyValue) Convert the value anyValue to a string, and then save it under the key name strKey. To avoid the errors that can occur when JavaScript converts non-string data, you should use writeString() instead of write() whenever possible.
writeString(strKey, strValue) Save the string value strValue under the key name strKey. In most cases, this method should be used, rather than the write() method.

As indicated in Table 1, readString() and writeString() should almost always be favored over the read() and write() methods. (The word "almost" is used in case there is an instance where read or write should be used, but I haven't thought of one yet.)

If you try to store an Object data type, the word "object" will be stored, not the enumerated contents of the object. Therefore, in order to store an Object data type it needs to be converted to the JSON string representation, and to do that I'd recommend using the public domain json.js library, developed by Douglas Crockford (http://www.json.org).

It is beyond the scope of this article to get into any great depth with json.js, but suffice to say that you can easily store an Object data type using the following syntax.

Limitations of the built-in gadget settings

As you would expect, there are inherent limitations involved with storing settings using the built-in methods, but the limitations may be more restrictive than you would initially guess.

  • There is a limit of 2,048 characters for each setting. I could not find any documentation of this fact; I figured it out through trial and error (and through some painful debugging sessions). Normally this does not pose a serious limitation, but if you try to store complex objects using the technique described above, you may run into this limitation, as I did.
  • When you close a gadget (i.e., remove it from the Sidebar by clicking the "X" icon), the Sidebar removes all of the settings for that gadget from the Sidebar's Settings.ini file. There is no way for settings to survive a closed gadget.
  • Because each gadget instance stores its settings in a separate section in the Sidebar's Settings.ini file, there is no way to share settings between gadget instances.
  • There is no built-in method of "packaging" default settings along with a gadget, or to ship different versions of a gadget with different default settings. (Other than hard-coding the defaults into JavaScript code.)

With these limitations in mind, we arrive at the subject of this article: the Gadget Settings Manager.

Gadget Settings Manager

The solution to the limitations imposed by the Sidebar Gadget.Settings object is the creation of a Settings.ini file located in the Gadget's root folder, combined with a small JavaScript library to manage the settings contained within it.

The Settings.ini file created by SettingsManager is structured exactly like the Windows .ini files you are probably accustomed to using. In fact, while researching the topic during development of this library, I dusted off my copy of The Waite Group's Windows API Bible and made sure it would be compatible. However, I utterly refused to call the settings retrieval method GetPrivateProfileString().

For those unacquainted with .ini file structure, it is a basic ASCII text file containing key/value pairs organized into sections, called "groups". Any text that starts with a semi-colon (;) or pound sign (#) is considered a comment, and is ignored. There is no multi-line comment syntax.

Listing 1: Structure of an .ini file

You can find more information about the .ini file format at http://en.wikipedia.org/wiki/INI_file. The SettingsManager values are always surrounded by double-quotes, which is the same behavior as the Sidebar's Settings.ini string values. However, the surrounding double-quotes are not included as part of the returned value.

Getting started

To get started with the SettingsManager, a download link for the SettingsManager.js file can be found below in the Downloads section below.

The file should be placed into whichever folder you are placing "core" JavaScript files for your gadget, because it needs to be accessible to every page that needs to read and/or write settings, and it is language-independent. Then, in the <head /> section of each page that needs to access settings, place a <script /> tag referencing the SettingManager.js file.

Replace core/js/ with the actual folder name in which SettingsManager.js resides. As with all src references in Sidebar gadgets, do not make the path absolute by inserting a leading slash character. The path should be relative to the root folder.

The SettingsManager library will not interfere or conflict with any other JavaScript code in the gadget because it is entirely encapsulated in one globally-scoped object, called SettingsManager. It maintains storage of all the in-memory settings within that object, as well as all the methods for accessing and changing the settings.

Working with SettingsManager

Before accessing any settings, you first need to instruct SettingsManager to load all the settings from the gadget's Settings.ini file into memory. That is done using the loadFile() method. (If you don't first load the setting, then you run the risk of over-writing any settings currently in the Settings.ini file.)

Once the settings are loaded, you can retrieve any of the settings; you can add, update, and delete settings; and you can get lists of group names and key names. See the method reference table below for documentation of each method.

All settings are manipulated in memory, not on disk. Settings are not saved back to the Settings.ini file until the saveFile() function is called. This allows you full control over how and when file access is performed.

Listing 2 contains a typical interaction with SettingsManager. saveBoxSettings() is a hypothetical function in the gadget to save the current position and size of an element in the .ini file.

Listing 2: Typical SettingsManager usage

Using the example function in Listing 2, let's call the function with sample arguments and see what the resulting Settings.ini file would look like.

Sample function call:

Contents added to the Settings.ini file:

Property and Method Reference

The following tables list all of the available properties and methods of the SettingsManager library. Remember, property and method names are case-sensitive.

Table 2: Properties available in the SettingsManager library

Property Description
FileName Name of the file used to store the settings. Does not include any path information, but does include the extension (.ini). The default value is "Settings.ini". If working with more than one settings file, this property controls which settings file you are currently loading or saving.  Changing this property has no direct effect on the settings in memory.
LocalFolder The sub-folder where the settings file is to be found/stored. Needs to be expressed relative to the gadget root. For example, if the settings are to be stored as <gadget root>\core\settings\Settings.ini, then LocalFolder should be set to "core\settings". The default value is an empty string (""), which stores the settings in the gadget root.

Table 3: Methods available in the SettingsManager library

Method Usage and Description
getFullPath

Constructs and returns the full file system path of the settings file, including drive letter, gadget path, LocalFolder, and FileName.

loadFile

Loads all the settings from the .ini file into memory. This is normally the first method called before adding, changing, and deleting settings.

saveFile

Saves all settings in memory to the .ini file, completely overwriting the existing file.

getValue

Retrieves the value of one setting from memory. If the combination of groupName and keyName is not found, then defaultValue is returned, or an empty string ("") is returned if defaultValue is not passed.

setValue

Sets one setting's value in memory. The value is not save to the .ini file on disk until saveFile() is called. If groupName does not exist, it is created. If keyName exists in the group it is overwritten; otherwise it is created. value must be a string.

deleteValue

Deletes one setting from memory. The value is not deleted from the .ini file on disk until saveFile() is called. If the group is empty after deleting the value, the group itself will be deleted if deleteEmptyGroup is true.

deleteGroup

Deletes one group from memory, and all values stored in that group. The changes are not saved to the .ini file on disk until saveFile() is called.

getKeyCount

Returns the number of keys [i.e., settings] stored within the specified group in memory.

getKeyNames

Returns an array of strings containing all of the key names within the specified group in memory.

getGroupCount

Returns the number of groups currently stored in memory.

getGroupNames

Returns an array of strings containing all of the group names currently stored in memory.

isValidName

Returns true if name is a valid group or key name.  (Group and key names have the same criteria for validity: at least one character in length, must start and end with a letter, number, or underscore, and in between can be almost any combination of letters, numbers, spaces, and punctuation.)

getIniString

Generates and returns all of the settings in memory as a string, in .ini file format. The return value is exactly the same as the contents of the .ini file would be. In fact, this method is used by saveFile() to generate the contents of the .ini file that is saved. This method is exposed since it may have additional value apart from saving to disk.

One at a time, please!

Under most circumstances, SettingsManager will be used infrequently - for example, when the user clicks a Save button to persist the current settings. Realistically, that means we don't need to worry about more than one gadget instance trying to save changes to the .ini file at the same time. Thus, the SettingsManager was purposely designed to ignore that possibility. Rather than adding the bulk and complication of a proprietary locking scheme I decided to keep the library size smaller and place the onus of a locking mechanism on the small percentage of programs that require them.

In others words, why increase the size and complexity of 100% of the gadgets, if only 5% will require it?

However, there are some situations in which a locking mechanism is critical. For example, there may be a case where several gadget instances are active simultaneously, and the instances use SettingsManager to communicate with each other. In this case it is important that only one instance of the gadget be allowed to write to the .ini file at any one time.

In this example, a gadget instance would use a locking mechanism in the following manner:

  1. Obtain a lock,
  2. Load the settings from disk, change the setting(s) in memory, and then immediately save them back to disk, and,
  3. Release the lock.

The most important design aspects of any such locking mechanism are:

  • The locking mechanism must be globally available to all gadget instances, and,
  • The program code must obtain (or deny) the lock in one single operation - within one line of code - so that there is no chance that two instances could simultaneously obtain a lock.

Building an actual locking mechanism is beyond the scope of this article, but I did build a generic LockManager module, which I may include in a future article. (If there is interest, please leave me a comment!)

How it works

So now that we know how to use SettingsManager, let's see how it works. Since developers tend to be most interested in the under-the-cover details, I'll start inside and work my way out.

ActiveX objects

One of the nice aspects of Windows Vista gadget development is that you don't need to worry about which browser you're developing for, because everything runs on IE7 in the Sidebar. Another helpful thing is that the Sidebar has a high level of trust of the installed gadgets, so permissions are rarely an issue. Both of these helped a great deal in developing SettingsManager, because ActiveX objects could be used with confidence. It is a liberating experience to code an HTML page without fear of browser incompatibility!

ActiveX objects are used in the loadFile() and saveFile() methods, to create a Scripting.FileSystemObject. Once a FileSystemObject is created, reading and writing data to disk is straight-forward. For example, the saveFile() method consists of the following logic.

Listing 3: saveFile() program logic

Note: For those interested in reading about all the various FileSystemObject methods, there is a good, simple overview located at http://www.webreference.com/js/column71/index.html

To provide control over when file access takes place, SettingsManager only touches the file system during loadFile() and saveFile(). All other operations, such as reading and writing individual settings, take place in memory.

Storage in memory

Storing the settings in memory is accomplished using arrays and objects. One private property, SettingsManager._groups, is used to store all settings. (In my coding standards, private properties and methods start with an underscore.)

Even though JavaScript objects do not have true private members, accessing members marked private from your program code is strongly discouraged. The _groups property is considered private for all the reasons one would make a private property in a class: the internal structures can be changed without affecting programs that rely on them, and the built-in methods provide all the functionality necessary under normal circumstances.

_groups is an array of JavaScript objects, with each element of the array representing one group of settings within the .ini file. (As explained in Gadget Settings Manager above, a group is all the settings that come after the [Group Name] notation in the file.) Each element of the _groups array, called a "group object", is defined using JSON as:

As you can see, each group object has two properties - Name and Values. The Values property is an array of Key/Value objects, used to store all the settings within the group.

Apart from simple assignment statements, the arrays are manipulated in the SettingsManager methods using the splice() function. This handy JavaScript function can be used to delete elements, insert elements, or both at the same time.

Bursting performance

The loadFile() method is used to fill the _groups array with the settings stored in the .ini file. I have not done actual measurements and comparisons of different loading methods, but the techniques used in loadFile() should produce very quick load times.

Recognizing that I/O is normally the most restrictive bottleneck in an application, all of the data is read at once using the FileSystemObject.ReadAll() function. I have found in real-world usage that all-at-once I/O functions like ReadAll() can greatly improve performance, just as any "bursting" operation would.

After the file contents are in memory, a simple RegExp match() call puts all the non-blank, non-comment lines into an array, one element per line.

Then, it is a matter of iterating through the elements and building the _groups array. Again, regular expressions are employed to analyze the text on each line.

First, a line is tested to see if it contains a group name, in which case it creates a new element in the _groups array. Or, if not, the line is tested to see if it contains a Key/Value pair. If so, it adds the Key/Value pair to the Values array in the last group added to the _groups array.

The group and key/value tests are defined as follows. (_validNamePattern is defined as a private property so that the group and key naming requirements can easily be changed in one place.)

JavaScript doc comments

Each method in SettingsManager is fully commented, using Microsoft's IntelliSense-compatible format. This will allow a developer using the "Orcas" version of Visual Studio to get full IntelliSense support with their JavaScript files. For example, if you type SettingsManager. the full list of available properties and methods in SettingsManager will appear. Then, if you select getValue(), the list of that method's arguments and their types will be displayed.

I believe once developers start seeing the tremendous value of IntelliSense support in JavaScript, this comment style will flourish. You can find a thorough write-up of the specification for "JavaScript doc comments" on Bertrand Le Roy's blog post.

Integration

Finally, the SettingsManager is implemented as one large JSON (JavaScript Object Notation) variable, which is a common technique employed by third party library makers. It is similar to declaring a class in C# and other object-oriented languages. The most important reason for using this technique is because only the module name itself (in this case SettingsManager) is exposed to the global namespace.

Thus, assuming your JavaScript code does not use the name SettingsManager in the global namespace, the module can be integrated without fear of naming conflicts.

Summary

SettingsManager fills a gap in the Windows Vista Sidebar gadget object model: the ability to share settings between gadgets, as well as to "package" a gadget with default settings. The module reads and writes the standard Windows .ini file format, making it simple to directly edit settings while maintaining compatibility with Sidebar gadget standards.

Implementing SettingsManager is accomplished with the simple addition of a <script /> tag at the top of any page that needs access to settings. SettingsManager should not introduce any naming conflicts, because its JavaScript code is encapsulated in a single object called SettingsManager.

SettingsManager does not have any built-on locking mechanism, which is fine for 95% of its uses, but for the small percentage of applications that require a locking mechanism, that code will have to be supplied and managed external to SettingsManager. (Don't forget to leave me a comment if you are interested in having me include LockManager in a future article!)

When reading and writing settings, the most important thing to remember is that settings are not saved to disk until the saveFile() method is called. Keeping that concept in the forefront of your thoughts may save a great deal of debugging time later.

I am interested in hearing from you! Please take a moment to leave a comment to let me know if this article was useful for you, and what you have used SettingsManager for in your gadget applications.

Download

References

  • Wikipedia - INI file – http://en.wikipedia.org/wiki/INI_file
  • Scripting the File System – http://www.webreference.com/js/column71/index.html
  • JSON-to-String – http://www.json.org/js.html
  • JavaScript doc comments – Bertrand Le Roy's blog post
  • About the author

    Todd Northrop is the owner and creator of Lottery Post, the Internet's largest lottery community. He founded his company, Speednet Group, to perform consulting, web development, and web hosting, working primarily with Microsoft .NET technologies.

    <<  Previous Article Continue reading and see our next or previous articles Next Article >>

    About Speednet .

    Sorry, no bio is available

    This author has published 2 articles on DotNetSlackers. View other articles or the complete profile here.

    Other articles in this category


    Android for .NET Developers - Location and Maps
    In Windows Phone and iOS getting the current position of the device in terms of latitude and longitu...
    Android for .NET Developers - Using Web Views
    In this article, I'll show a native app that contains a web-based view. The great news is that HTML ...
    Android for .NET Developers - Building a Twitter Client
    In this article, I'll discuss the features and capabilities required by an Android application to ta...
    Ref and Out (The Inside Story)
    Knowing the power of ref and out, a developer will certainly make full use of this feature of parame...
    Developing a Hello World Java Application and Deploying it in Windows Azure - Part II
    In this article we will see the steps involved in deploying the WAR created in the first part of thi...

    You might also be interested in the following related blog posts


    SQL Express - "Failed generate a user instance..." read more
    Stumbling Through: K2 Blackpearl Programmability read more
    Using your mashups from Popfly read more
    Apple Safari for Windows and Microsoft Silverlight read more
    Vista: Glass read more
    Top
     
     
     

    Discussion


    Subject Author Date
    placeholder Not sure how to use getValue() Mark Cheek 8/4/2010 10:10 AM
    RE: Not sure how to use getValue() Speednet . 8/4/2010 10:50 AM
    placeholder special characters in key? Chris Davis 4/15/2008 10:23 PM
    RE: special characters in key? Speednet . 4/15/2008 11:37 PM
    placeholder RE: RE: special characters in key? Chris Davis 4/16/2008 12:38 PM
    RE: RE: RE: special characters in key? Speednet . 4/16/2008 5:36 PM
    placeholder RE: RE: RE: RE: special characters in key? Chris Davis 7/5/2008 10:20 AM
    Great job! Speednet . 7/5/2008 1:59 PM
    placeholder RE: RE: RE: RE: RE: special characters in key? Sonu Kapoor 7/6/2008 9:31 PM
    Live Gallery Speednet . 7/12/2008 8:19 PM
    placeholder Thanks! Ed Ketterer 11/19/2008 9:08 PM
    RE: Thanks! Speednet . 11/19/2008 10:07 PM
    placeholder Fabulous Tool! Jeff Koch 1/30/2009 6:16 PM

    Please login to rate or to leave a comment.