Tuesday, August 25, 2015

SharePoint Server 2016 Preview is released

After reading lot of blogs postings and Tweets regarding SharePoint 2016 new features/Roles/Hybrid with Office 365, Microsoft has launch the preview version of SharePoint 2016, the next big thing in the SharePoint World for the next months... The SharePoint RSS feed will burn thousands of words about this topic, let see if SharePoint survive with all this changes in IT.

Here some links about the SharePoint Server 2016 Preview and documentation associated.

Download SharePoint Server 2016 Preview
SharePoint Server 2016 IT Preview Quick Start Guide

SharePoint Server 2016 IT Preview

New and improved features in SharePoint Server 2016 IT Preview
What's deprecated or removed from SharePoint Server 2016 IT Preview
Known Issues in SharePoint Server 2016 IT Preview

Announcing availability of SharePoint Server 2016 IT Preview and cloud hybrid search

Enjoy and play around with SharePoint 2016 Preview ;)

Best Regards, 
André Lage

Friday, July 10, 2015

Header/Footer with Breadcrumb and Global Ribbon SPO Office 365 Dev

After long time and work associated. I have some time for a new article about frequent question regarding Header/Footer and Breadcrumb in SharePoint on-premise  and Office 365 and my feedback about a specific request from Office 365 Dev team https://github.com/OfficeDev where i contributed with the following described sample.

The Yammer Group associated to Office 365 dev has a section associated call Office 365 Dev Patterns and Practices (PnP), the PnP team requested support for frequent question regarding Breadcrumb and i create an example to support the following topics:
  • Create an Header and Footer in SharePoint Online
  • Create an full Breadcrumb in SharePoint Online (Full site Path and Folder Path)
  • Create a Global Ribbon and Global Custom Menu in SharePoint Online
To achieve this sample used a simple SharePoint-hosted Add-in to manage the content provision into SharePoint site and have the following result as shown in image bellow:



This solution can be accessible in the PnP repository, there are fantastic samples, scenarios and hybrid approach guidance about Office 365 DEV.
Core.EmbedJavaScript.HeaderFooter
https://github.com/OfficeDev/PnP/tree/dev/Samples/Core.EmbedJavaScript.HeaderFooter

Provisioning Process

This process implements the sample support SharePoint Object and file to implement the features associated with the following sequence.


  • PropertyBag PnPGlobalBreadcrumbRibbon is created in RootWeb of the SiteCollection, this item will have the JSON data from the JSON Editor Area, if the JSON is not correct format then assumes the default JSON Data.
  • A file from the AppWeb PnPGlobal.js is copied to folder "_catalogs/masterpage/Display Template", the JS file includes the code for the creation of the Global Breadcrumb (SharePoint or JSON Data) & Ribbon.
  • ScriptLink is created in SiteCollection with Name PnPGlobalBreadcrumbRibbon and linked to JS filePnPGlobal.js.

For this Sample was created a SharePoint-Hosted Add-in where is displayed the current status of the provisioning process.
This process is important, i see a lot of developers and not developers making deployments without any type of documentation associated, that is a huge risk when a new version or changes in the SharePoint Online and SharePoint 2013 changes. 
Please always document all included files in the SharePoint Site, what SharePoint Objects were changed, good description and comments on your Pages/JS/CSS files and description about the solution.
This will make life more easy for the next person that needs to make changes and maintain the implemented solution.


The code is separated in 2 Areas(Provisioning Area/Global JS file), the code to support the provisioning and Status report and the JS file to support the implementation of the following Features in the SharePoint site:
  • Header and Footer
  • Custom Breadcrumb (SharePoint, JSON)
  • Custom Ribbon
Architecture of the Sample

The following image show how the SharePoint Objects interact with the SharePoint Site and where the support File is loaded to be accessible using the UserCustomAction in the Scope "Site".



MDS Management

The sample manage the current state of MDS and use javascript eventListener/_spBodyOnLoadFunctionNames to manage the Page Javascript Lifecycle to reponse when the page is loaded or ajax calls are made in he MDS method.

Here are the Methods used and their description
  • _spBodyOnLoadFunctionNames.push
