SharePoint 2013 Responsive CSS Mega Menu

The standard SharePoint 2013 top navigation menu just wasn't blowing my hair back. There are some decent examples of how to style the top menu but nothing close to what I was looking to do. I wanted a Mega Menu type layout without heaps of flair which was simple to implement and easy for others to understand. After looking around at various options like CSS friendly control adapters, custom code and jQuery hacks, I was able to get pretty close with just pure CSS and some SharePoint 2013 Design Manager Top Menu configuration options. The result was a responsive CSS menu that is simple to manage.

My requirements were pretty easy; only CSS contained in a custom stylesheet, term store driven hierarchy and as little as possible complexity. The twist was to have 3 levels of hierarchy build out in the term store. I like the flexibility and it allowed me to build out the navigation with custom ordering, labels and groupings.

The implementation isn't as straight forward as creating a custom stylesheet and overriding the existing bloated SharePoint 2013 menu styles. However, it is (what I consider) super clean. Here's what you'll need:

  • New master page or a copy of an existing
  • Custom stylesheet referenced in your Master Page settings located in Site Settings
  • Managed Meta Data Term Set for the Menu items

Crazy right? "But I'm already doing this", you say. I know, I know it gets more complicated…perhaps. It was for me at first because I hadn't used the Design Manager that new in SharePoint 2013. It's worth learning if you haven't taken the time yet. With Design Manager you can work with simple HTML templates and build master pages. Much quicker than dealing with the full blown SharePoint master pages and trying to figure out what needs to stay and what can go.

sharePoint_mega_menu_1 sharePoint_mega_menu_2 sharePoint_mega_menu_3 sharePoint_mega_menu_4 sharePoint_mega_menu_5

  1. Build out a navigation term set that will control your menu items. Site Settings –> Navigation. Make sure Managed Navigation is selected
  2. Download and rename the master page HTML template of your choice from Site Settings –> Master Pages. I used seattle.html and renamed it to super_simple.html.
  3. Upload it back to the Master Page Gallery, edit the properties and associate a file with it. That will create the associated super_simple.master file.
  4. In Design Manager click Top Navigation and change the following configuration options on the right side:

     

     

     

     

     

    1. CssClass: nav
    2. MaximumDynamicDispalyLevels: 0
    3. StaticDisplayLevels: 4

 

Create a new HTML master page

Site Settings –> Master pages and page layouts
Download a copy of seattle.html
Rename to simple.html and upload into Master Page Gallery
Publish a Major Version so that it shows up Site Settings –> Master Page
Specify the new master page called 'simple' and hit OK

Open up Design Manager under Look and Feel and click Edit Master Pages and click 'simple'
Click Snippets in upper right corner
Click Top Navigation under Design tab
Under Customization – Top Navigation ( AjaxDelta ) define a CssClass of your choosing. I used 'topNav'.
Under Customization – Top Navigation ( AspMenu ) change StaticDisplayLevels to fit your hierarchy. I used 4.
Click Update at the bottom
You will now need to copy the new HTML Snippet to use in your HTML Master Page. Click Copy to Clipboard.

Edit the simple.html file you downloaded earlier and find the AspMenu control (around line 218).
Paste the new snippet above then remove the old and save. Upload to the Master Page Gallery and overwrite the old one.
You may need to remove some residual code like duplicated content placeholders, etc. but the errors are easy to read.
If you are successfull you can browse to the home and will see your links across the top.

Open SharePoint Designer and create a new stylesheet in the style library. I called my custom.css.
Add the following style and publish a Major Version:

/* ————— MEGA MENU ————— */
/* reset all ULs in top nav */
.nav ul {
    padding: 0;
    margin: 0;
}
.nav div, ul.root {
    position: relative;
    width: 100%;
    background: #f7f7f7;
}
/* hide all levels */
.nav ul > li > ul {
    display: none;
}
/* target top level – selector + psuedo */
.nav ul.root > li:hover > ul {
    display: block;
    position: absolute;
    left: 0;
    width: 100%;
    z-index: 999;
    background: #0072c6;
    padding:10px 0 20px 0;
}
.nav ul > li > ul > li > ul {
    display: block;
    position: relative;    
}
.nav ul li li {
    float: left;
    margin: 0 15px;
}
.nav ul li li li {
    clear: both;
    padding: 0;
    margin: 0;
}
/* link styles */
/* top level */
.nav ul.root > li > a {
    padding: 10px 20px;
    /* override core styles */
    margin-right: 0 !important;
    border-right: 1px #ddd solid !important;    
}
/* first level hover */
.nav ul.root > li:hover {
    background: #0072c6;
}
.nav ul.root > li:hover > a > span {
    color:#fff;
}
/* every span past first level */
.nav ul.root > li > ul li span {
    color: #fff;
}
.nav ul.root > li > ul li span:hover {
    color: #ddd;
}

