Friday, May 23, 2014

How to Create Global Custom Ribbon using ScriptLink without MDS issues

This article explains how you can create a Custom Ribbon to be accessed in all Out of the Box Ribbon Location using ScripLlink Custom Action  and don’t change any Page/Masterpage/File out of the box, no server code, no Declarative Code, No Visual Studio Solution and will manage the Minimal Download Strategy (MDS)  to include the Ribbon file to the SharePoint Site, everything will be done by a JavaScript File, simple like that.

PS: i am aware of the MakeTabAvailable but this method is for Server side, this demo is for Client Side "JavaScript" to include Office 365 environments.

PS: there is a new version about this topic in the following link
http://aaclage.blogspot.ch/2015/07/headerfooter-with-breadcrumb-and-global.html

The challenge here wasn't to create the Ribbon itself, there is a lot of code on how to create Ribbon using SharePoint JSOM, 
Here is a article examplethe challenge was on how to do it with MDS (Minimal Download Strategy).
The problem when you have the MDS approach, some of our assumptions has to be change:
  • There isn't a real redirection of page, 
    • Then there isn't a postback associated
  • The scripts are loaded in the first page other pages with MDS will have this files preloaded from "_layouts/15/start.aspx#/" this makes the page load faster, 
    • "SP Context will be hard to get".
  • When the users change pages, the URL query parameter is changed but not the main page  "/_layouts/15/start.aspx#/Shared%20Documents/Forms/AllItems.aspx"
    • Ajax calls to change specific zones and becomes hard to understand the changes in the page and associate the correct Context.

Here some nice articles talking about this Topic "MDS" and how you should work with your Javascript and include the "RegisterModuleInit" Method in specific areas of SharePoint to work correctly with MDS, but this is not what i want....
What i want is to make a simple reference to a JavaScript File and it's done. Don't need to change WebParts, no CSR  override Files, add file to MasterPage, that is to complicated for some people and to many steps, i like the 2 way approach.
  • Developer makes the code and add JavaScript File in Document Library
  • Site Administrator add Link to JavaScript using Scriptlink with one click in the SharePoint and done, it's shared in all pages of the SharePoint Site, everything in a very easy way.
    • The process that some Developers make to deploy their solutions can be very complicated with multiple clicks and confusing descriptions .Some Site administrators are just Business Persons, they don't need to be SharePoint functionality experts.....
Here are some articles talking about MDS i can recommend you to read:


Here was the challenge to create a Custom Ribbon to be accessed in all Ribbons Locations:
  • Need to include Custom JavaScript to be loaded in all SharePoint pages and include Custom Ribbon if there is a Ribbon Location in the Page.
  • Minimal Download Strategy don't allow postback "everything is the same page and is managed by url parameters" with this approach we lose control of context, then needs to include in the JavaScript a specific Listener onhashchange to get the events when the url is changed and include the JavaScript for the Custom Ribbon.
The image below shows the final result of this solution: 



The following Code inject 2 Main Methods:

  • LoadMDSGlobalRibbon()
    • Include Javascript code in the SharePoint and Page to add a Listener when the MDS is activated and his url is changed then the Custom Ribbon is injected everytime the DOM Object Are Loaded
  •  LoadGlobalRibbon()
    • This script is execute when the Page is loaded ensures the SP.Ribbon.JS file is added to include the Custom Ribbon in the Page. 

Create the JavaScript file named "GlobalRibbon.js" with the following code: 

//Load the Global Ribbon Method without MDS
LoadGlobalRibbon();

//This injected code that includes a listener for url parameters, when the MDS is Activated.
$('body').append('<script>window.onhashchange = locationHashChanged;function locationHashChanged(){LoadMDSGlobalRibbon();}</script>');

