SharePoint Drag Drop Tile Selector - Part 1

The Problem

While working on re-branding, restructuring and organizing the SharePoint Farm, I came across a requirement to have standardized navigation tiles.

tiles like this

There are about 30 tiles to choose from, each has it's own icon and background color.

I suppose I could have just uploaded these icons and made a document about how to use them, but then, nobody would. It would just be too hard.

Solution

What I settled on is a drag and drop, sortable selector with and editable link field. Once the page is in edit mode the selector appears. The designer can then drag the desired items over, customize the link (if the default doesn't work), drag the items into the desired order, and click Set Tiles before saving the page.

drag it drop it sort it

Nifty!

In my situation this was accomplished through a custom page layout, a couple content types and REST calls. It is a local solution, but could be easily ported to use the App model.

Setup

First you need to create a couple of content types. I create these in my Content Type Hub so they are available in all of my collections, hopefully you do too, but if not, this could be done at the Site Collection level instead.

Custom Page Layout Content Type

Create a new Content Type, using Article Page as the Parent.

page content type

Once that is done scroll to the bottom and select Add from new site column. Name it something reasonable, ideally without spaces. I named it "KEH Page Icons JSON Object" which is a touch difficult to use later in this process. C'est la vie.

Make it multiple lines of text. Super Important Set Allow unlimited length in libraries to YES. Save that thing.

Icon Library Content Type

Create another Content Type, this time using Image as the Parent.

Only 2 columns to add here, Background Color and Link for this icon. Both are single line of text.

Either add this content type to your Site Assets library (for the collection) or create a new library at the Site Collection Level that uses this content type (this is what I did). Upload some icons, place a rgb value in the color column (ex. 111,111,111) and a default url for each (these can be overidden when the icons are used).

Page Layout

Before I get ahead of myself, this will only work on a site that has the Publishing feature enabled. So if you haven't yet done that, go to your settings and enable that. I'll wait...

Oh yeah, you will also need jQuery and jQuery-ui (for the drag, drop & sorting). These will go in your (Site Collection) masterpage folder. You can do a custom jQuery-UI download that only has the widget factory, draggable, droppable and sortable. To make life easy, go get bootstrap (I just use the grid and maybe tables from the custom download section - it's much smaller and doesn't interfere with SharePoint too much). I will keep waiting...

About that masterpage folder... One way to get there is navigate to

http(s)://<yoursite-collection>/_catalogs/masterpage/Forms/AllItems.aspx  

you could also map this as a local drive to save a bunch of time (I'm not telling you what to do, but do this).

Now create a folder structure:

--custom-master-pages
    --css
    --js

Put your jquery files in the js folder and the bootstrap in the css folder. While your at it create a few new files. In the css folder add a styles.css file. In the js folder add custom-request.js and script.js.

--custom-master-pages
    --css
        --bootstrap.min.css
        --styles.css
    --js
        --jquery-1.10.2.min.js
        --custom-request.js
        --script.js

Let's assume you don't have any custom layouts yet. Navigate to the custom-master-pages folder, select New Document from the Ribbon and choose Html Page Layout from the drop down. Give it a great name like - CustomPageLayout - ok, you can probably do better than that. Make sure you select your new content type for this page. Another option is to use an existing html layout, duplicate and rename it (use Windows Explorer if you mapped the drive, SharePoint Designer or another program to do this), you will need to edit the properties in the browser to associate the new content type you created.

The folder structure should look like this now:

--custom-master-pages
    --css
        --bootstrap.min.css
        --styles.css
    --js
        --jquery-1.10.2.min.js
        --custom-request.js
        --script.js
    --CustomPageLayout.html

Now off to SharePoint Designer (or other preferred code editor if you have mapped master pages as a local drive). Open the new CustomPageLayout.html file in the editor.

There's a link to the Snippet Generator at the top of the page, you need this. Copy it and paste it into a browser. You should see a page like this:

snippet generator

Back to the html file... Look for this line in the code:

<!--MS:<asp:ContentPlaceHolder id="PlaceHolderAdditionalPageHead" runat="server">-->  

and right before this line:

<!--MS:<Publishing:EditModePanel runat="server" id="editmodestyles">-->  

connect your styles and scripts. Here's what the PlaceHolderAdditionalPageHead tag should look like now:

<!--MS:<asp:ContentPlaceHolder id="PlaceHolderAdditionalPageHead" runat="server">-->  
    <!--CS: Start Edit Mode Panel Snippet-->
    <!--SPM:<%@Register Tagprefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"%>-->
    <!--SPM:<%@Register Tagprefix="Publishing" Namespace="Microsoft.SharePoint.Publishing.WebControls" Assembly="Microsoft.SharePoint.Publishing, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"%>-->
    <link href="css/bootstrap-grid/bootstrap.min.css" rel="stylesheet" type="text/css" ms-design-css-conversion="no" />            
    <link href="css/styles.css" rel="stylesheet" type="text/css" ms-design-css-conversion="no" />
    <script type="text/javascript" src="js/twitter.js"></script>
    <script type="text/javascript" src="js/jquery-1.10.2.min.js"></script>
    <script type="text/javascript" src="js/jquery-ui.min.js"></script>
    <script type="text/javascript" src="js/custom-request.js"></script>
    <script type="text/javascript" src="js/script.js"></script>
    <!--MS:<Publishing:EditModePanel runat="server" id="editmodestyles">-->
        <!--MS:<SharePoint:CssRegistration name="&#60;% $SPUrl:~sitecollection/Style Library/~language/Themable/Core Styles/editmode15.css %&#62;" After="&#60;% $SPUrl:~sitecollection/Style Library/~language/Themable/Core Styles/pagelayouts15.css %&#62;" runat="server">-->
        <!--ME:</SharePoint:CssRegistration>-->
    <!--ME:</Publishing:EditModePanel>-->
    <!--CE: End Edit Mode Panel Snippet-->
<!--ME:</asp:ContentPlaceHolder>-->  

Fantastic! You have styles and scripts, sure there's nothing in some of those files, but you'll get there.

While you're here, you can change the title tag to whatever you want this layout name to be in the edit screen for your page.

Now make a place for the tiles and the selector to appear. Find this line in the html file:

<!--MS:<asp:ContentPlaceHolder ID="PlaceHolderMain" runat="server">-->  

directly beneath that, add a div that will be your container:

<div class="nav-tiles">

</div>  

You are going to put two different views inside this div. One that appears when going to the page and one that appears in edit mode. I like to start with the edit mode.

Go to the Snippet Generator that should be open in your browser. Select Edit Mode Panel from the Ribbon:

edit mode panel

In the customization section, make sure the PageDisplayMode is set to Edit. (Update the snippet if you make any changes.) Copy this snippet to your clipboard and paste it into the div you created. It should look like this:

<div class="nav-tiles">  
    <div data-name="EditModePanelShowInEdit">
        <!--CS: Start Edit Mode Panel Snippet-->
        <!--SPM:<%@Register Tagprefix="Publishing" Namespace="Microsoft.SharePoint.Publishing.WebControls" Assembly="Microsoft.SharePoint.Publishing, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"%>-->
        <!--MS:<Publishing:EditModePanel runat="server" CssClass="edit-mode-panel">-->
            <!--PS: Start of READ-ONLY PREVIEW (do not modify)--><!--PE: End of READ-ONLY PREVIEW-->
            <div class="DefaultContentBlock" style="border:medium black solid; background:yellow; color:black; margin:20px; padding:10px;">
            You should replace this div with content that renders based on your Edit Mode Panel Properties.

            </div>
            <!--PS: Start of READ-ONLY PREVIEW (do not modify)--><!--PE: End of READ-ONLY PREVIEW-->
        <!--ME:</Publishing:EditModePanel>-->
        <!--CE: End Edit Mode Panel Snippet-->
    </div>
</div>  

We need a space for the available tiles, selected tiles, a button to set the tiles and a spot for the JSON field from the page content type. Replace the DefaultContentBlock div with:

<div class="nav-tiles-edit">  
    <div class="row">
       <div class="col-sm-6">
            <h3>Available Tiles</h3>
            <div id="available-tiles" class="connected-sortable row">
            </div>
       </div>
       <div class="col-sm-6">
            <h3>Selected Tiles</h3>
            <div id="selected-tiles" class="connected-sortable row">
            </div>
            <div id="selection-submit"><h2>Set Tiles</h2></div>
       </div>
    </div>
    <div id="icon-json" class="row">
    </div>
</div>  

Before you finish the Edit Mode view, put a spot for the tiles to appear in display mode. On the Snippet Generator change the PageDisplayMode to Display and click Update. Copy to your clipboard and paste directly beneath the last snippet. Replace the DefaultContentBlock div with:

<div class="nav-tiles-display row">  
</div>  

Finally add the JSON page field (again I hope you used a better name than me). Go to the Snippet Generator again and select that page field.

page icon snippet

Copy the snippet and paste it into the icon-json div. That's the whole layout for this piece.

Here's the whole code for this section (NOTE: you shouldn't just copy and paste, you need to use the snippet generator to make sure there are unique GUIDs where needed):

<div class="nav-tiles">  
    <div data-name="EditModePanelShowInEdit">
        <!--CS: Start Edit Mode Panel Snippet-->
        <!--SPM:<%@Register Tagprefix="Publishing" Namespace="Microsoft.SharePoint.Publishing.WebControls" Assembly="Microsoft.SharePoint.Publishing, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"%>-->
        <!--MS:<Publishing:EditModePanel runat="server" CssClass="edit-mode-panel">-->
            <!--PS: Start of READ-ONLY PREVIEW (do not modify)--><!--PE: End of READ-ONLY PREVIEW-->
            <div class="nav-tiles-edit">
                <div class="row">
                    <div class="col-sm-6">
                        <h3>Available Tiles</h3>
                        <div id="available-tiles" class="connected-sortable row">
                        </div>
                    </div>
                    <div class="col-sm-6">
                        <h3>Selected Tiles</h3>
                        <div id="selected-tiles" class="connected-sortable row">
                        </div>
                        <div id="selection-submit"><h2>Set Tiles</h2></div>
                    </div>
                </div>
                <div id="icon-json" class="row">
                    <div data-name="Page Field: KEH Page Icons JSON Object"><!--CS: Start Page Field: KEH Page Icons JSON Object Snippet--><!--SPM:<%@Register Tagprefix="PageFieldNoteField" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"%>--><!--MS:<PageFieldNoteField:NoteField FieldName="5ff9c8f2-4402-48d5-bfe0-997d34312eb9" runat="server">--><!--PS: Start of READ-ONLY PREVIEW (do not modify)--><div align="left" class="ms-formfieldcontainer"><div class="ms-formfieldlabelcontainer" nowrap="nowrap"><span class="ms-formfieldlabel" nowrap="nowrap">KEH Page Icons JSON Object</span></div><div class="ms-formfieldvaluecontainer">KEH Page Icons JSON Object field value.</div></div><!--PE: End of READ-ONLY PREVIEW--><!--ME:</PageFieldNoteField:NoteField>--><!--CE: End Page Field: KEH Page Icons JSON Object Snippet-->
                    </div>
                </div>
            </div>
            <!--PS: Start of READ-ONLY PREVIEW (do not modify)--><!--PE: End of READ-ONLY PREVIEW-->
        <!--ME:</Publishing:EditModePanel>-->
        <!--CE: End Edit Mode Panel Snippet-->
    </div>
    <div data-name="EditModePanelShowInEdit">
        <!--CS: Start Edit Mode Panel Snippet-->
        <!--SPM:<%@Register Tagprefix="Publishing" Namespace="Microsoft.SharePoint.Publishing.WebControls" Assembly="Microsoft.SharePoint.Publishing, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"%>-->
        <!--MS:<Publishing:EditModePanel runat="server" PageDisplayMode="Display" CssClass="edit-mode-panel">-->
            <!--PS: Start of READ-ONLY PREVIEW (do not modify)--><!--PE: End of READ-ONLY PREVIEW-->
            <div class="nav-tiles-display row">            
            </div>
            <!--PS: Start of READ-ONLY PREVIEW (do not modify)--><!--PE: End of READ-ONLY PREVIEW-->
        <!--ME:</Publishing:EditModePanel>-->
        <!--CE: End Edit Mode Panel Snippet-->
    </div>
</div>  

CSS

Well this is great, but it needs a little style to get those cool squares. Time to fill in styles.css:

/****************************************************
 * Make sure Bootstrap doesn't mess up the microsoft 
 * styles that make SharePoint function 
 ***************************************************/
#ms-designer-ribbon,
#ms-designer-ribbon *,
[class*=ms-] {
    -webkit-box-sizing: content-box;
       -moz-box-sizing: content-box;
            box-sizing: content-box;
}

.nav-tiles-display{
    padding: 0 15px;
}

.keh-nav-tile:before {
    content: "";
    display: block;
    padding-top: 100%;
}

.keh-nav-block {
    position: absolute;
    top: 4%;
    left: 4%;
    bottom: 4%;
    right: 4%;
}

.keh-nav-content {
    position: relative;
    display: block;
    height: 100%;
    width: 100%;
    margin: auto;
}

.keh-nav-icon {
    position: relative;
    display: block;
    height: 45%;
    width: 45%;
    margin: 10px auto;
    padding-top: 10px;
}
.keh-nav-icon img {
    position: relative;
    display: block;
    max-height: 100%;
    margin: 0 auto;
}
.keh-nav-title h2 {
    margin: auto;
    padding: 5px;
    text-align: center;
    font-size: 1.2em;
    color: #fff;
}

/************** EDIT PANEL STYLES *****************/

#available-tiles, #selected-tiles {
    border: 1px solid #000;
    min-height: 20px;
    list-style-type: none;
    margin: 0;
    padding: 5px;
    margin-right: 10px;
}
.button-items {
    border: 3px solid #e5e5e5;
    padding: 5px;
}
.button-items submit {
    color: #000;
}
.button-items:hover {
    cursor: move;
}
#selection-submit {
    border: thin #538c3f solid;
    background-color: rgb(182,209,133);
    padding: 10px;
    margin: 10px auto;
    width: 40%;
}
#selection-submit:hover{
   cursor: pointer;
   background-color: rgb(255,255,255);
}
#selection-submit h2 {
   margin: 0 auto;
   text-align: center; 
}

Final Thoughts

Ok. You have all your files in place and a nice Layout to work with. My next post will cover the Javascript and putting this in play.

Part 2 - The Javascript