Metro Style Site Directory for SharePoint Using EMCAScript

I’ve always been trying to come up with a useful and clever way to allow users to navigate around SharePoint sites. Recently I put together an “Application Directory” which basically displayed a menu system to navigate around apps. Using the JavaScript Class Library for SharePoint to pull values out of SharePoint I quickly put together something that normally would be a lot of C# code and a web part.

The EMCAScript object model is powerful in that you can quickly pull data out of SharePoint sites and lists and make a pleasant user experience with just a little JavaScript, CSS, HTML, and jQuery. It still will make a call back to the server to fetch the data but it’s done asynchronously so the perception to the user is almost seamless.

This post walks you through building a site directory of sorts. It could be used as a landing page on a top level site collection or as a web part sitting on a team site (to show the contents below the site). It’s up to you but the net result is a nice navigation system (done with a little “Metro” styling) all done in a hundred lines of JavaScript.

Here’s what we’re building:

Let’s start with the simplest thing possible. A script that we’ll insert into the page using the Content Editor Web Part via a Content Link. This is my preferred way of doing lightweight adds to SharePoint (like JavaScript or even just raw HTML). The Content Link points to a file in my SiteAssets library in the site and since it’s a link it just gobbles up the content and serves it up. If you try adding HTML to a Content Editor Web Part you’ll find a nice message after saving “Your HTML may have been modified”.

WTF?

Yeah, SharePoint deliciously will go in after you save your nicely formatted content and do some neat things like rename your CSS entities. Oh yeah, it’ll also strip away your JavaScript if it doesn’t like it.

Trust me. Just include the file and you’ll be much better off in the end. Having the file located in the SiteAssets library also lets me just crack it open in SharePoint Designer and while the editor isn’t the greatest, it does give you some Intellisense but the real advantage is saving it in SPD then hitting refresh on your page to see the effect. The file will contain the CSS, JavaScript, and HTML markup. I like putting everything together so I don’t have to worry about files all over my system but you can just as easily use multiple files if you want.

Alright, back to business. Create a new file in your SiteAssets library (you get a SiteAssets library when you create a new site regardless of what template you use or what feature you activate, it’s always there and accessible through SharePoint Designer).

Name the file SiteDirectory.js or something. Doesn’t really matter and you can call it ISavedTheWorldUsingPork.HowAboutThat if you want, but leaving it with a .js extension will give you some semblance of Intellisense inside of SPD.

Drop a Content Editor Web Part on the home page of your site (or wherever you want to put this). This could be a Wiki Page (the Home.aspx page is a wiki page if you activate the Wiki Home Page Feature on a site) or a Web Part Page. There are a few small tweaks you should do depending on what type of page you put this on but just adjust the CSS we’ll be building as you see fit.

Starting simple we're going to just enumerate the child sites and display them in an unordered list. The list is easy to style and works well with jQuery later to be able to animate or attach plugins to.

Here’s the initial code that we’ll put into the SiteDirectory.js file:

1 <style> 2 </style> 3 4 <script type="text/javascript" src="/siteassets/jquery-1.4.2.min.js"></script> 5 6 <script> 7 8 var sites; 9 10 ExecuteOrDelayUntilScriptLoaded(loadSites, 'SP.js'); 11 12 function loadSites() 13 { 14 var context = new SP.ClientContext.get_current(); 15 if(context != undefined && context != null) { 16 var web = context.get_web(); 17 this.sites = web.getSubwebsForCurrentUser(null); 18 context.load(this.sites); 19 context.executeQueryAsync( 20 Function.createDelegate(this, this.onSuccess), 21 Function.createDelegate(this, this.onFailed)); 22 } 23 } 24 25 function onSuccess() 26 { 27 var items = this.sites.getEnumerator(); 28 var menuNavContent = ''; 29 30 menuNavContent += '<h1>Apps</h1>'; 31 menuNavContent += '<ul>'; 32 33 while(items.moveNext()) 34 { 35 var item = items.get_current(); 36 var itemUrl = item.get_serverRelativeUrl(); 37 var title = item.get_title(); 38 39 menuNavContent += '<li>'; 40 menuNavContent += '<a href="' + itemUrl + '">' + title + '</a>'; 41 menuNavContent += '</li>'; 42 } 43 44 menuNavContent += '</ul>'; 45 $('#menuNav').html(menuNavContent); 46 } 47 48 function onFailed(sender, args) 49 { 50 $('#menuNav').html(args.get_message()); 51 } 52 53 </script> 54 55 <div id="appdir"> 56 <div id="menuNav">Loading...</div> 57 </div>