//Load all DOM OBJECTS and Load Global Ribbon in the Page
function LoadMDSGlobalRibbon() {
    $(window).load(function () {
        LoadRibbon();
    });
}
//Load Content of Page and Load Global Ribbon in the Page
function LoadGlobalRibbon() {
    $(document).ready(function () {
        LoadRibbon();
    });
}
//Validate Ribbon Context in the Page
function LoadRibbon() {
    SP.SOD.executeOrDelayUntilScriptLoaded(function () {
        try {
            var pm = SP.Ribbon.PageManager.get_instance();
            pm.add_ribbonInited(function () {
                ribbonTab();
            });
            var ribbon = null;
            try {
                ribbon = pm.get_ribbon();
            }
            catch (e) { }
            if (!ribbon) {
                if (typeof (_ribbonStartInit) == "function")
                    _ribbonStartInit(_ribbon.initialTabId, falsenull);
            }
            else {
                ribbonTab();
            }
        } catch (e)
        { }
    }, "sp.ribbon.js");
}
//include the Ribbon in the Page
function ribbonTab() {
    var Ribbonhtml = "";
    Ribbonhtml += "<div><div><a href='#' onclick=\"alert('Option Selected!')\" ><img src='/_layouts/images/NoteBoard_32x32.png' /></a><br/>Ribbon Example</div></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'true);
    $("span:contains('Custom Ribbon')").prev("span").html(Ribbonhtml);
    SelectRibbonTab('Ribbon.Read'true);

}

Add the created file in the SharePoint Library "Site Assets"



Include the Global Script to your SharePoint Site, for this task i used my SharePoint App "Manage User Custom Action" to include using ECMAscrpt JSOM the ScriptLink in the Site.



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.

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

After the Reference is made a new Option Appears in your Ribbon, you can access to your SharePoint page and you will see the MDS is activated and the Option Ribbon is loaded.


When you select another page the URL Parameters are changed and the Custom Ribbon appears in the Out of the Box Ribbons Locations



To validate if everything was working correctly, deactivate the the MDS Feature and access to a SharePoint Page.


After access the SharePoint page you will be able to see the Url change, it's not calling "_layouts/15/start.aspx#/" but the correct Page "/sitesPages/DevHome.aspx" and the Ribbon Load correctly.



As you can see it's very easy to add a Global Ribbon using a Scriptlink and avoid possible issues with MDS, this was not tested fully but a PoC on how we can have nice workarounds without creating SharePoint Solutions WSP or extreme XML Customization .

Here the Video on how to configure this Global Ribbon in Office365



Hope you like this article, 
Kind regards, 
Andre Lage

Wednesday, May 21, 2014

My List of Tools for SharePoint Apps - SharePoint Hosted

This last month  i have been working with SharePoint App - SharePoint Hosted for collaborative Sites and want to give feedback from some of the tools that help me create the some Apps base in JSOM.

Here is a small List of Extensions  and solutions you can use when you are creating SharePoint apps as SharePoint Hosted:

Web Essentials

This Visual Studio extension it's something that every Web Developer should have, will help you to optimize you Web Files such us CSS/JS and Images, this options will reduce the size of images and files and will be faster to Load the script in the Web Site.
  • Minify Javascript and CSS Files
  • Optimize Images



SPFastDeploy

For your Mental sanity i recommend to use this extension, this extension copies the File from Visual Studio Solution to the SharePoint App site with a simple Click and will avoid all the process of uninstall and install App with Debugger of JavaScript and time consumption.
I never use Visual Studio debugger to catch JavaScript errors to don't have to call a exorcist when the F5 is Hit, i can recommend to use Internet Explorer "Developer Tools" is more efficient and more direct on the problem.



SharePoint Client Browser for SharePoint 2013

I use every day to get Object properties/Metadata/XML/Schema/REST/Data that is possible using SharePoint Client Object Model, has been a huge helper when i need SharePoint Dsta from SharePoint Sites.



Internet Explorer Developer Tools (F12)

 Internet Explorer developer tool has been a nice Swiss knife tool  to compile, debug, capture Traffic and Web analysis , the amount of times i used by day to Test and capture errors is really amazing, already save me a lot of time and did a lot of IT administrative work on the site just using JSOM and the Console from IE.



