Saturday, November 16, 2013

Dynamic Horizontal Navigation In SharePoint 2013 With SPServices


Recently I supported a migration from 2010 to 2013 by creating a new user interface guided by the client. One of the ideas that came out of this was removing the left navigation to support users that had smaller width screens and to keep with the fixed width design the client wanted. I decided to go with the Horizontal Quicklaunch option of 2013 but felt that this was not snappy enough. So….

Here is a video of what it looks like. Of course you can change that!!

Dynamic Secondary Navigation

Bring on the SPServices!!

Yes, another great use for this cool tool. If you don’t have it, simply get it from here. This is a great tool and I have used it for many solutions. For this solution, I had to modify the master page to remove the left navigation and add the horizontal quick launch code.

1 <div id="breadcrumbBox" class="ms-breadcrumb-box ms-verticalAlignTop ms-tableRow"> 2 <h1 id="pageTitle" class="ms-core-pageTitle"> 3 <SharePoint:AjaxDelta id="DeltaPlaceHolderPageSiteName" class="ms-core-navigation" runat="server"> 4 <asp:ContentPlaceHolder ID="ContentPlaceHolder1" runat="server" /> 5 <SharePoint:SPLinkButton runat="server" NavigateUrl="~site/" id="onetidProjectPropertyTitle1"> 6 <SharePoint:ProjectProperty Property="Title" runat="server" /> 7 </SharePoint:SPLinkButton> 8 </asp:ContentPlaceHolder> 9 </SharePoint:AjaxDelta> 10 </h1> 11 <SharePoint:AjaxDelta id="DeltaHorizontalQuickLaunch" class="ms-core-navigation ms-belltown-quicklaunch" role="navigation" BlockElement="true" runat="server"> 12 <div class="ms-quicklaunchouter"> 13 <div class="ms-quickLaunch"> 14 <Sharepoint:SPNavigationManager id="QuickLaunchNavigationManager" runat="server" QuickLaunchControlId="V4QuickLaunchMenu" ContainedControl="QuickLaunch" EnableViewState="false"> 15 <SharePoint:DelegateControl runat="server" ControlId="QuickLaunchDataSource"> 16 <Template_Controls> 17 <asp:SiteMapDataSource SiteMapProvider="SPNavigationProvider" ShowStartingNode="False" id="QuickLaunchSiteMap" StartingNodeUrl="sid:1025" runat="server"/> 18 </Template_Controls> 19 </SharePoint:DelegateControl> 20 <SharePoint:AspMenu id="V4QuickLaunchMenu" runat="server" EnableViewState="false" DataSourceId="QuickLaunchSiteMap" UseSimpleRendering="true" Orientation="Horizontal" StaticDisplayLevels="1" DynamicHorizontalOffset="0" AdjustForShowStartingNode="true" MaximumDynamicDisplayLevels="5" StaticPopoutImageUrl="/_layouts/15/images/menudark.gif?rev=23" StaticPopoutImageTextFormatString="" SkipLinkText="" StaticSubMenuIndent="0"/> 21 </Sharepoint:SPNavigationManager> 22 </div> 23 </div> 24 </SharePoint:AjaxDelta> 25 </div>

 

So this was the update to the master page. The next step was to update my custom CSS file with the formatting of the menu as desired by the client. There are some CSS rules not included but of course you can design how you want!!


 