/* second level */
.nav ul.root > li > ul > li > a > span {
    font-weight: 900;
}


Set the Alternate CSS URl under Site Settings –> Master Page –> Alternate CSS URL to your custom.css file.
Hit OK and tada!

SQL Query to Get Reporting Services Report Permissions SSRS 2012

Sometimes you need a way to quickly get a list of users that have permissions to all of your SQL Server Reporting Services reports. This is especially true if you have fine grained permissions or use Active Directory roles that are constantly changing.

The idea is simple; query the ReportServer database and find who has access to what. The list of reports and their associated security data can be found in the Catalog and SecData tables. The security values are stored as XML in a column called XmlDescription.

Once you join the report and it's associated security values you need to shred the XML using cross apply. Using the XML datatype methods in SQL Server 2012 you can get to a clean list. Here's the reading material – value() Method (xml Data Type)

select Name
       ,perms.value('.', 'varchar(200)') as UserRole
from (select Name
	     ,cast(XmlDescription as xml) as XmlDesc
      from Catalog
	left outer join SecData
	  on Catalog.PolicyID = SecData.PolicyID) shred
cross apply XmlDesc.nodes('/Policies/Policy/GroupUserName') as XmlNodes(perms)

Pretty simple, quick way to audit your report permissions using a SQL query against SSRS ReportServer database.

SharePoint 2010 Display Form Sections Based on URL Querystring Parameters

So how can you display sections of your form based on where the item is in the approval process? Easy, some basic XHTML, a querystring parameter and javascript.

The idea was have an Edit Item List Form that would show or hide sections based on when the item was in the approval process. I started with a very basic New Item List Form which would collect a minimal amount of data then kickoff an approval workflow. The workflow would direct the first approver to an Edit Item List Form through a formatted link which contained a querystring parameter [EditItem.aspx?ItemID=13&Section=1]. The next approver would get an email which would expose the next form section [EditItem.aspx?ItemID=13&Section=2].