Like I said, all this is doing is a) enumerating through the list of subsites then b) spitting them out into an unordered list. Here’s a breakdown of how this works:

Line 1-2: We’ll include some CSS styles here later

Line 4: We include jQuery so we can a) make it easy to replace elements on the page and b) support plugins later. You can choose to use pure JavaScript or omit this if your site already includes jQuery.

Line 8: We declare a variable we’re going to use to hold the list of sites

Line 10: We wait until the core JavaScript files are loaded by SharePoint. This ensures the ClientContext is setup for us when we need it.

Line 12-23: This is the function  that calls the Client Object Model to get our web then get the subwebs for the current user. Finally on lines 19-21 we execute the call (which is where we talk to the server) and define the success and failed methods.

Line 25-46: We define the success function to call with our list of subwebs. Here we’re getting an enumerator to the web collection and iterating through them, grabbing the url and title of the site then creating our unordered list using regular HTML markup. Finally on line 45 we find the DIV tag we’re replacing and substitute the HTML we just created.

Line 55-57: This is the HTML markup we’re going to replace in our JavaScript. We initially set the text to “Loading…” so users will see this when the page loads then *magically* it’ll get replaced with our content.

Here’s the result:

Looks good and lets us know we’re on the right track. If there are any failures you’ll see them here because our failed function will get called and output the error message. This could be anything from a JavaScript error to not calling a known method. Also note that this is already security trimmed since we’re using the getSubwebsForCurrentUser method so we’ll only see sites the user has access to.

Let’s add another list and DIV tag so we have two lists to use:

1 function onSuccess() 2 { 3 var items = this.sites.getEnumerator(); 4 var menuNavContent = ''; 5 var contentNavContent = ''; 6 7 menuNavContent += '<h1>Apps</h1>'; 8 menuNavContent += '<div>'; 9 menuNavContent += '<ul>'; 10 11 contentNavContent += '<div>'; 12 contentNavContent += '<ul>'; 13 var counter = 1; 14 15 while(items.moveNext()) 16 { 17 var item = items.get_current(); 18 var itemUrl = item.get_serverRelativeUrl(); 19 var title = item.get_title(); 20 21 menuNavContent += '<li>'; 22 menuNavContent += '<a href="' + itemUrl + '">' + title + '</a>'; 23 menuNavContent += '</li>'; 24 25 contentNavContent += '<li>'; 26 contentNavContent += title; 27 contentNavContent += '</li>'; 28 } 29 30 menuNavContent += '</ul>'; 31 menuNavContent += '</div>'; 32 $('#menuNav').html(menuNavContent); 33 34 contentNavContent += '</ul>'; 35 contentNavContent += '</div>'; 36 $('#contentNav').html(contentNavContent); 37 } 38 39 </script> 40 41 <div id="appdir"> 42 <div id="menuNav">Loading...</div> 43 <div id="contentNav">Loading...</div> 44 </div>

Not much to explain here, just added a new DIV tag and built up the HTML just like the original. Now we have two unordered lists. We also wrapped up each list in its own DIV tag.

Now we’ll do some simple styling by floating the list of sites down the left hand side and the second list on the right and applying a little styling to the text.