1 #breadcrumbBox, .ms-breadcrumb-box 2 { 3 width: 1100px; 4 /* background-color: rgba(255, 255, 255, 0.65); */ 5 background-image: url('/Style Library/en-us/Themable/images/white-25percent-9x9.png'); 6 background-repeat: repeat repeat; 7 background-size: 9px 9px; 8 margin: 10px auto 0 auto; 9 height: 100%; 10 display: block; } 11 #breadcrumbBox h1, .ms-breadcrumb-box h1 { font-size: 1.5em; background: transparent; padding-left: 5px;} 12 #breadcrumbBox h1 span, .ms-breadcrumb-box h1 span { background: transparent; } 13 14 .menu 15 { 16 position: relative; 17 width: 1100px; 18 height: 36px; 19 margin: 0 auto; 20 padding: 0; 21 background: transparent; 22 z-index: 300 !important; 23 } 24 .menu ul li a, .menu ul li a:visited 25 { 26 display: block; 27 width: 120px; 28 height: 28px; 29 /*background: transparent; */ 30 padding-top: 8px; 31 border-right: 1px solid #FFFFFF; 32 text-decoration: none; 33 text-align: center; 34 font-family: "Segoe UI"; 35 font-size: 13px; 36 color: #000000; 37 border: none; 38 } 39 .menu ul 40 { 41 padding: 0; 42 margin: 0; 43 list-style: none; 44 } 45 .menu ul li 46 { 47 float: left; 48 position: relative; 49 } 50 .menu ul li ul 51 { 52 display: none; width: 240px; 53 } 54 .menu ul li:hover a {color:#f28a0a;} 55 .menu ul li:hover ul {display:block; position:absolute; top:36px; left:0; width:240px; border: 1px solid #333;} 56 .menu ul li:hover ul li a.hide {background:#ccddff; color:#000;} 57 .menu ul li:hover ul li:hover a.hide {background:#ffffff; color:#000;} 58 .menu ul li:hover ul li ul {display:none;} 59 .menu ul li:hover ul li a {display:block; background:#ffffff; color:#000; width: 240px; text-align: left; text-indent: 10px;} 60 .menu ul li:hover ul li a:hover {background:#410181; color:#ffffff;} 61 .menu ul li:hover ul li:hover ul {display:block; position:absolute; left:240px; top:0;} 62 .menu ul li:hover ul li:hover ul.left {left:-240px;} 63 .arrowdown { background-image: url('/_layouts/images/menu-down.gif'); background-repeat: no-repeat; background-position: center right; } 64 .arrowright { background-image: url('/_layouts/images/menu-right.gif'); background-repeat: no-repeat; background-position: center right; } 65 66 .ms-dialog #breadcrumbBox { display: none !important; }

 

The last piece of this CSS code is used to hide the box in a dialog so it doesn’t get in the way when uploading files or editing content. So now we have the master page updated, and the custom CSS needed, so we just need the cool SPServices code!


 