To get started create three new publishing pages;  newItem.aspx, editItem.aspx, thankYou.aspx.

  1. Add a New Item Dataform Web Part to the newItem.aspx page bound to your list and remove the fields you don’t want. I just wanted a Category, Description and Date to initiate the workflow but the list had about 20 columns used in the process.
  2. Add a Edit Item Dataform Web Part to the editItem.aspx page bound to your list.
  3. Add parameter binding to the Dataform Web Part to filter the list item (ItemID) for edititem forms so you can get list item you are interested in.
    <ParameterBinding Name="dvt_apos" Location="Postback;Connection"/>
    <ParameterBinding Name="UserID" Location="CAMLVariable" DefaultValue="CurrentUserName"/>
    <ParameterBinding Name="Today" Location="CAMLVariable" DefaultValue="CurrentDate"/>
    <ParameterBinding Name="dvt_startposition" Location="Postback" DefaultValue=""/>
    <ParameterBinding Name="dvt_firstrow" Location="Postback;Connection"/>
    <ParameterBinding Name="dvt_nextpagedata" Location="Postback;Connection"/>
    <ParameterBinding Name="ListID" Location="None" DefaultValue="11439F77-26F8-4E0A-8DF7-2B3BB608D7E9"/>
    <ParameterBinding Name="ItemID" Location="QueryString(ItemID)" DefaultValue="0"/>
  4. Format the XSL Template. I used tables with ID’s [id=”s2″] and using inline CSS set initial display to none [style=”display:none; border:0; margin:0; padding:0;”] where the ID controls visibility.

     

     

    <tr>
      <td colspan="2">
        <table id="s1" style="display:none; border:0; margin:0; padding:0;">
          <tr>
            <td width="190px" valign="top" class="ms-formlabel">
              <h3 class="ms-standardheader">First Approval</h3>
            </td>
            <td width="400px" valign="top" class="ms-formbody">
             <SharePoint:FormField runat="server" id="ff13{$Pos}" ControlMode="Edit" FieldName="First_x0020_Approval" ItemId="{@ID}" __designer:bind="{ddwrt:DataBind('u',concat('ff13',$Pos),'Value','ValueChanged','ID',ddwrt:EscapeDelims(string(@ID)),'@First_x0020_Approval')}"/>
             <SharePoint:FieldDescription runat="server" id="ff13description{$Pos}" FieldName="First_x0020_Approval" ControlMode="Edit"/>
           </td>
          </tr>
        </table>
        <table id="s2" style="display:none; border:0; margin:0; padding:0;">
          <tr>
            <td width="190px" valign="top" class="ms-formlabel">
              <h3 class="ms-standardheader">Final Approval</h3>
            </td>
            <td width="400px" valign="top" class="ms-formbody">
              <SharePoint:FormField runat="server" id="ff14{$Pos}" ControlMode="Edit" FieldName="Final_x0020_Approval" ItemId="{@ID}" __designer:bind="{ddwrt:DataBind('u',concat('ff14',$Pos),'Value','ValueChanged','ID',ddwrt:EscapeDelims(string(@ID)),'@Final_x0020_Approval')}"/>
            </td>
          </tr>
          <tr>
            <td width="190px" valign="top" class="ms-formlabel">
              <h3 class="ms-standardheader">Status</h3>
            </td>
            <td width="400px" valign="top" class="ms-formbody">
              <SharePoint:FormField runat="server" id="ff15{$Pos}" ControlMode="Edit" FieldName="Status" ItemId="{@ID}" __designer:bind="{ddwrt:DataBind('u',concat('ff15',$Pos),'Value','ValueChanged','ID',ddwrt:EscapeDelims(string(@ID)),'@Status')}"/>
            </td>
          </tr>
        </table>
      </td>
    </tr>
  5. Add a Content Editor Web Part above the Dataform Web Part on the page that contains the following javascript.

     

     

    <script type="text/javascript" language="javascript">
            function myCheckQueryStr() {
                var qs = '&' + (document.location + '?').split('?')[1];
    
                if (qs.match(/&Section=1/)) {
                    document.getElementById("s1").style.display = '';
                }            
                if (qs.match(/&Section=2/)) {
                    document.getElementById("s1").style.display = '';
                    document.getElementById("s2").style.display = '';
                } 
            }
    
            function addLoadEvent(func) {
                var oldonload = window.onload;
                if (typeof window.onload != 'function') {
                    window.onload = func;
                } else {
                    window.onload = function () {
                        oldonload();
                        func();
                    }
                }
            }
            addLoadEvent(myCheckQueryStr);
    </script>

In your workflow the links would look something like this:

  • http://intra/web/Pages/EditItem.aspx?ItemID=13&Section=1
  • http://intra/web/Pages/EditItem.aspx?ItemID=13&Section=2

SSIS Script Task to Upload a File to a Website using C#, HTTP Post with HttpWebRequest

Need a way to upload a generated file to a website using SSIS? The code below uses HttpWebRequest to make page requests, HttpWebResponse to get server responses and CookieContainer to hold the session data.

The website required a session cookie along with login credentials. To complicate things even more the cookie was created on one page, login was on another and file upload was on a third page.

The final result is a way to upload a file to a website using SSIS without any specials tools. This is an alternative to legacy WGET scripts.

using System;
using System.Data;
using Microsoft.SqlServer.Dts.Runtime;
using System.IO;
using System.Text;
using System.Net;

namespace ST_{GUID}.csproj
{
    [System.AddIn.AddIn("ScriptMain", Version = "1.0", Publisher = "", Description = "")]
    public partial class ScriptMain : Microsoft.SqlServer.Dts.Tasks.ScriptTask.VSTARTScriptObjectModelBase
    {

        #region VSTA generated code
        enum ScriptResults
        {
            Success = Microsoft.SqlServer.Dts.Runtime.DTSExecResult.Success,
            Failure = Microsoft.SqlServer.Dts.Runtime.DTSExecResult.Failure
        };
        #endregion

