You want to provide your users with a visual skin selector in your settings, but don't know where to start. Here's all the code you'll need to do it. The end result will look something like this:
File structure
Firstly, create a file structure to support skins. You'll be placing all the skins in subfolders under a themes folder.
| <Gadget Folder> |
| images |
| themes |
| <skin1 folder> |
| <skin2 folder> |
This keeps all your skins out of the way, and easy to distinguish from the rest of the Gadget. Each skin subfolder should be named as you'd like the skin name to appear to the user. ie Default, Blue, Black etc. And each folder needs to contain a folder.png file, which should be a screenshot of the Gadget with the skin applied. This image should ideally be scaled and no larger than 100x100 pixels.
Images
You'll need some images for the previous/next buttons, so save the following images in the images folder created above.
| left.png: | | leftdown.png: | | lefthover.png: | |
| right.png: | | rightdown.png: | | righthover.png: |  |
Settings.html
The settings file needs to search the themes subfolder, store the options and select the current skin.
Without going into too much detail, it will create an array of the subfolders under themes and using the gSkin stored setting, select the currently selected skin. The only entry you need to change is the default skin name, which is the first line of the script.
<html>
<style>
* {
font-family:Segoe UI;
font-size:12px;
}
body{
width:300px;
height:250px;
margin:0px;
}
</style>
<head>
<script language="javascript" type="text/javascript">
var defaultSkin = "Default"; // default skin name
var maxSkins = 256; // max number of skins allowed
System.Gadget.onSettingsClosing = settingsClosing;
var oFSO = new ActiveXObject("Scripting.FileSystemObject");
var aSkins = new Array(maxSkins);
var skinCount = 0; // number of skins found
var currentSkinNo = 0; // the current skin selected
var currentSetting = System.Gadget.Settings.readString("gSkin");
if (currentSetting == "")
currentSetting = defaultSkin;
function init() {
getSkins();
updateSkin();
}
function settingsClosing(event) {
try{
if (event.closeAction == event.Action.commit) {
//write settings to INI
System.Gadget.Settings.write("gSkin", aSkins[currentSkinNo].substring(aSkins[currentSkinNo].lastIndexOf("\\") + 1, aSkins[currentSkinNo].length));
// update the Gadget if the skin has changed
if (currentSetting != System.Gadget.Settings.readString("gSkin"))
updateGadget();
}
} catch(err) {System.Debug.outputString("settingsClosing: " + err.name+" - "+err.message)}
event.cancel = false;
}
function updateSkin() {
imgSkin.src = aSkins[currentSkinNo] + "\\folder.png";
divCount.innerText = (currentSkinNo + 1) + "/" + skinCount;
divName.innerText = aSkins[currentSkinNo].substring(aSkins[currentSkinNo].lastIndexOf("\\") + 1, aSkins[currentSkinNo].length);
}
// Read skin folders
function getSkins() {
var oFolder = oFSO.GetFolder(System.Gadget.path + "\\themes");
var oSubFolders = new Enumerator(oFolder.SubFolders);
try{
for (; !oSubFolders.atEnd() && skinCount < maxSkins; oSubFolders.moveNext())
{
aSkins[skinCount] = String(oSubFolders.item());
// have we found the current skin
if(aSkins[skinCount].toLowerCase() == (System.Gadget.path.toLowerCase() + "\\themes\\" + currentSetting).toLowerCase())
currentSkinNo = skinCount;
skinCount++;
}
} catch(err) {System.Debug.outputString("getSkins: " + err.name + " - " + err.message)}
}
// Display the next Skin
function nextSkin() {
currentSkinNo = (currentSkinNo+1) % skinCount;
updateSkin();
}
// Display the previous Skin
function previousSkin() {
currentSkinNo = (currentSkinNo-1+skinCount) % skinCount;
updateSkin();
}
// Change a IMG source
function changeIMG(imagesource, imagename) {
imagesource.src= "images\\" + imagename;
}
</script>
</head>
<body onload="init()">
<div style="position:static; width:100%; height:160px;">
<div style="position:absolute; top:0px; left:0px; width:100%; height:100px; text-align:center;">
<div style="width:128px; height:100%; background-color:white; border-style: solid; border-width:1px; border-color:rgb(180,180,180); overflow:hidden;">
<div style="position:absolute; top:50%; left:50%;">
<img id="imgSkin" src="images/blank.png" style="position:relative; top:-50%; left:-50%"/>
</div>
</div>
</div>
<div style="position:absolute; width:100%; height:50px; top:110px; left:0px; text-align:center;">
<div style="position:absolute; top:0px; left:50%;">
<div style="position:absolute; left:-35px;">
<img id="imgPreviousSkin" title="Previous" src="images/left.png"
style="position:relative; left:-50%;"
onMouseOver="javascript:changeIMG(imgPreviousSkin,'LeftHover.png')"
onMouseOut="javascript:changeIMG(imgPreviousSkin,'Left.png')"
onClick="previousSkin()"
/>
</div>
<div style="position:absolute; left:35px;">
<img id="imgNextSkin" title="Next" src="images/right.png"
style="position:relative; left:-50%;"
onMouseOver="javascript:changeIMG(imgNextSkin,'RightHover.png')"
onMouseOut="javascript:changeIMG(imgNextSkin,'Right.png')"
onClick="nextSkin()"
/>
</div>
</div>
<div style="position:absolute; top:2px; left:50%;">
<div id="divCount" style="position:relative; left:-50%;"></DIV>
</div>
<div style="position:absolute; top:22px; left:50%;">
<B><DIV id="divName" style="position:relative; left:-50%;"></DIV></B>
</div>
</div>
</div>
</body>
</html>
The only function not coded above is updateGadget(), which needs to apply the skin to the Gadget. An example would be to reload the Gadget:
function updateGadget() {
try{
System.Gadget.document.parentWindow.location.href = System.Gadget.document.parentWindow.location.href;
} catch(err) {}
}
Or, call the parent function that loads the skin (see Loading the Gadget skin):
function updateGadget() {
try{
System.Gadget.document.parentWindow.loadSkin();
} catch(err) {}
}
Loading the Gadget skin
This is the tricky bit and depends very much on how complicated you want your skins to be. It can also get very complicated if you're supporting docked/undocked states and left/right handed sidebars. For sanity, I've assumed we have only one Gadget state in the following examples.
Option 1 - The skin contains graphics only, in which case you just need to update the URL's of your images. eg.
var defaultSkin = "Default";
function loadSkin() {
// use the default skin if there's no saved setting
if (System.Gadget.Settings.readString("gSkin") != "")
var skinPath = "themes/" + System.Gadget.Settings.readString("gSkin") + "/";
else
var skinPath = "themes/" + defaultSkin + "/";
// update the Gadget background and images
System.Gadget.background = "url(" + skinPath + "background.png)";
img1.src = skinPath + "img1.png";
img2.src = skinPath + "img2.png";
...
}
Option 2 - As well as containing images, the skin also has a stylesheet called skin.css. To see this in action, look at Media Player
var defaultSkin = "Default";
function loadSkin() {
// use the default skin if there's no saved setting
if (System.Gadget.Settings.readString("gSkin") != "")
var skinPath = "themes/" + System.Gadget.Settings.readString("gSkin") + "/";
else
var skinPath = "themes/" + defaultSkin + "/";
// create the stylesheet if it doesn't exist
var x = document.getElementsByTagName('link');
if (x.length > 0)
x[0].href = skinPath + "skin.css";
else {
var oLink = document.createElement("link");
oLink.href = skinPath + "skin.css";
oLink.rel = "stylesheet";
oLink.type = "text/css";
document.body.appendChild(oLink);
}
// update the Gadget background and images
System.Gadget.background = "url(" + skinPath + "background.png)";
img1.src = skinPath + "img1.png";
img2.src = skinPath + "img2.png";
...
}
Note that when applying a stylesheet, due to bugs in Sidebar, several style settings aren't applied. Body background images, body width and body height to name but a few. If you skins change the Gadget size, you'll have to code around this by reading and applying the stylesheet settings yourself.
Option 3 - The skin contains code. To see this in action, look at Spectrum Analyser.
This is ironically the easiest option, as you can leave the skin to initialise the Gadget's graphics etc. Simply append the following code immediately before the closing </html> statement in your gadget.html file:
<script language="javascript" type="text/javascript">
try{
if (System.Gadget.Settings.readString("gSkin") != "")
var skinPath = "themes/" + System.Gadget.Settings.readString("gSkin") + "/";
else
var skinPath = "themes/" + defaultSkin + "/";
var scriptInclude = "<script src=\"" + skinPath + "skin.js\" language=\"javascript\" type=\"text/javascript\"></script" + ">";
document.body.appendChild(document.createElement(scriptInclude));
} catch(err) {System.Debug.outputString("loadSkin: " + err.name + " - " + err.message)}
</script>
Your skins should then use the skinPath variable for all local file references.