1 (function ($) { 2 3 // String constants 4 var SLASH = "/"; 5 var cmlQry; // Internal variable for SPServices CAML Query 6 var cmlVfs; // Internal variable for SPServices ViewFields 7 var cx, cy, windowHandle; 8 var servertemplate; 9 var zhtml = ""; 10 var yhtml = ""; 11 var listshtml = ""; 12 var documentshtml = ""; 13 var imageshtml = ""; 14 var linkshtml = ""; 15 var eventshtml = ""; 16 var pageshtml = ""; 17 var discushtml = ""; 18 var siteshtml = ""; 19 var hassites = false; 20 var thisSite; 21 var tsite, ttitle; 22 var viewlsts; 23 24 $.fn.SPSTools_HorizontalSiteNav = function (options) { 25 var opt = $.extend({}, defaults, options); 26 switch (opt.operation) { 27 case "initialize": 28 var thisUserAccount = $().SPServices.SPGetCurrentUser({ 29 fieldName: "Name", 30 debug: false 31 }); 32 33 thisUserAccount = new String(thisUserAccount); 34 35 thisSite = $().SPServices.SPGetCurrentSite(); 36 $().SPServices({ 37 operation: "GetWeb", 38 webURL: thisSite, 39 completefunc: function (xData, Status) { 40 $(xData.responseXML).SPFilterNode("Web").each(function () { 41 ttitle = $(this).attr("Title"); 42 tsite = $(this).attr("URL"); 43 }); 44 } 45 }); 46 47 $().SPServices({ 48 operation: "GetWebCollection", 49 webURL: thisSite, 50 completefunc: function (xData, Status) { 51 $(xData.responseXML).find("Webs > Web").each(function () { 52 if ($(this).attr("Title") != null || $(this).attr("Title") != "") { 53 hassites = true; 54 if (siteshtml == "") { siteshtml += '<ul>'; } 55 siteshtml += '<li><a href="' + $(this).attr("Url") + '">' + $(this).attr("Title") + '</a></li>'; 56 } 57 }); 58 } 59 }); 60 61 viewlsts = thisSite + "/_layouts/15/viewlsts.aspx"; 62 63 zhtml += '<h1 id="pageTitle" class="ms-core-pageTitle"><span id="DeltaPlaceHolderPageSiteName" class="ms-core-navigation">'; 64 zhtml += '<a id="ctl00_onetidProjectPropertyTitle1" href="' + thisSite + '">' + ttitle + '</a>'; 65 zhtml += '</span></h1><div class="menu" role="navigation">'; 66 zhtml += '<ul>'; 67 zhtml += '<li id="ql_li_lists"><a class="hide" href="' + viewlsts + '">Lists</a></li>'; 68 69 zhtml += '<li id="ql_li_libraries"><a class="hide arrowdown" href="' + viewlsts + '">Libraries</a>'; 70 zhtml += '<ul>'; 71 zhtml += '<li id="ql_li_documents"><a href="' + viewlsts + '">Documents</a></li>'; 72 zhtml += '<li id="ql_li_images"><a href="' + viewlsts + '">Images</a></li>'; 73 zhtml += '</ul></li>'; 74 75 zhtml += '<li id="ql_li_links"><a class="hide" href="' + viewlsts + '">Links</a></li>'; 76 77 zhtml += '<li id="ql_li_events"><a class="hide" href="' + viewlsts + '">Events and Tasks</a></li>'; 78 79 zhtml += '<li id="ql_li_pages"><a class="hide" href="' + viewlsts + '">Pages</a></li>'; 80 81 zhtml += '<li id="ql_li_discussions"><a class="hide" href="' + viewlsts + '">Discussions</a></li>'; 82 83 if (hassites == true) { 84 zhtml += '<li id="ql_li_sites"><a class="hide" href="' + viewlsts + '">Sites</a></li>'; 85 } 86 87 zhtml += '<li id="ql_li_sitecontents"><a class="hide" href="' + viewlsts + '">All Site Contents</a></li>'; 88 89 zhtml += '</ul></div>'; 90 91 $("#breadcrumbBox").html("").append(zhtml); 92 if (siteshtml != "") { 93 siteshtml += '</ul>'; 94 $("#ql_li_sites").find('a').addClass('arrowdown'); 95 $("#ql_li_sites").append(siteshtml); 96 } 97 // Get all the lists 98 $().SPServices({ 99 operation: "GetListCollection", 100 webURL: thisSite, 101 completefunc: function (xData, Status) { 102 $(xData.responseXML).SPFilterNode("List").each(function () { 103 servertemplate = $(this).attr("ServerTemplate"); 104 yhtml += servertemplate + ", " + $(this).attr("Title") + "=:="; 105 switch (servertemplate) { 106 case "100": 107 if (listshtml == "") { listshtml += '<ul>'; } 108 listshtml += '<li><a href="' + $(this).attr("DefaultViewUrl") + '">' + $(this).attr("Title") + '</a></li>'; 109 break; 110 111 case "101": 112 if (documentshtml == "") { documentshtml += '<ul>'; } 113 documentshtml += '<li><a href="' + $(this).attr("DefaultViewUrl") + '">' + $(this).attr("Title") + '</a></li>'; 114 break; 115 116 case "115": 117 if (documentshtml == "") { documentshtml += '<ul>'; } 118 documentshtml += '<li><a href="' + $(this).attr("DefaultViewUrl") + '">' + $(this).attr("Title") + '</a></li>'; 119 break; 120 121 case "103": 122 if (linkshtml == "") { linkshtml += '<ul>'; } 123 linkshtml += '<li><a href="' + $(this).attr("DefaultViewUrl") + '">' + $(this).attr("Title") + '</a></li>'; 124 // May add another fly out with the actual links 125 break; 126 127 case "104": 128 if (listshtml == "") { listshtml += '<ul>'; } 129 listshtml += '<li><a href="' + $(this).attr("DefaultViewUrl") + '">' + $(this).attr("Title") + '</a></li>'; 130 break; 131 132 case "105": 133 if (listshtml == "") { listshtml += '<ul>'; } 134 listshtml += '<li><a href="' + $(this).attr("DefaultViewUrl") + '">' + $(this).attr("Title") + '</a></li>'; 135 break; 136 137 case "106": 138 if (eventshtml == "") { eventshtml += '<ul>'; } 139 eventshtml += '<li><a href="' + $(this).attr("DefaultViewUrl") + '">' + $(this).attr("Title") + '</a></li>'; 140 break; 141 142 case "107": 143 if (eventshtml == "") { eventshtml += '<ul>'; } 144 eventshtml += '<li><a href="' + $(this).attr("DefaultViewUrl") + '">' + $(this).attr("Title") + '</a></li>'; 145 break; 146 147 case "108": 148 if (discushtml == "") { discushtml += '<ul>'; } 149 discushtml += '<li><a href="' + $(this).attr("DefaultViewUrl") + '">' + $(this).attr("Title") + '</a></li>'; 150 break; 151 152 case "109": 153 if (imageshtml == "") { imageshtml += '<ul>'; } 154 imageshtml += '<li><a href="' + $(this).attr("DefaultViewUrl") + '">' + $(this).attr("Title") + '</a></li>'; 155 break; 156 157 case "851": 158 if (imageshtml == "") { imageshtml += '<ul>'; } 159 imageshtml += '<li><a href="' + $(this).attr("DefaultViewUrl") + '">' + $(this).attr("Title") + '</a></li>'; 160 break; 161 162 case "119": 163 if (pageshtml == "") { pageshtml += '<ul>'; } 164 pageshtml += '<li><a href="' + $(this).attr("DefaultViewUrl") + '">' + $(this).attr("Title") + '</a></li>'; 165 break; 166 167 case "850": 168 if (pageshtml == "") { pageshtml += '<ul>'; } 169 pageshtml += '<li><a href="' + $(this).attr("DefaultViewUrl") + '">' + $(this).attr("Title") + '</a></li>'; 170 break; 171 172 default: 173 break; 174 } 175 }); 176 if (thisUserAccount.indexOf("dwalker") > 0) { 177 //alert(yhtml); 178 } 179 if (listshtml != "") { 180 listshtml += '</ul>'; 181 $("#ql_li_lists").find('a').addClass('arrowdown'); 182 $("#ql_li_lists").append(listshtml); 183 } 184 185 if (documentshtml != "") { 186 documentshtml += '</ul>'; 187 $("#ql_li_documents").find('a').addClass('arrowright'); 188 $("#ql_li_documents").append(documentshtml); 189 } 190 191 if (linkshtml != "") { 192 linkshtml += '</ul>'; 193 $("#ql_li_links").find('a').addClass('arrowdown'); 194 $("#ql_li_links").append(linkshtml); 195 } 196 197 if (eventshtml != "") { 198 eventshtml += '</ul>'; 199 $("#ql_li_events").find('a').addClass('arrowdown'); 200 $("#ql_li_events").append(eventshtml); 201 } 202 203 if (discushtml != "") { 204 discushtml += '</ul>'; 205 $("#ql_li_discussions").find('a').addClass('arrowdown'); 206 $("#ql_li_discussions").append(discushtml); 207 } 208 209 if (imageshtml != "") { 210 imageshtml += '</ul>'; 211 $("#ql_li_images").find('a').addClass('arrowright'); 212 $("#ql_li_images").append(imageshtml); 213 } 214 215 if (pageshtml != "") { 216 pageshtml += '</ul>'; 217 $("#ql_li_pages").find('a').addClass('arrowdown'); 218 $("#ql_li_pages").append(pageshtml); 219 } 220 } 221 }); 222 223 break; 224 225 default: 226 break; 227 } 228 }; 229 230 var defaults = { 231 operation: '', // Operation to perform 232 action: '', // action to perform with or after operation if any. Can be another operation 233 tab: '', // id of tab element. Will expand later 234 cqry: '', // CAML query 235 cvfs: '', // CAML viewfields 236 SelectedDiv: '' 237 }; 238 239 })(jQuery);

So there is the code! I did write it as a jQuery plugin as I may expand it later and add more options or possibly include the styles in the plugin itself. I have another javascript file that actually calls the function to draw it on the screen. It just calls the initialize part.

1 $().SPSTools_HorizontalSiteNav({ operation: 'initialize' });

So there you have it! I hope that this will be useful to you all and if you have any suggestions for improvement or feature additions, just let me know!!