        public void Main()
        {
            HttpWebRequest wPost;
            HttpWebResponse wResponse;
            CookieContainer cookieCont;
            DateTime fileDate = DateTime.Now;

            //suppress self signed cert error if necessary
            //ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };

            string hostURL = @"https://website.com";
            //string testURL = @"https://test.website.com";
            string cookieURL = "/index.html";
            string loginURL = "/loginpage";
            string uploadURL = "/uploadpage";
            string fileToLoad = @"\\server\path\file_" + String.Format("{0:yyyy-MM-dd}", fileDate) + ".txt";
            string userName = "yourLogin";
            string passWord = "yourPassword";
            string loginData = "username=" + userName + "&password=" + passWord;
            byte[] byteArrayLogin = Encoding.UTF8.GetBytes(loginData);

            //get cookie from the first page            
            wPost = (HttpWebRequest)WebRequest.Create(hostURL + cookieURL);
            wPost.CookieContainer = new CookieContainer();
            cookieCont = wPost.CookieContainer;

            //get response
            wResponse = (HttpWebResponse)wPost.GetResponse();
            wResponse.Close();

            //post login creds to login page
            wPost = (HttpWebRequest)WebRequest.Create(hostURL + loginURL);
            wPost.Method = "POST";
            wPost.CookieContainer = cookieCont;
            wPost.ContentType = "application/x-www-form-urlencoded";
            wPost.ContentLength = byteArrayLogin.Length;

            //get request stream and write login data to it
            using (Stream s = wPost.GetRequestStream())
            {
                s.Write(byteArrayLogin, 0, byteArrayLogin.Length);
                s.Close();
            }

            //get response
            wResponse = (HttpWebResponse)wPost.GetResponse();
            wResponse.Close();

            //upload file
            wPost = (HttpWebRequest)WebRequest.Create(hostURL + uploadURL);
            wPost.Method = "POST";
            //set timeout threshold to 5 mins to allow script to run against slow servers
            wPost.Timeout = 600000;
            wPost.CookieContainer = cookieCont;
            wPost.ContentType = "application/x-www-form-urlencoded";

            Stream uploadStream = wPost.GetRequestStream();
            StreamWriter sourceWriter = new StreamWriter(uploadStream);

            //open file
            StreamReader sourceFile = new StreamReader(fileToLoad);

            //loop through file and read each line and write to request stream buffer
            string inLine = sourceFile.ReadLine();
            while (inLine != null)
            {
                sourceWriter.WriteLine(inLine);
                inLine = sourceFile.ReadLine();
            }

            sourceFile.Close();
            sourceWriter.Close();

            //get response
            wResponse = (HttpWebResponse)wPost.GetResponse();

            Stream receiveStream = wResponse.GetResponseStream();
            StreamReader rdrResp = new StreamReader(receiveStream, Encoding.UTF8);
            //output response here if needed
            rdrResp.Close();
            wResponse.Close();

            Dts.TaskResult = (int)ScriptResults.Success;
        }
    }
}

SSIS Date Part Expressions

If you are diving deep into SSIS you're probably familiar with the fancy string manip needed to get your file names and folder paths to line up with various date structures and formats.

Here's a few I've used recently that might come in handy. Feel free to offer improvments and supplements.

Month Date Short Year

@[User::Path]+@[User::FlatFileName] 
+ Right("0" + (DT_STR,4,1252) DatePart("m",getdate()),2) 
+ Right("0" + (DT_STR,4,1252) DatePart("d",getdate()),2) 
+ RIGHT((DT_STR,4,1252)YEAR(GETDATE()),2) 
+ ".txt"

Returns – \\UserPath\FlatFileName021013.txt

Continue reading “SSIS Date Part Expressions”

Get all Workflows from a SharePoint 2010 Application using PowerShell

I needed a way to programmatically get all of the workflows from all site collections and webs. PowerShell looks to be the best answer.

This script returns all items in all sites, webs and lists with workflow attached from the application level. It then outputs a file with a pipe separated list of everything. Continue reading “Get all Workflows from a SharePoint 2010 Application using PowerShell”

Refresh Development from a Production SharePoint 2010 Farm Using Content Database Restore

In the real world it's nice to have a refresh cycle for your development farm. Usually it needs to happen after a large development project or some business process improvement.

The problem is how to update your development site so that it mirrors your current production farm. One answer is to use the publishing infrastructure, create a job and hopefully it runs to completion. In my experience this is messy and takes forever, usually failing at the end with a cliche error.

Another approach is to completely replace the contents of your existing development content database with a copy from production. Here's how I did it: Continue reading “Refresh Development from a Production SharePoint 2010 Farm Using Content Database Restore”