1 <style> 2 #menuNav 3 { 4 float: left; 5 width: 170px; 6 padding-left: 9px; 7 } 8 #contentNav 9 { 10 float:left; 11 } 12 #applist > ul 13 { 14 list-style: none outside none; 15 padding-left: 0; 16 } 17 #applist > h1 18 { 19 margin-top: 12px; 20 color: #333333; 21 font: 36px/42px "Segoe WPC Light", "Segoe UI Light", Helvetica, Arial, Sans-Serif; 22 } 23 #appbox 24 { 25 width: 700px; 26 } 27 #appbox > ul 28 { 29 list-style: none; 30 float: left; 31 overflow: hidden; 32 margin: 0 auto; 33 padding: 10px; 34 } 35 </style> 36 37 function onSuccess() 38 { 39 var items = this.sites.getEnumerator(); 40 var menuNavContent = ''; 41 var contentNavContent = ''; 42 43 menuNavContent += '<div id="applist">'; 44 menuNavContent += '<h1>Apps</h1>'; 45 menuNavContent += '<ul>'; 46 47 contentNavContent += '<div id="appbox">'; 48 contentNavContent += '<ul>'; 49 var counter = 1; 50 51 while(items.moveNext()) 52 { 53 var item = items.get_current(); 54 var itemUrl = item.get_serverRelativeUrl(); 55 var title = item.get_title(); 56 57 menuNavContent += '<li>'; 58 menuNavContent += '<a href="' + itemUrl + '">' + title + '</a>'; 59 menuNavContent += '</li>'; 60 61 contentNavContent += '<li>'; 62 contentNavContent += title; 63 contentNavContent += '</li>'; 64 } 65 66 menuNavContent += '</ul>'; 67 menuNavContent += '</div>'; 68 $('#menuNav').html(menuNavContent); 69 70 contentNavContent += '</ul>'; 71 contentNavContent += '</div>'; 72 $('#contentNav').html(contentNavContent); 73 } 74 </script> 75

Here’s the updated output:

Now it’s starting to look like our target. Let’s style the menu list with a larger font. We’ll also just make one line of code change in our markup in the onSuccess method. Find the line that says menuNavContent += ‘<ul>’ and change it to read menuNavContent += ‘<ul class=”apps”>’. This will style just the unordered list of items on the left.

Here are the new styles to add to the CSS

1 <style> 2 #appdir 3 { 4 font: 15px/19px "Segoe WPC", "Segoe UI", Helvetica, Arial, "Arial Unicode MS", Sans-Serif; 5 } 6 #applist > ul.apps li 7 { 8 margin-bottom: 9px; 9 overflow: hidden; 10 } 11 #applist > ul.apps li a 12 { 13 text-decoration: none; 14 } 15 #applist > ul.apps li a:hover 16 { 17 color: red; 18 } 19 </style> 20

Now that we have the list done lets focus on the second list which will form our tiles. They’re not as live and vibrant as they could be but they do show some metadata from the site so are at least a little more informative than just navigation boxes.

First we’ll apply some styles to the list to make them into boxes and space them apart. It’s just CSS markup here to add and a couple of small changes in the construction of the HTML for the second list.

1 <style> 2 #appdir 3 { 4 font: 15px/19px "Segoe WPC", "Segoe UI", Helvetica, Arial, "Arial Unicode MS", Sans-Serif; 5 } 6 #menuNav 7 { 8 float: left; 9 width: 170px; 10 padding-left: 9px; 11 } 12 #contentNav 13 { 14 float:left; 15 } 16 #applist > ul 17 { 18 list-style: none outside none; 19 padding-left: 0; 20 } 21 #applist > ul.apps li 22 { 23 margin-bottom: 9px; 24 overflow: hidden; 25 } 26 #applist > ul.apps li a 27 { 28 text-decoration: none; 29 } 30 #applist > ul.apps li a:hover 31 { 32 color: red; 33 } 34 #applist > h1 35 { 36 margin-top: 12px; 37 color: #333333; 38 font: 36px/42px "Segoe WPC Light", "Segoe UI Light", Helvetica, Arial, Sans-Serif; 39 } 40 #appbox 41 { 42 width: 700px; 43 } 44 #appbox > ul 45 { 46 list-style: none; 47 float: left; 48 overflow: hidden; 49 margin: 0 auto; 50 padding: 10px; 51 } 52 #appbox > ul.apps li 53 { 54 width: 150px; 55 height: 150px; 56 margin-bottom: 9px; 57 margin-left: 12px; 58 padding-bottom: 4px; 59 padding-left: 4px; 60 float: left; 61 position: relative; 62 color: white; 63 } 64 #appbox > ul.apps li a 65 { 66 color: white; 67 text-decoration: none; 68 } 69 #appbox > ul.apps li a:hover 70 { 71 } 72 .theme_blue 73 { 74 background-color: #1ba1e2; 75 } 76 </style> 77 78 <script> 79 80 function onSuccess() 81 { 82 var items = this.sites.getEnumerator(); 83 var menuNavContent = ''; 84 var contentNavContent = ''; 85 86 menuNavContent += '<div id="applist">'; 87 menuNavContent += '<h1>Apps</h1>'; 88 menuNavContent += '<ul class="apps">'; 89 90 contentNavContent += '<div id="appbox">'; 91 contentNavContent += '<ul class="apps">'; 92 var counter = 1; 93 94 while(items.moveNext()) 95 { 96 var item = items.get_current(); 97 var itemUrl = item.get_serverRelativeUrl(); 98 var title = item.get_title(); 99 100 menuNavContent += '<li>'; 101 menuNavContent += '<a href="' + itemUrl + '">' + title + '</a>'; 102 menuNavContent += '</li>'; 103 104 contentNavContent += '<div style="cursor:pointer;" onclick="'; 105 contentNavContent += "location.href='" + itemUrl + "';"; 106 contentNavContent += '">'; 107 contentNavContent += '<li class="theme_blue">'; 108 contentNavContent += '<div>'; 109 contentNavContent += title; 110 111 contentNavContent += '</div>'; 112 contentNavContent += '</li>'; 113 contentNavContent += '</div>'; 114 } 115 116 menuNavContent += '</ul>'; 117 menuNavContent += '</div>'; 118 $('#menuNav').html(menuNavContent); 119 120 contentNavContent += '</ul>'; 121 contentNavContent += '</div>'; 122 $('#contentNav').html(contentNavContent); 123 } 124 125 </script>

