Tiles web part

  • Epic application analysts wanted to make training and knowledge base documents look good for anesthesiologists, so I built this easy to maintain web part that grouped document links in colored category boxes and resized well to fit on any display device.

    Out-of-the-box (OOTB) web parts on their SharePoint site made their content look no better than a directory of files in folder. And none of the analysts were SharePoint experts, nor did they want to be. They just wanted to post documents and get back to their day job. In other words, they wanted a zero-maintenance way to group documents in blocks on a page.

  • Tiles web part

Group and block

  • The web part had to respond gracefully on any desktop display size and work in all major browsers, including Internet Explorer 8. It also had to make grouping, blocking, and color assignments easy to do in the document library.

    More than one enterprise team had been looking for something like this, so the scope volunteered to formulate an approach could be replicated on multiple sites.

  • The first step created a "Documents-Lookup" list on the site with three columns: one plain text column for a string that described groups and subgroups (with content in parentheses for ordering), one column for defining the background color of a subgroup block, and one column for defining the color of the subgroup block's border color.

  • Documents-Lookup list

    Documents-Lookup list with color columns in hex.

  • The next step added these columns to the site's "Documents" library as lookup columns. Changes to the group/subgroup string in the Documents-Lookup list, therefore, would automatically cascade-update any existing selections in the Documents library. This allowed analysts to quickly categorize documents any way they wanted.

    Activating a "Link to Document" content type in the library then allowed the analysts to avoid duplication of work by simply linking to documents already residing elsewhere in the SharePoint system.

    These were the basic list/library setup steps that any SharePoint site owner could learn without special training.

  • Documents library

    Documents library with group lookup column.

  • The last step build an HTM file which contained all the web part's CSS, HTML, and JavaScript code. The code for this relied on an older jQuery core library and a SharePoint Web Services jQuery library.

    The HTM file resided in another document library on the site called "scripts," and a Content Editor Web Part on the home page then called that file to display documents.

  • The original web part organized subgroups horizontally across the page under group headings in a responsive design that resized browser window. But the vertical layout did a better job of handling blocks with a wide variation in content height.

Results

  • Manipulating and reordering the documents on the page was simple. Text in the description of each color field pointed to an online hex color picker made the tile colors easy to configure. The analysts caught on quickly.

  • Anesthesia documents web part

    The final documents web part in combination with a tabs web part.

Under the hood

  • The SharePoint 2010 production platform cached all CSS and JS files, so they ended up inside the HTML file. This made development fast, and non-developers could understand the code enough to tweak it from a simple text editor like Notepad.

  • SharePoint
    • 1 code-behind pages
    • 1 script library
    • 1 documents library
  • Protocols
    • JavaScript
    • jQuery
    • jQuery Library for SharePoint Services
    • HTML
    • CSS
  • Design
    • Analysis, composition and usability
  • Tools
    • Notepad++

Code snip

