For a long time now many skinners for DotNetNuke have felt like customizing individual menu items was like looking for the Holy Grail. All the menus I have contributed to the community (Solpart and DNNMenu) have always had the ability to customize each menu item. The problem always was
how do we allow DotNetNuke the ability to customize the items? The only place the unique menu structure is known is after the portal is created, well out of reach of someone providing a generic skin to fit any site. Sure there are those portal admins out there that know enough about css to make it work, but there still was a problem in how to access each item directly in css.
I have been considering options for how to overcome this for some time now, and am currently working on the 2.0 version of the webcontrols which should contain one possible solution. However, those controls are far from complete so I decided to offer a minor hack along the same lines.
First, let us consider eclayf's idea from the forums, where I borrowed the name of this blog entry from. The idea is to loop through all menu items and assign a background image to each item based off of the icon assigned. This allows the background image to be customized by the portal admin.
To try this idea out add the following code to your ascx page using the DNNMenu (not solpart). I tested with default blue for DNN.
<script type="text/javascript">
function setupMenuImageCss(id)
{
var menu = dnn.controls.controls[id + '_ctldnnNAV'];
assignImageCss(menu, menu.rootNode);
}
function assignImageCss(menu, parent)
{
var menuNode = new dnn.controls.DNNMenuNode(parent);
var iconCtl = menu.getChildControl(menuNode.id, 'icn');
if (iconCtl)
{
iconCtl.style.display = 'none'; //hide icon
var menuCtr = menu.getChildControl(menuNode.id, 'ctr');
menuCtr.style.backgroundImage = "url('" + iconCtl.src + "')";
}
for (var i=0; i < parent.childNodeCount(); i++)
{
assignImageCss(menu, parent.childNodes(i));
}
}
try
{
document.execCommand("BackgroundImageCache", false, true);
}
catch(err) {}
</script>
<script runat="server">
Private Sub Page_PreRender(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.PreRender
DotNetNuke.UI.Utilities.DNNClientAPI.AddBodyOnloadEventHandler(Me.Page, "setupMenuImageCss('" & dnnNAV.ClientID & "')")
End Sub
</script>
This should cause the icon images to be displayed as the background.
Note: I noticed that if your skin declares the background-color (as the default blue does), the hover will no longer display the background. If you change it to not set the background then the image remains.
While this works, it is far from ideal. For one thing the admin menu item icons are not customizeable. Probably the biggest shortcoming is you can only customize the image, which happens to meet eclayf's requirements, but not generic enough to implement for DotNetNuke.
The idea I have been toying with is allowing the next version of the menu to support known client-side IDs. My current thought is to allow the skin to decide whether to go this route by providing a "safe" known namespace. For example, in your skin you would have a property like ClientIDPrefix="jcompany", then on the client each node will be assigned a unique id, prefixed with that text. So the root would be jcompany__0, jcompany__1, etc. The child of
jcompany__2 would be jcompany__2_1, jcompany__2_2, etc. This way it would allow the skinner
to predefine as many levels as he/she sees fit, and as long as the user of the portal doesn't exceed the predefined number, the skin should work as originally intended. Even if the skinner doesn't provide the level necesssary (something like jcompany__2_2_1_3_5), there is nothing stopping the user from adding the css himself, perhaps copying the value from a class defined higher up. The only pitfal in this approach is the worry about overlapping prefixes with other controls on the page.
In order to try this concept out add the following code to you ascx file that uses the DNNMenu
<script type="text/javascript">
function setupMenuIds(id, prefix)
{
var menu = dnn.controls.controls[id + '_ctldnnNAV'];
assignMenuIds(menu, menu.rootNode, prefix, '');
}
function assignMenuIds(menu, parent, prefix, id)
{
var menuNode = new dnn.controls.DNNMenuNode(parent);
var menuCtr = menu.getChildControl(menuNode.id, 'ctr');
var newId = prefix + '_' + id;
if (menuCtr)
menuCtr.id = newId;
for (var i=0; i
<script runat="server">
Private Sub Page_PreRender(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.PreRender
DotNetNuke.UI.Utilities.DNNClientAPI.AddBodyOnloadEventHandler(Me.Page, "setupMenuIds('" & dnnNAV.ClientID & "', 'jcompany')")
End Sub
</script>
Then in your css file add something like
#jcompany__0
{
background: red;
}
#jcompany__1
{
background: blue;
}
#jcompany__1_0 td
{
background: green;
}
#jcompany__1_1 td
{
background: orange;
}
Note: It is worth noting that in general it is a bad idea to change the ids of a any control, as it most likely will break the control when it does a lookup by its id. In the DNNMenu's case, those references to the lookup are cached internally, thus this reassignment of ids will work.
Right now this is just an idea I'm toying with. If people like the approach I probably will include it in version 2.0 of the DNNMenu. Any feedback on this would be welcome.