We’re just adding some new styles here. There’s a class called theme_blue set to the Metro blue (#1ba1e2) which we set as the background colour for each tile. In addition we set the entire tile to be clickable to the same url as the site. This lets the user click anywhere on the tile (or the list one the left) to launch the site rather than having to click on the title.

Here’s the updated output.

Now that we have our tiles we can add some dynamic metadata to them. This will be pulled from the website itself and give us a navigation system that’s more information than just links.

1 <style> 2 /* title for tiles */ 3 .tileTitle 4 { 5 bottom: 8px; 6 left: 6px; 7 position: absolute; 8 font-size: 18px; 9 font-weight: bold; 10 } 11 /* subtitle to display at top of tile */ 12 .tileSubtitle 13 { 14 font-size: 13px; 15 position: absolute; 16 top: 4px; 17 left: 6px; 18 } 19 </style> 20 21 <script> 22 23 function onSuccess() 24 { 25 var items = this.sites.getEnumerator(); 26 var menuNavContent = ''; 27 var contentNavContent = ''; 28 29 menuNavContent += '<div id="applist">'; 30 menuNavContent += '<h1>Apps</h1>'; 31 menuNavContent += '<ul class="apps">'; 32 33 contentNavContent += '<div id="appbox">'; 34 contentNavContent += '<ul class="apps">'; 35 var counter = 1; 36 37 while(items.moveNext()) 38 { 39 try 40 { 41 var item = items.get_current(); 42 var itemUrl = item.get_serverRelativeUrl(); 43 var title = item.get_title(); 44 var lastItemModified = getModifiedDateString(new Date(item.get_lastItemModifiedDate())); 45 46 menuNavContent += '<li>'; 47 menuNavContent += '<a href="' + itemUrl + '">' + title + '</a>'; 48 menuNavContent += '</li>'; 49 50 contentNavContent += '<div style="cursor:pointer;" onclick="'; 51 contentNavContent += "location.href='" + itemUrl + "';"; 52 contentNavContent += '">'; 53 contentNavContent += '<li class="theme_blue">'; 54 contentNavContent += '<div>'; 55 56 contentNavContent += '<div class="tileTitle">' + title + '</div>'; 57 contentNavContent += '<div class="tileSubtitle">' + lastItemModified + '</div>'; 58 59 contentNavContent += '</div>'; 60 contentNavContent += '</li>'; 61 contentNavContent += '</div>'; 62 } 63 catch(err) 64 { 65 menuNavContent = err.name; 66 contentNavContent = err.message; 67 } 68 } 69 70 menuNavContent += '</ul>'; 71 menuNavContent += '</div>'; 72 $('#menuNav').html(menuNavContent); 73 74 contentNavContent += '</ul>'; 75 contentNavContent += '</div>'; 76 $('#contentNav').html(contentNavContent); 77 } 78 79 function getModifiedDateString(date) 80 { 81 var rc = ""; 82 rc += (date.getMonth()+1).toString(); 83 rc += "/"; 84 rc += date.getDate().toString(); 85 rc += "/"; 86 rc += date.getFullYear().toString(); 87 rc += " at "; 88 rc += date.getHours().toString(); 89 rc += ":"; 90 rc += date.getMinutes(); 91 if(date.getHours() > 11) 92 { 93 rc += " PM"; 94 } 95 else 96 { 97 rc += " AM"; 98 } 99 return rc; 100 } 101 102 </script> 103

We wrap the title in a DIV tag with a class of tileTitle which lets us style it to place it at the bottom of the tile and give it a larger font. You do need to be careful of the length of the titles of your sites as this doesn’t work for all scenarios but just adjust it to fit your needs.

We also pull the last modified item date from the web properties. Every site tracks whatever the last item that was modified is and holds onto the date for that item. So now users can see when some content on the site was last changed.

Also we parse out the date from SharePoint into a JavaScript Date object and build a formatted date to display on the tile.

The final image:

That’s it! You now have a single script that you can just drop onto any site to create a Metro style navigation to the subsites. New sites can be added and will automatically show up and users can see when the content on the site was last modified and be able to click on the site to visit it.

Here’s the full source code for the page for you play with.

Remember, this is just a start. There are some fun things you can do with this. For example create custom styles for different colours (for example blue for team sites, red for wikis, etc.) and style them accordingly. Other ideas are to pull other data from the site like description, etc. and put that on a bigger tile. Enumerate the number of subsites in a site and display that. There are other properties you can access off the Web object like if RSS is enabled, etc. so you might want to display different icons on the tile to reflect that. The list of properties on the SP.Web class can be found here.

If you’re following the “Metro” style then remember to keep the UI light and simple. Content over chrome. You don’t want to be dumping all kinds of information here, just enough that your users need to make it useful.

Enjoy!

Published Tuesday, February 28, 2012 11:10 AM by Bil Simser

Comments

# re: Metro Style Site Directory for SharePoint Using EMCAScript

Saturday, March 3, 2012 9:38 PM by dimpels73

This is just what I was looking for. Thank you for sharing!

# re: Metro Style Site Directory for SharePoint Using EMCAScript

Thursday, March 8, 2012 12:05 AM by Erica Toelle

Thank you, Bill! This is extremely useful for me. I happen to have a client that is already falling in love with the metro UI and I'm going to send this article to him.

# re: Metro Style Site Directory for SharePoint Using EMCAScript

Friday, April 6, 2012 3:03 PM by carolyn

Great job! Very useful and it looks great. Quick question: how would you go about excluding the Meeting Workspaces? I just want the subsites to show up on tiles. thanks!

# re: Metro Style Site Directory for SharePoint Using EMCAScript

Friday, May 18, 2012 3:21 AM by Myrko

Thank you the inspiration. Based on your idea I create the tiles based on a SP-List. So you have the possibility to link everything.

# re: Metro Style Site Directory for SharePoint Using EMCAScript

Monday, June 11, 2012 5:22 AM by renato

How you create tiles based on "lists" instead of "sites"?

# re: Metro Style Site Directory for SharePoint Using EMCAScript

Tuesday, June 12, 2012 9:54 AM by Bil Simser

@renato I just posted an update that will read from lists instead of sites. You can check it out here weblogs.asp.net/.../metro-sharepoint-directory-reading-from-lists.aspx

# re: Metro Style Site Directory for SharePoint Using EMCAScript

Tuesday, June 12, 2012 11:22 AM by Jos&#233; Marcos

Thank you for sharing!

# re: Metro Style Site Directory for SharePoint Using EMCAScript

Sunday, June 17, 2012 10:28 AM by Victor

I have get the  full source code place it in site assets link it via content editor and I get 2 "loading" messages, could anybody tell me what I am doing worng? Thanks in advance

# re: Metro Style Site Directory for SharePoint Using EMCAScript

Thursday, June 28, 2012 1:57 AM by Egil Myklestad

Great post! I'm just getting started with EMCAScript and really appreciate you sharing this. I'm gonna check out your 'reading from list' post next.

# re: Metro Style Site Directory for SharePoint Using EMCAScript

Friday, July 20, 2012 3:53 AM by Myrko

Hi, can you tell me why the apps are arranged vertically on Chrome and Firefox?