<!DOCTYPE HTML>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
        <title>Workflows v1.0</title>

        <style type="text/css">
            #workflows {
                margin: 0;
                padding: 0;             
            }
            #workflows ul {
                margin: 0;
                padding: 0;
            }
            #workflows table {
                border-collapse: collapse;
                width: 100%;
            }
            #workflows td {
                vertical-align: top;
            }
            #workflows li {
                list-style: none;
            }
            #workflows h2 {
                color: #184080 !important;
                font-size: 12pt;
                font-weight: bold !important;
                padding: 5px 0 0 0;
                text-align: center;
            }
            #workflows h4 {
                color: #ff6600;
                color: #ff6600;
                font-size: 12pt;
                font-weight: normal !important;
                margin: 0;
                padding: 5px 0;
                text-align: center;             
            }
            #workflows a {
                font-size: 10pt;
            }
            #workflows .group {
                display: inline-block;
                margin: 0;
                vertical-align: top;
            }           
            #workflows .group-outer {
                vertical-align: top;
                margin: 10px 5px;
            }
            #workflows .group-inner {
                background: #e6e6e6;
                border-color: #e6e6e6;
                border-radius: 10px;
                border-style: solid;
                border-width: 1px;
                margin: 0 5px;
                padding: 0 0 10px 0;
            }
            #workflows .group-item {
                line-height: 1;
                padding: 10px 10px 0 10px;
            }
        </style>

        <script src="/anesth/scripts/webparts/workflows/js/jquery-1.7.2.min.js" type="text/javascript"></script>
        <script src="/anesth/scripts/webparts/workflows/js/jquery.SPServices-0.7.2.min.js" type="text/javascript"></script>
        <script type="text/javascript">
            (function ($) {
                $(document).ready(
                        function ()
                        {
                            var ar_docs = [], ar_grp = [], ar_sgrp = [];
                            var initalheightbugfix = 0;


                            $('.ms-vh-div').parent('th').hide();
                            $('#workflows').parent('div').css('margin', '0');
                            $('.welcome-content').next('table').find('td').first().css('width', '70%');

                            getDocs();

                            function getDocs()
                            {
                                // Get list content and create HTML
                                $().SPServices({
                                    operation: 'GetListItems',
                                    webURL: 'anesth',
                                    listName: 'Documents',
                                    async: false,
                                    CAMLQuery: '<Query></Query>',
                                    CAMLViewFields: '<ViewFields>' +
                                            '<FieldRef Name="ID" />' +
                                            '<FieldRef Name="Group" />' +
                                            '<FieldRef Name="Group_x003a_BackColorNumber" />' +
                                            '<FieldRef Name="Group_x003a_BorderColorNumber" />' +
                                            '<FieldRef Name="Name" />' +
                                            '<FieldRef Name="URL" />' +
                                            '</ViewFields>',
                                    CAMLRowLimit: 0,
                                    completefunc: function (xData, Status)
                                    {
                                        var k = 0;
                                        var k2 = 0;
                                        var u1 = [];
                                        var u2 = [];
                                        $(xData.responseXML).SPFilterNode('z:row').each(function (k)
                                        {
                                            var id = $(this).attr("ows_ID"),
                                                    bkgclr = !$(this).attr("ows_Group_x003a_BackColorNumber") ? '' : $(this).attr("ows_Group_x003a_BackColorNumber").split(';')[1],
                                                    bdrclr = !$(this).attr("ows_Group_x003a_BorderColorNumber") ? '' : $(this).attr("ows_Group_x003a_BorderColorNumber").split(';')[1],
                                                    file = !$(this).attr("ows_FileLeafRef") ? '' : $(this).attr("ows_FileLeafRef").split(';#')[1],
                                                    group = !$(this).attr("ows_Group") ? '' : $(this).attr("ows_Group").split(';#')[1].split(':')[0],
                                                    subgroup = !$(this).attr("ows_Group") ? '' : $(this).attr("ows_Group").split(';#')[1].split(': ')[1],
                                                    name = !$(this).attr("ows_FileLeafRef") ? '' : $(this).attr("ows_FileLeafRef").split(';#')[1].replace(/^Anesthesia - /, '').split('.')[0],
                                                    url = !$(this).attr("ows_URL") ? '' : $(this).attr("ows_URL").split(',')[1];

                                            // build array of documents
                                            ar_docs[k] = {
                                                id: id,
                                                bkgclr: bkgclr,
                                                bdrclr: bdrclr,
                                                file: file,
                                                group: group,
                                                name: name,
                                                subgroup: subgroup,
                                                url: url
                                            };

                                            // build array of unique groups
                                            if ($.inArray(group, u1) === -1 && !!group)
                                            {
                                                ar_grp.push(group);
                                                u1.push(group);
                                            }

                                            // build array of unique subgroups
                                            if ($.inArray(subgroup, u2) === -1 && !!subgroup)
                                            {
                                                ar_sgrp[k2] = {group: group, subgroup: subgroup, bkgclr: bkgclr, bdrclr: bdrclr};
                                                k2++;
                                                u2.push(subgroup);
                                            }
                                        });
                                    }
                                });
                            }

                            // sort arrays
                            ar_grp.sort();
                            ar_sgrp.sort(function (m1, p1) {
                                var m = ('' + m1.subgroup).toLowerCase(),
                                        p = ('' + p1.subgroup).toLowerCase();
                                if (m > p)
                                    return 1;
                                if (m < p)
                                    return -1;
                                return 0;
                            });

                            // store HTML in variable
                            var h = '';

                            // build HTML
                            $.each(ar_grp, function (i, v)
                            {
                                // remove order number from group text
                                var g = v.split(')')[1].replace(/^\s*/, '');

                                // wrap groups in UL container
                                h += '<div class="group">';
                                // add group title
                                h += '<h2 class="ms-standardheader">' + g + '</h2>';

                                for (var i = 0, len = ar_sgrp.length; i < len; i++)
                                {
                                    if (ar_sgrp[i].group === v)
                                    {
                                        // add subgroup item in block to display in line
                                        h += '<div class="group-outer">';

                                        // contain content in nice DIV with borders
                                        if (ar_sgrp[i].bkgclr === '')
                                        {
                                            var color = 'class="group-inner"';
                                        } else
                                        {
                                            var color = 'class="group-inner" style="background-color:' + ar_sgrp[i].bkgclr + '; border-color:' + ar_sgrp[i].bdrclr + '"';
                                        }

                                        h += '<div ' + color + '>';

                                        // remove order number from subgroup text
                                        var sg = ar_sgrp[i].subgroup.split(')')[1].replace(/^\s*/, '');

                                        h += '<ul>';
                                        // add subgroup title 
                                        h += '<li><h4>' + sg + '</h4></li>';

                                        // add list of links
                                        h += filterOnSubgroup(ar_sgrp[i].subgroup);
                                        h += '</ul>';
                                        h += '</div>';
                                        h += '</div>';
                                    }
                                }
                                h += '</div>';
                            });

                            // append HTML to page
                            $('#workflows').append(h);

                            // even the width of boxes in each workflow group
                            setEqualWidth($(".group"));
                            $(window).resize(
                                    function ()
                                    {
                                        setEqualWidth($(".group"));
                                    }
                            );

                            // set objects to equal height
                            function setEqualWidth(w)
                            {
                                // for each item in object
                                w.each(
                                        function ()
                                        {
                                            // set this item's width in percent (with fluff)
                                            $(this).css('width', (100 / w.length) + '%');
                                        }
                                );
                            }

                            // filter documents on subgroup
                            function filterOnSubgroup(itm)
                            {
                                // start with empty html 
                                var g = "";
                                var r = [];

                                // filter the documents array for a list of items matching subgroup value
                                for (z = 0, len = ar_docs.length; z < len; z++)
                                {
                                    if (ar_docs[z].subgroup.indexOf(itm) > -1)
                                    {
                                        r.push(ar_docs[z]);
                                    }
                                }

                                r.sort(function (m1, p1)
                                {
                                    var m = ('' + m1.name).toLowerCase(),
                                            p = ('' + p1.name).toLowerCase();
                                    if (m > p)
                                        return 1;
                                    if (m < p)
                                        return -1;
                                    return 0;
                                });

                                // transform the result into a string
                                for (var i = 0, len = r.length; i < len; i++)
                                {
                                    if (r[i].url === '')
                                    {
                                        var link = '/anesth/Docs/' + r[i].file;
                                        g += '<li class="group-item"><a class="ms-vb" href="' + link + '" target="_blank">' + r[i].name + '</a></li>';
                                    } else
                                    {
                                        var link = r[i].url;
                                        g += '<li class="group-item"><a class="ms-vb" href="' + link + '" target="_blank">' + r[i].name + '</a></li>';
                                    }
                                }
                                return g;
                            }
                        }
                );
            }(jQuery));

        </script>
    </head>
    <body>
        <div id="workflows"></div>
    </body>
</html>