Skins and modules are the reasons that DotNetNuke became so popular. They just get you involved. From the early versions you could skin it working with one HTML and one CSS file when for the most of the modern web applications out there, you have to deal with a bunch of files (sometimes and images!) to make them look the way you want.
With this tutorial I'll try to make you understand the basics about DotNetNuke skins and hopefully you'll learn how to create your own first skin from a static HTML page.
To follow this tutorial you need to know HTML and CSS and your web design skills should allow you to create a static HTML page (like this one). Also DotNetNuke must be installed either locally on your machine or on a live server. Installation help can be found in the DotNetNuke official documentation. Dreamweaver and my extension are required too for this tutorial although you can create skins even with Notepad or any other free editor like Notepad++ which is my favorite one.
Another tool you'll find useful is the DotNetNuke Skin Objects and Container Objects Dynamic Reference which is the same reference you can find in the DotNetNuke Skinning document but much easier to read.
Let's go ahead to our step by step tutorial. You can download the final skin and containers here.
1) Skin folders
On your desktop, or wherever you want, create a new folder with the name "MyFirstSkin", that's the name of the skin we'll develop. In that folder create another one with the name "skins" and another one with the name "containers".
2) The skin.ascx from a static HTML page
First thing we have to do is to decide the way we want our pages to look like. So we run dreamweaver and we create a new HTML page so we can define what goes where. Click here to see my own example. As you can see all I did was to define the areas where later we'll put the dynamic content. We save this file as "skin.ascx" in the "MyFirstSkin/skins/" folder.
In our skin.ascx file:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<title>Untitled Document</title>
<style>
body{background:#555;}
.skinwrapper{border:5px #333 solid;background:#FFF;padding:1px;}
.skinheader{padding:10px;background:#EEE;}
.skinmenu{padding:10px;background:#C00;}
.skinuser{padding:5px;background:#CCC;text-align:right;}
.skincontentstable{width:100%;}
.leftpane{background:#EEE;width:200px;}
.contentpane{background:#FFF;}
.rightpane{background:#EEE;width:200px;}
.skinfooter{padding:5px;background:#CCC;text-align:center;}
</style>
</head>
<body>
<div class="skinwrapper">
<div class="skinheader">Logo</div>
<div class="skinmenu">Menu</div>
<div class="skinuser">Register - Login</div>
<table border="0" cellpadding="10" cellspacing="0" class="skincontentstable" summary="Design Table">
<tr valign="top">
<td class="leftpane">Left Pane</td>
<td class="contentpane">Content Pane</td>
<td class="rightpane">Right Pane</td>
</tr>
</table>
<div class="skinfooter">Copyright</div>
</div>
</body>
</html>
3) The skin.css and our skin.ascx page clean up
For the skin we need only everything in the "body" tag and the CSS styles we have included in the head area. So we "Cut" the styles inside the "style" tag and we "Paste" them in a new CSS file with the name "skin.css". We save this file in the same "MyFirstSkin/skins/" folder. Now we can remove the unneeded tags from the skin.ascx page... we keep only everything in the "body" tag.
In our skin.ascx file:
<div class="skinwrapper">
<div class="skinheader">Logo</div>
<div class="skinmenu">Menu</div>
<div class="skinuser">Register - Login</div>
<table border="0" cellpadding="10" cellspacing="0" class="skincontentstable" summary="Design Table">
<tr valign="top">
<td class="leftpane">Left Pane</td>
<td class="contentpane">Content Pane</td>
<td class="rightpane">Right Pane</td>
</tr>
</table>
<div class="skinfooter">Copyright</div>
</div>
In our skin.css file:
body{background:#555;}
.skinwrapper{border:5px #333 solid;background:#FFF;padding:1px;}
.skinheader{padding:10px;background:#EEE;}
.skinmenu{padding:10px;background:#C00;}
.skinuser{padding:5px;background:#CCC;text-align:right;}
.skincontentstable{width:100%;}
.leftpane{background:#EEE;width:200px;}
.contentpane{background:#FFF;}
.rightpane{background:#EEE;width:200px;}
.skinfooter{padding:5px;background:#CCC;text-align:center;}
4) DotNetNuke skin objects and the dreamweaver extension
Skin objects are special tags that DotNetNuke recognizes as dynamic content placeholders. So when we write this tag: <dnn:LOGO runat="server" id="dnnLOGO" /> we tell DotNetNuke to put our portal's logo in that place. Dynamic means that we'll be able to change the logo image from our portal's "Site Settings" without touch the code again. Let's put the skin objects in the areas we have specified, using the dreamweaver extension.
Our updated skin.ascx page:
<%@ Control language="vb" CodeBehind="~/admin/Skins/skin.vb" AutoEventWireup="false" Explicit="True" Inherits="DotNetNuke.UI.Skins.Skin" %>
<%@ Register TagPrefix="dnn" TagName="LOGO" Src="~/Admin/Skins/Logo.ascx" %>
<%@ Register TagPrefix="dnn" TagName="SOLPARTMENU" Src="~/Admin/Skins/SolPartMenu.ascx" %>
<%@ Register TagPrefix="dnn" TagName="LOGIN" Src="~/Admin/Skins/Login.ascx" %>
<%@ Register TagPrefix="dnn" TagName="USER" Src="~/Admin/Skins/User.ascx" %>
<%@ Register TagPrefix="dnn" TagName="COPYRIGHT" Src="~/Admin/Skins/Copyright.ascx" %>
<div class="skinwrapper">
<div class="skinheader"><dnn:LOGO runat="server" id="dnnLOGO" /></div>
<div class="skinmenu">
<dnn:SOLPARTMENU
runat="server"
id="dnnSOLPARTMENU"
usearrows="true"
userootbreadcrumbarrow="false"
usesubmenubreadcrumbarrow="false"
rootmenuitemlefthtml="<span>"
rootmenuitemrighthtml="</span>"
rootmenuitemcssclass="rootmenuitem"
rootmenuitemselectedcssclass="rootmenuitemselected"
rootmenuitembreadcrumbcssclass="rootmenuitembreadcrumb"
submenucssclass="submenu"
submenuitemselectedcssclass="submenuitemselected"
submenuitembreadcrumbcssclass="submenuitembreadcrumb"
CSSNodeSelectedRoot="rootmenuitembreadcrumb"
CSSNodeSelectedSub="submenuitembreadcrumb"
delaysubmenuload="true"
/>
</div>
<div class="skinuser">
<dnn:USER runat="server" id="dnnUSER" CssClass="skinuser" />
-
<dnn:LOGIN runat="server" id="dnnLOGIN" CssClass="skinuser" />
</div>
<table border="0" cellpadding="0" cellspacing="0" class="skincontentstable" summary="Design Table">
<tr valign="top">
<td id="LeftPane" runat="server" width="200" nowrap class="leftpane" visible="false"></td>
<td id="ContentPane" runat="server" class="contentpane"></td>
<td id="RightPane" runat="server" width="200" nowrap class="rightpane" visible="false"></td>
</tr>
</table>
<div class="skinfooter"><dnn:COPYRIGHT runat="server" id="dnnCOPYRIGHT" CssClass="skinfooter" /></div>
</div>
What happened here?
- I have put most of the DotNetNuke skin objects using the dreamweaver extension and I added to them some custom CSS classes.
- Every skin object needs to be registered at the top of the page.
There is a button in the dreamweaver extension with the name "Skin TagPrefix". If we put our cursor at the top of the page and click this button, we insert in our page the code we need to register all the skin objects. We can keep all of that or delete the code lines for skin objects we don't use.
- I copied the code for the solpart menu from another skin.
- Some attributes added to the table cells that we'll use to put our contents...
Let's examine one of them and learn some interesting things about panes.
<td id="LeftPane" runat="server" width="200" nowrap class="leftpane" visible="false"></td>
- All panes need a unique ID... can be anything! We just give them names that will help us to remember later where is what.
- All panes need runat="server" so they can be identified as .net server controls.
- Using the width attribute helps for backwards compatibility for older browsers.
- The attribute nowrap="nowrap" is wrong when we want to use it with .net controls. We use a simple nowrap instead.
- When we add the visible="false" to a table cell which is server control it won't show up when there is nothing in it. With this feature we add flexibility to the structure of our page because although we have three columns now, they will become two if we don't put anything in one of the side columns.
Some more hints about panes:
- We can have as many panes as we want.
- Every HTML element can be a pane which means a dynamic content placeholder. Table cells are the most common used because their behavior is predictable. DIVs are good but it's difficult to create a flexible lay-out like with the table cells. Also their behavior as panes is not always ideal.
And a hint about the skin objects:
- We can put a skin object as many times as we want in a page. To do that we have to create unique instances using a suffix for every new instance and registering this instance at the top of the page. So if we want to add two LOGGIN objects in our page we do it this way:
<dnn:LOGIN1 runat="server" id="dnnLOGIN1" CssClass="skinuser" />
<dnn:LOGIN2 runat="server" id="dnnLOGIN2" CssClass="skinuser" />
and at the top of our page:
<%@ Register TagPrefix="dnn" TagName="LOGIN1" Src="~/Admin/Skins/Login.ascx" %>
<%@ Register TagPrefix="dnn" TagName="LOGIN2" Src="~/Admin/Skins/Login.ascx" %>
5) Overriding the default DotNetNuke CSS styles
The next step is to add more CSS styles in our skin.css to control better the appearance of our portal. DotNetNuke uses some default CSS styles that we'll need to override. You can read more about that topic in the DotNetNuke CSS hierarchy and default styles article.
Our updated skin.css
/*page*/
html,body{background:#333;height:100%;}
.skinwrapper{border:10px #333 solid;background:#FFF;padding:1px;}
.skinheader{padding:10px;background:#EEE;}
.skinmenu{background:#C00;}
.skinuser{padding:5px;background:#CCC;text-align:right;}
.skincontentstable{}
.leftpane{background:#EEE;width:200px;}
.contentpane{background:#FFF;}
.rightpane{background:#EEE;width:200px;}
.skinfooter{padding:5px;background:#CCC;text-align:center;font-size:10px;}
/*panes*/
.leftpane{width:200px;padding:10px 10px 0 10px;}
.contentpane{width:100%;padding:10px 10px 0 10px;}
.rightpane{width:200px;padding:10px 10px 0 10px;}
/*text*/
.normal,div,span,p,td,th,li,a{font-family:Verdana, Arial, Helvetica, sans-serif;font-size:12px;color:#333;}
.head,.subhead{font-family:Verdana, Arial, Helvetica, sans-serif;color:#333;}
.head{font-size:16px;font-weight:bold;}
/*links & buttons*/
a:link{color:#C00;text-decoration:underline;}
a:visited{color:#C00;text-decoration:underline;}
a:hover{color:#C00;text-decoration:none;}
a:active{color:#C00;text-decoration:none;}
.StandardButton{background:#CCC;border:1px #CCC outset;padding:5px;}
a.CommandButton{font-size:14px;font-weight:bold;}
.controlpanel a.CommandButton{font-size:10px;}
a.CommandButton:link{color:#C00;text-decoration:none;}
a.CommandButton:visited{color:#C00;text-decoration:none;}
a.CommandButton:hover{color:#C00;text-decoration:underline;}
a.CommandButton:active{color:#C00;text-decoration:underline;}
a.skinuser:link{color:#000;text-decoration:none;}
a.skinuser:visited{color:#000;text-decoration:none;}
a.skinuser:hover{color:#C00;text-decoration:underline;}
a.skinuser:active{color:#C00;text-decoration:underline;}
/*menu*/
.MainMenu_MenuContainer{background:transparent;width:1px;}
.MainMenu_MenuContainer td{cursor:default;font-size:3px;width:1px;}
.MainMenu_MenuIcon{display:none;}
.MainMenu_RootMenuArrow{display:none;}
.MainMenu_MenuItem{font-family:Verdana, Arial, Helvetica, sans-serif;color:#FFF;font-size:12px;font-weight:normal;padding:2px 15px 2px 7px;border:1px #C00 solid;border-width:1px 0;}
.submenuitembreadcrumb{font-family:Verdana, Arial, Helvetica, sans-serif;color:#FF0;font-size:12px;font-weight:normal;padding:2px 15px 2px 7px;border:1px #C00 solid;border-width:1px 0;}
.submenuitemselected{font-family:Verdana, Arial, Helvetica, sans-serif;color:#FF0;font-size:12px;font-weight:normal;padding:2px 15px 2px 7px;background:#F00;border:1px #C00 solid;border-width:1px 0;}
.MainMenu_MenuArrow{font-size:11px;padding:2px 0 2px 10px;height:24px;border:#C00 solid;border-width:1px 1px 1px 0;}
.submenu{z-index:1000;border:0;padding:0;background:#C00;}
.rootmenuitem{background:transparent;}
.rootmenuitem td{padding:0 10px 0 9px;height:40px;cursor:pointer;cursor:hand;border-right:1px #A00 solid;}
.rootmenuitem td span{font-family:Verdana, Arial, Helvetica, sans-serif;color:#FFF;font-size:12px;font-weight:bold;}
.rootmenuitembreadcrumb{background:transparent;}
.rootmenuitembreadcrumb td{padding:0 10px 0 9px;height:40px;cursor:pointer;cursor:hand;border-right:1px #A00 solid;}
.rootmenuitembreadcrumb td span{font-family:Verdana, Arial, Helvetica, sans-serif;color:#FF0;font-size:12px;font-weight:bold;}
.rootmenuitemselected{background:#F00;}
.rootmenuitemselected td{padding:0 10px 0 9px;height:40px;cursor:pointer;cursor:hand;border-right:1px #A00 solid;}
.rootmenuitemselected td span{font-family:Verdana, Arial, Helvetica, sans-serif;color:#FF0;font-size:12px;font-weight:bold;}
.MainMenu_MenuBreak{display:none;}
Well, I cannot explain every single class I added but I think that if you study the above styles a little, you'll see that except the menu everything else is pretty easy to understand. The menu styles are the trickiest part in our skin.css but I did put them so you can have a good working example as an initial point for your experiments.
6) Your first container – common.ascx and container.css
Containers are the part of the skin where we define the way we want the modules we put in our portal to look. Containers can be very simple but they can also be very complicated... it's up to our taste and skills. The example below is a simple container.
Let's create two new files.
Our "MyFirstSkin/containers/common.ascx" file:
<%@ Control language="vb" CodeBehind="~/admin/Containers/container.vb" AutoEventWireup="false" Explicit="True" Inherits="DotNetNuke.UI.Containers.Container" %>
<%@ Register TagPrefix="dnn" TagName="ACTIONS" Src="~/Admin/Containers/SolPartActions.ascx" %>
<%@ Register TagPrefix="dnn" TagName="TITLE" Src="~/Admin/Containers/Title.ascx" %>
<%@ Register TagPrefix="dnn" TagName="ACTIONBUTTON1" Src="~/Admin/Containers/ActionButton.ascx" %>
<%@ Register TagPrefix="dnn" TagName="ACTIONBUTTON2" Src="~/Admin/Containers/ActionButton.ascx" %>
<table width="100%" border="0" cellspacing="0" cellpadding="0" class="ccommon">
<tr>
<td class="ctitle"><dnn:TITLE runat="server" id="dnnTITLE" /></td>
</tr>
<tr valign="top">
<td runat="server" id="ContentPane" class="ccontent"><dnn:ACTIONS runat="server" id="dnnACTIONS" /></td>
</tr>
<tr>
<td class="actionbuttons">
<dnn:ACTIONBUTTON1 runat="server" id="dnnACTIONBUTTON1" CommandName="SyndicateModule.Action" DisplayLink="False" DisplayIcon="True" />
<dnn:ACTIONBUTTON2 runat="server" id="dnnACTIONBUTTON2" CommandName="PrintModule.Action" DisplayLink="False" DisplayIcon="True" />
</td>
</tr>
</table>
Our "MyFirstSkin/containers/container.css" file:
.ccommon{margin-bottom:20px;}
.ccommon .ccontent{padding:10px 0;}
.ccommon p{margin-top:0;}
.ccommon .actionbuttons{text-align:right;font-size:1px;line-height:1px;height:1px;padding-right:20px;}
.actionbuttons img{margin:0 0 10px 0;}
As you can see, containers have their own DotNetNuke skin objects and again you can use the dreamweaver extension to put them. Other than that the HTML and the CSS are very simple. What you're probably wondering is why I used the ".ccommon" class to our container's table. The reason is that a DotNetNuke page may include several containers so if we want to avoid conflicts we need a way to differentiate the styles for each container.
7) Ready to upload it?
In order to make our skin available to our portal, we have to upload it using the admin interface (advanced user may also use FTP to upload the skin files). Let's browse the "MyFirstSkin/skins/" folder and compress the skin.ascx and the skin.css in a MyFirstSkin.zip file. The same with the containers, we browse the "MyFirstSkin/containers/" folder and compress the common.ascx and container.css in in a MyFirstSkin.zip file.
So now we have two zip files: MyFirstSkin/skins/MyFirstSkin.zip and MyFirstSkin/containers/MyFirstSkin.zip.
It's time to run our browser and type in the address bar our DotNetNuke's portal URL, login and apply the skin!
Click here to find my own working demo.