This (old) SharePoint method call the necessary function when the html tab <Body> is loaded
  • window.addEventListener("readystatechange"
This method wait's until  XMLHttpRequest requests is finish and call the necessary function.
  • window.addEventListener("DOMContentLoaded"
This method wait's until all DOM Objects are loaded in the page and call the necessary function.






This are the main methods to manage the load of scripts where the custom Header/Footer is loaded and then we can start to add our custom SharePoint Breadcrumb and custom Ribbon.

Code Functions to load content

Load of SharePoint Web Breadcrumb Code

The code sample loads the Web Objects from the current user location to the Root Web in a recursive way using JSOM and print .


LoadSiteBreadcrumb: function () {
        SP.SOD.executeOrDelayUntilScriptLoaded(function () {
            var clientcontext = SP.ClientContext.get_current();
            var site = clientcontext.get_site();
            var currentWeb = clientcontext.get_web();
            clientcontext.load(currentWeb, 'ServerRelativeUrl', 'Title', 'ParentWeb', 'Url');
            clientcontext.load(site, 'ServerRelativeUrl');
            clientcontext.executeQueryAsync(
            function () {

                var element = document.createElement('ol');
                element.id = "breadcrumbSite";
                element.className = "breadcrumb";
                var Footerelement = document.createElement('ol');
                Footerelement.id = "footerbreadcrumbSite";
                Footerelement.className = "breadcrumb";
                var Custombreadcrumb = document.getElementById("s4-bodyContainer");
                Custombreadcrumb.insertBefore(element, Custombreadcrumb.childNodes[0]);
                var CustomFooterbreadcrumb = document.getElementById("PnPFooter");
                CustomFooterbreadcrumb.insertBefore(Footerelement, CustomFooterbreadcrumb.childNodes[0]);
                var li = document.createElement('li');
                li.innerHTML = '<a href="' + currentWeb.get_url() + '">' + currentWeb.get_title() + '</a>';
                Custombreadcrumb = document.getElementById("breadcrumbSite");
                Custombreadcrumb.insertBefore(li.cloneNode(true), Custombreadcrumb.childNodes[0]);
                CustomFooterbreadcrumb = document.getElementById("footerbreadcrumbSite");
                CustomFooterbreadcrumb.insertBefore(li.cloneNode(true), CustomFooterbreadcrumb.childNodes[0]);
                if (site.get_serverRelativeUrl() !== currentWeb.get_serverRelativeUrl()) {
                    PnPGlobal.RecursiveWeb(currentWeb.get_parentWeb().get_serverRelativeUrl())
                }
            }, fail);
        }, "sp.js");
    },
    RecursiveWeb: function (siteUrl) {
        var clientcontext = new SP.ClientContext(siteUrl);
        var site = clientcontext.get_site();
        var currentWeb = clientcontext.get_web();
        clientcontext.load(currentWeb, 'ServerRelativeUrl', 'Title', 'ParentWeb', 'Url');
        clientcontext.load(site, 'ServerRelativeUrl');
        clientcontext.executeQueryAsync(
    function () {
        if (site.get_serverRelativeUrl() !== currentWeb.get_serverRelativeUrl()) {
            var li = document.createElement('li');
            li.innerHTML = '<a href="' + currentWeb.get_url() + '">' + currentWeb.get_title() + '</a>';
            var Custombreadcrumb = document.getElementById("breadcrumbSite");
            var CustomFooterbreadcrumb = document.getElementById("footerbreadcrumbSite");
            Custombreadcrumb.insertBefore(li.cloneNode(true), Custombreadcrumb.childNodes[0]);
            CustomFooterbreadcrumb.insertBefore(li.cloneNode(true), CustomFooterbreadcrumb.childNodes[0]);
            PnPGlobal.RecursiveWeb(currentWeb.get_parentWeb().get_serverRelativeUrl())
        } else {
            var li = document.createElement('li');
            li.innerHTML = '<a href="' + currentWeb.get_url() + '">' + currentWeb.get_title() + '</a>';
            var Custombreadcrumb = document.getElementById("breadcrumbSite");
            var CustomFooterbreadcrumb = document.getElementById("footerbreadcrumbSite");
            Custombreadcrumb.insertBefore(li.cloneNode(true), Custombreadcrumb.childNodes[0]);
            CustomFooterbreadcrumb.insertBefore(li.cloneNode(true), CustomFooterbreadcrumb.childNodes[0]);
        }

    }, fail);

    }



Load of SharePoint Path Folder Breadcrumb Code

The code validates current document url and try to get current folder path or parameter associated when it's Library/List or Parameter RootFolder, when you navigate between folder this parameter is essential to get the correct Path for your breadcrumb.

//Main Method to validate current Folder Path from Library/List
var path = getQueryStringParameter("RootFolder", fullurl).replace(decodeURIComponent(currentWeb.get_serverRelativeUrl()), "");


//Support Methods
function getQueryStringParameter(param, serverRelativeUrl) {
    if (document.URL.indexOf("_layouts/15/start.aspx#") > -1) { return getQueryStringParameterMDS(param, serverRelativeUrl) }
    else if (document.URL.split("?").length > 1) {
        var params = document.URL.split("?")[1].split("&");
        for (var i = 0; i < params.length; i = i + 1) {
            var singleParam = params[i].split("=");
            if (singleParam[0] == param)
                return "/" + decodeURIComponent(singleParam[1]);
        }
        return "/" + _spPageContextInfo.serverRequestPath;
    }
}
function getQueryStringParameterMDS(param, serverRelativeUrl) {
    if (document.URL.split("#").length > 1) {
        if (document.URL.split("?").length > 1) {
            var params = document.URL.split("?")[1].split("&");
            for (var i = 0; i < params.length; i = i + 1) {
                var singleParam = params[i].split("=");
                if (singleParam[0] == param) {
                    return decodeURIComponent(singleParam[1]);
                } else if (i < params.length && singleParam[0] !== param) {
                    return decodeURIComponent(document.URL.split("#")[1]);
                }
            }
        } else {
            return serverRelativeUrl + decodeURIComponent(document.URL.split("#")[1]);
        }
    } else {
        return "";
    }

}





Load of JSON PropertyBag Menu Code

The code sample loads the Root Web PropertyBag "vti_GlobalBreadcrumRibbon" where is located the JSON data and load in the custom Header as Menu using JSOM.

CreateBreadcrumb: function () {
        SP.SOD.executeOrDelayUntilScriptLoaded(function () {
            var element = document.createElement('div');
            var context = new SP.ClientContext.get_current();
            var site = context.get_site();
            var web = site.get_rootWeb().get_allProperties();
            context.load(web)
            context.executeQueryAsync(function () {
                var results = JSON.parse(web.get_item('vti_GlobalBreadcrumbRibbon'));
                var breadcrumb = '<ol class="breadcrumb">';

                for (var i = 0; i < results.Breadcrumb.length; i++) {
                    breadcrumb = breadcrumb + '<li><a href="' + results.Breadcrumb[i].url + '">' + results.Breadcrumb[i].title + '</a></li>';
                }
                breadcrumb = breadcrumb + '</ol>';
                element.innerHTML = breadcrumb;
                var Custombreadcrumb = document.getElementById("s4-bodyContainer");
                Custombreadcrumb.insertBefore(element, Custombreadcrumb.childNodes[0]);

            }, function () { });

        }, "sp.js");
    },


Load of Global Ribbon 

The code sample loads Custom Ribbons using SP.ribbon.js methods when the SharePoint Page and associated JS script is loaded.

var Ribbonhtml = document.createElement('div');
    Ribbonhtml.setAttribute("id", "CustomRibbon");
    Ribbonhtml.innerHTML = "<div><a href='#' onclick=\"alert('Custom Ribbon')\" ><img src='../_layouts/images/NoteBoard_32x32.png' /></a><br/>Ribbon Example</div><div><a href='#' onclick=\"LoadApps()\" ><img src='../_layouts/images/NoteBoard_32x32.png' /></a><br/>SP Add-in\'s</div>";
    var ribbon = SP.Ribbon.PageManager.get_instance().get_ribbon();
    if (ribbon) {
        var tab = new CUI.Tab(ribbon, 'GlobalRibbon.Tab', 'Option', 'Option', 'GlobalRibbon.Tab.Command', false, '', null);
        ribbon.addChildAtIndex(tab, 1);
        var group = new CUI.Group(ribbon, 'GlobalRibbon.Tab.Group', 'Custom Ribbon', 'Global Ribbon Example', 'GlobalRibbon.Group.Command', null);
        tab.addChild(group);
    }
    SelectRibbonTab('GlobalRibbon.Tab', false);
    document.getElementById("GlobalRibbon.Tab.Group").childNodes[0].childNodes[0].appendChild(Ribbonhtml);
    SelectRibbonTab('Ribbon.Read', true);



After this code is implemented and together you will have the final output in the sample.


A very simple way to implement an Header/Footer, custom Breadcrumb and global Ribbon all together. 

Support Links:
Core.EmbedJavaScript.HeaderFooter
https://github.com/OfficeDev/PnP/tree/dev/Samples/Core.EmbedJavaScript.HeaderFooter
Create sticky footer in all SharePoint Site using ScriptLink without changing MasterPage
http://aaclage.blogspot.ch/2014/04/create-sticky-footer-in-all-sharepoint.html
How to Create Global Custom Ribbon using ScriptLink without MDS issues
http://aaclage.blogspot.ch/2014/05/how-to-create-global-custom-ribbon.html
Best regards, André Lage