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

Monday, May 11, 2015

Manage your customizations with custom JS/CSS without changes

From time to time i see a lot of discussions about the Masterpages, Branding, Styles, Customizations, UI/UX in SharePoint/Office 365...
This article uses examples in my custom environment where i can explain how you can manage branding/styles and Customization changes in my SharePoint/Office 365 Sites and keep the environment regarding Out of the Box "Masterpage/Style files" being untouched, manageable and clean to constant the SharrePoint/Office365 upgrades.

This was done in Test environment, if you need to make change in production please reflect in the impact.

Important and recommend to read before you do something in Branding:

Every time you need to change the branding, think in all the impact and costs you could have and this means $$$ costs, this is the reason people talk about Customization Tax, because if you make changes this could have impact in newer version of your SharePoint/Office365 environment and that means COST.

There is a video from the ignite from Vesa Juvonen about "Deep Dive into Safe SharePoint Branding in Office 365 Using Repeatable Patterns and Practices" and shared in Channel9 explain all the impact and possible cost of changing the Branding and explains what is supported and not with a lot of recommendations.

Here a nice article talking about Branding from Chris O'Brien,

Video from ignite about Branding:

Deep Dive into Safe SharePoint Branding in Office 365 Using Repeatable Patterns and Practices

https://channel9.msdn.com/Events/Ignite/2015/BRK3164

PS: 
A lot of Designers/Dev/PowerUsers already the work with this approach in other ways, for them is not new stuff, but want to show different approaches for the same issues and explain how i work with this type of topic in Office 365 and SharePoint related.  
There is also a SharePoint Property call AlternateCssUrl that could do this trick, 

There is the 5 questions rule you should make your self every time you need to make something (Who, What, Where, When, Why).

It's always better to use Out of the box options and not reinvent the wheel.

Here is the output example of the changes made without changing Out of the Box files.

My Architecture definition for the Branding/Customization

Here is the Architectural View of the approach about Managing Branding/ Styles/ Customizations and functionalities in SharePoint/Office 365 using as base this SharePoint Provider App.


For this example used the 
Processlynx Custom Action and Ribbon Manager (here a Demo of the app in Office Store)

Process for implementation

DEV/Designer
  • This users are responsible to create the Customizations, Styles and functionalities in JS/CSS/images and other support files and include them in a Folder
  • If they have SharePoint Knowledge they should be able to configure the Out of box User Custom Actions provided by that SharePoint Object using UI SharePoint App, 

Site Administrator/PowerUser

  • This users are responsible to Manage the correct reference to Files
  • If they have the SharePoint Knowledge should be able to configure the Out of box User Custom Actions provided by that SharePoint Object using a SharePoint App,
This approach add new UserCustomActions in your SharePoint Webs Object where are located all the references to JS files, this files will manage the new designs styles/functionalities and can be propagated to all site or even site Collection.
Every time a new change is needed the Power User or site Administrator can remove or update the JS Files without the need of changes of OoB files and everything should work because in Background everything is Out of box and can assume the Office 365 Microsoft changes without errors.



Example:
This example explains how to use a JavaScript file to load dynamically a CSS in the header of the SharePoint Site to make the changes in the Styles. 

Creation of AddCSS.JS 

This JavaScript file add dynamically the CSS file "CustomCSS.css" to all headers of the SharePoint site by JQuery.

$(document).ready(function () {
    $("head").append("<link rel='stylesheet' href='" + _spPageContextInfo.webAbsoluteUrl + "/SiteAssets/CustomCSS.css' type='text/css' media='screen'>");
});


Creation of Custom.CSS

/* change the background of Ribbon Area */
.ms-cui-tabBody
{
background-color:#F2F2F2;
}



/* change the background of Left Menu */
.ms-core-listMenu-verticalBox
{
background-color: antiqueWhite !important;
}

/* change the size of contentPlaceholder*/
#s4-workspace{
width:60% !important;
position:absolute;
left: 20%;
}



/* change the background of SharePoint Site */
.ms-backgroundImage{
  background-color:#8CBF26; 
}


/* change the position of OoB SharePoint Search*/
#DeltaPlaceHolderSearchArea{
position:absolute;
left:50%;
top:10%;
}



Here the final CSS:
.ms-cui-tabBody
{
background-color:#F2F2F2;
}
.ms-core-listMenu-verticalBox
{
background-color: antiqueWhite !important;
}
#s4-workspace{
width:60% !important;
position:absolute;
left: 20%;
}
.ms-backgroundImage{
  background-color:#8CBF26; 
}
#DeltaPlaceHolderSearchArea{
position:absolute;
left:50%;
top:10%;
}
Include the files in SharePoint Library
Access to your Office 365 SharePoint and Site Assets library and add the following Files "AddCSS.JS" and "CustomCSS.css"



Add UserCustomAction to SharePoint Site
Using the Processlynx Custom Action and Ribbon Manager you can include your custom User Custom Action to refer your JS files.
Jquery.JS is mandatory to be 1, the second reference should be "AddCSS.JS" because uses JQuery code.


After the JavaScript is included in the SharePoint Object UserCustomAction by the SharePoint App, access to your Site and the changes in the MasterPage and Look and feel will be shown.


If you like to use Visual Studio and Declarative code you can use the option "Generate Declarative XML" to create the XML for your SharePoint Feature as shown.
You can also use JSOM or CSOM to make the same actions.

<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
 <CustomAction Id="Jquery" Title="Jquery" Description="" Location="ScriptLink"ScriptSrc="~Site/SiteAssets/Jquery.JS" Sequence="1" > </CustomAction>
</Elements>

<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
 <CustomAction Id="AddCSS" Title="AddCSS" Description="" Location="ScriptLink"ScriptSrc="~Site/SiteAssets/AddCSS.JS" Sequence="2" > </CustomAction>
</Elements>

To better understand the full power of this approach "
UserCustomAction" about customization's a design and functionality without breaking policies and keep it easily manage by Users for future upgrades:


Hope you like this article, 
André Lage