Fiddler

This tool helps to identify Web traffic and more specific SharePoint Data traffic, when you are working with JSON Data and Cross-Domain call's, it's also very usefull to capture Url error and other internet traffic 



JavaScript Parser

When you are are creating your JavaScript Object Model the JavaScript Parser could be a good Helper to identify the existing Methods and properties to easily reuse and get the correct values.


"Napa" Office 365 Development Tools

It's is nice tool for a beginner but with the type of development i am used to work, it's somehow limited and required a good base of start, all the basic stuff is there to create your initial App but for something more complex is Productive but not efficient, (when  this starts to happen can recommend use the NAPA option to export Solution to Visual Studio).




Visual Studio 2013

This is clear, but the reason i want to make focus is the ability to manage the files to be deployed in your SharePoint App, when you are working with development and Minify JavaScript or CSS files the developer needs to define the files that needs to be included in the SharePoint App. 
When you have your solution ready to be deploy i can recommend to include the Minify files (updated automatically using Web Essentials to be included in the final App Solution, for this you can change the property of the file "Deployment Type" to "NoDeployment" when you don't want to include in the App or change to "ElementFile" to include in your SharePoint App.



SharePoint 2013: Import, validate, and manage app licenses
My last tool is the test of the SharePoint Licenses on your app. I recommend download this tool to simulate the App Licenses (Free, Trial, Paid) before you sent to Microsoft Store for Microsoft Validation.
http://code.msdn.microsoft.com/office/SharePoint-2013-Import-f5f680a6 


image

Small article but hope could help you in your development and avoid the infamous Error 404 - Boat not found


Photo taken by Mark Hässig

Kind regards, 
André Lage

Thursday, May 08, 2014

Hide/Show Underscore (_) Folders in Explorer Viewer using (JSOM/REST) in LisItem PropertyBags

Today's case is about a Out of Box SharePoint behavior that some of you probably already face in your daily work using the Enterprise Content Management and Folders.

When end users creates folders in document Library and name they added the Folder Name started with underscore (_). After the creation they continue to work adding new Documents to that folder, but when they open the Document Library using Explorer Viewer are not able to see the Folder with "_" in the beginning, what causes some panic when they have to manage a huge volume of documents.




There is a very nice article talking about this behavior and reason but they only reference some Powershell IT task in the SPFarm.
http://blogs.msdn.com/b/spblog/archive/2012/04/04/hidden-objects-in-sharepoint-quot-explorer-view-quot.aspx

The article don't take in consideration multiple scenarios like consultants that don't have access to Farm or they are working with SharePoint 2010 Client API, this API don't allow to access "PropertyBag" SharePoint Objects in Lists, but there is a workaround for SharePoint 2010, that is explained in this article, or how users can manage this property using cloud "Office365" or SP2013. 
The SP2013/Office365  have the ability to change the Lists PropertyBag SharePoint Object using REST or JSOM for this case the option "vti_winfileattribs" .

This propose solution works using SharePoint JavaScript Object Model (JSOM) in SharePoint 2010, SharePoint 2013 and Office 365 in away we can manage the visibility of underscore Folders in Explorer Viewer.

For this example i create folder with a name wish has underscore (_) in the begin.



When you open the Explorer Viewer you are not able to see the Folder "_".





One way to update this properties could be using the F12 (Developer Tools) option of your Internet Explorer and the console to execute some JSOM Code as show in the image.



Show underscore Folder using PropertBag Listitem

The following code updates the Columns MetaInfo with the Property bag "vti_winfileattribs"  and Parameter "null" associated to show the Folder from the explorer Viewer. 

//update ListItem Metainfo
function updateListItem() {
    var clientContext = new SP.ClientContext.get_current();
    var oList = clientContext.get_web().get_lists().getByTitle('Documents');
    this.oListItem = oList.getItemById(30);//ID of Listitem
    oListItem.set_item('MetaInfo''vti_winfileattribs:SW|null');
    oListItem.update();
    clientContext.executeQueryAsync(Function.createDelegate(thisthis.onQuerySucceeded), Function.createDelegate(thisthis.onQueryFailed));
}
function onQuerySucceeded() {
    alert('Item updated!');
}
function onQueryFailed(sender, args) {
    alert('Request failed. ' + args.get_message() + '\n' + args.get_stackTrace());
}

updateListItem()

After the code is executed the column PropertyBag "MetaInfois updated with the new value.


You can see the PropertyBag "Metainfo" content of the ListItem using the following Tool with the new value.

vti_winfileattribs:SW|null
vti_foldersubfolderitemcount:IR|0
vti_folderitemcount:IR|0
vti_modifiedby:SR|i:0#.f|membership|*********.onmicrosoft.com

After the change is dont you should be able to see the Folder "_MyFolder"



Hide underscore Folder using PropertBag Listitem

The following code updates the Columns MetaInfo with the Property bag "vti_winfileattribs"  and Parameter "00000016"associated to hide the Folder from the explorer Viewer. 

//update ListItem Metainfo
function updateListItem() {
    var clientContext = new SP.ClientContext.get_current();
    var oList = clientContext.get_web().get_lists().getByTitle('Documents');
    this.oListItem = oList.getItemById(30); //ID of Listitem
    oListItem.set_item('MetaInfo', 'vti_winfileattribs:SW|00000016');
    oListItem.update();
    clientContext.executeQueryAsync(Function.createDelegate(this, this.onQuerySucceeded), Function.createDelegate(this, this.onQueryFailed));
}
function onQuerySucceeded() {
    alert('Item updated!');
}
function onQueryFailed(sender, args) {
    alert('Request failed. ' + args.get_message() + '\n' + args.get_stackTrace());
}

updateListItem()

You can see the PropertyBag "Metainfo" content of the ListItem using the following Tool with the new value.

vti_foldersubfolderitemcount:IR|0
vti_folderitemcount:IR|0
vti_modifiedby:SR|i:0#.f|membership|*********.onmicrosoft.com
vti_winfileattribs:SW|00000016

After the change is done the folder should not appear in the Explorer Viewer as shown in the image.


The hard in this topic was the SharePoint 2010 Environment, the Client Object Model don't allow  access PropertyBags but allows to access the Columns MetaInfo that uses Lookup for the PropertyBags Objects and that makes the trick.

Support Link:
http://blogs.msdn.com/b/spblog/archive/2012/04/04/hidden-objects-in-sharepoint-quot-explorer-view-quot.aspx

Done, very simple solution that could avoid some headaches.

Hope you like this article,
Kind regards,

André Lage

Friday, May 02, 2014

Remove Quick Launch Node from Recent when you create a List using REST from SharePoint App

This is a simples article on how we can use REST to manage Navigation QuickLaunch using REST call in a SharePoint App Base. 

No, I didn't find property or option to disable the List from appearing in the annoying “Recent” Group on the Left Menu “Quick Link”. If there is I will be very happy to know also.
What i did was use REST calls to manage the NavigationNodes "just to be clear".

This is my case: 
I want to create a support List in HostWeb using SharePoint App with some parameters for administrative purpose of my Solution and for that type of need, end users and Site admin should not see this list,  
When the list was prepared to be created as hidden and QuickLaunch as False.
Then you create the list using REST that should appear Hidden and what happen..... The List appears in the Left Menu as a huge traffic light...



The code bellow is used to create a List using REST in HostWeb:

function AddListHostWeb(appWebUrl, hostWebUrl) {
    var executor = new SP.RequestExecutor(appWebUrl);
    executor.executeAsync({
        url: appWebUrl + "/_api/SP.AppContextSite(@target)/web/lists?@target='" + hostWebUrl + "'",
        method: "POST",
        body: "{ '__metadata': { 'type': 'SP.List' }, 'AllowContentTypes': true, 'BaseTemplate': 100,'ContentTypesEnabled': true, 'Description': 'User Custom Actions Recycling Bin', 'Title': 'MyCustomList','Hidden':'True', 'OnQuickLaunch': false  }",
        headers: { "content-type": "application/json;odata=verbose" },
        success: function () {
            GetDeleteQuicklaunch(appWebUrl, hostWebUrl);
        }, error: function (xhr) { SP.UI.Notify.addNotification(xhr.status + ': ' + xhr.statusText, false); }
    });
}

Some Microsoft Link that can support your:
http://msdn.microsoft.com/en-us/library/office/dn531433(v=office.15).aspx

Ok the easy way "Out of the Box" to resolve this issue is accessing the Quick Launch and remove the option that was added, but if you are creating a solution this should be automatic... i think.



To don't use this option and make using custom code in the moment of the Creation of the List i create this example by REST to remove the Navigation Node.


Remove QuickLink option using REST in SharePoint App

The following code gets the quick Launch ID associated with the created List and calls the DELETE Method to remove the node from Recent group.

To remove the Navigation node  was created the following JSOM Methods
  • GetDeleteQuickLaunch
    • This Method Get the Navigation node ID  using Rest associated with the created List and call the Delete method 
    • The REST Call "...web/navigation/quicklaunch?$expand=Children..." returns Root navigation nodes and their children "to get the node from RECENT"
  • DeleteQuickLaunch
    • Send a REST call to delete the Navigation Node.

Here is the code where the call is made to remove the Navigation link from the Quick Launch.

function GetDeleteQuicklaunch(appWebUrl, hostWebUrl) {
    var executor = new SP.RequestExecutor(appWebUrl);
    executor.executeAsync(
    {
        url: appWebUrl + "/_api/SP.AppContextSite(@target)/web/navigation/quicklaunch?$expand=Children&$select=Title,Children/Title,Children/Id&@target='" + hostWebUrl + "'",
        method: "GET",
        headers: { "Accept": "application/json; odata=verbose" },
        success: function (data) {
            var jsonObject = JSON.parse(data.body).d;
            for (var i = 0; i < jsonObject.results.length; i++) {
                if (jsonObject.results[i].Children.results.length > 0) {
                    var children = jsonObject.results[i].Children;
                    for (var x = 0; x < children.results.length; x++) {
                        if (children.results[x].Title == 'MyCustomList') {
                            DeleteQuicklaunch(appWebUrl, hostWebUrl, children.results[x].Id)
                        }
                    }
                }
            }
        },
        error: function (xhr) { SP.UI.Notify.addNotification(xhr.status + ': ' + xhr.statusText, false); }
    });
}

function DeleteQuicklaunch(appWebUrl, hostWebUrl, id) {
    var executor = new SP.RequestExecutor(appWebUrl);
    executor.executeAsync(
    {
        url: appWebUrl + "/_api/SP.AppContextSite(@target)/web/navigation/quicklaunch('" + id + "')?@target='" + hostWebUrl + "'",
        method: "DELETE",
        headers: { "Accept": "application/json; odata=verbose" },
        success: function () { },
        error: function (xhr) { SP.UI.Notify.addNotification(xhr.status + ': ' + xhr.statusText, false); }
    });
}


The main idea of the code is get the QuickLink Node id and send a REST request to delete the item.
When you are using Fiddler you are able to get the NavigationNodes from QuickLaunch as shown in the image.



After this change the Link should not be visible anymore in the left menu and continues hidden to users.



Of course even if the list is Hidden you are able to access using direct Url to the List....



Simple and done...
Maybe i am missing something in the creation of the list or other thing "i am using JSOM", if you know a workaround or property, please provide some feedback, here are the properties of the List.

Hope you enjoy this article.
Kind regards, 
Andre Lage