SharePoint Framework Developer Preview Release – Where do I begin?

This is an exciting time! The much anticipated SharePoint Framework Preview was released yesterday. And guess what … they are essentially Add-ins /Apps. Right now just the web parts piece is released and the end result is an improvement over app parts. The most noteworthy part is that the web parts are now on the page as opposed to being inside of an iframe! They have a couple more improvements like being able to be fully responsive, having web part properties. They mention that new developments will be added frequently and they are targeting a release time frame of early 2017.

The biggest hurdle that I can see for traditional SharePoint developer is the tooling.  If you are not comfortable with using a command line, Node.js, NPM, Yeoman or Gulp then you should invest a large chunk of time to familiarize yourself with those technologies. I believe that having a base knowledge of the “What” is actually happening when you type “yo @microsoft/sharepoint” is important.  Most modern day front end web developers are already comfortable with these things. For the traditional SharePoint developer most of these things will be foreign and seemingly difficult to grasp at first (for some).

So you’ve visited the SharePoint Framework Developer Preview Release git-hub and you’re following the steps but you find yourself scratching your head. What did you miss? What is all this stuff?  Where do you begin to even get started?

First off don’t feel bad.. most people are seeing this for the first time just like you.

<rant> Just the other day I discussed the front end development build process of node/gulp/npm to other day to our Senior SharePoint developers and even they were confused. Some even got pissed off and mad at Microsoft. I can remember the comments clearly, “COMMAND LINE? What year is this?”. I’ve embraced the change and I can see the good things that can come out of it.  Getting back to point of this article. </rant>

I recommend that you take the time to learn about the new tools that they are asking us to adopt. There are ton’s of free online resource on YouTube alone… However if you have or can get your job to pay for a Pluralsight you will access to premium content with some of the top authors (this will be more important for the SharePoint work)

Online resources (free) to help you get familiar with for Node/Gulp/TypeScript/Yeoman

  • Node.js Fundamentals
  • NodeJS – NPM Package Manager – Tutorial 2
  • Gulp.js Build System #1 – Fundamentals
  • Gulp – The Basics
  • TypeScript/ES6 Module Syntax Intro
  • Yeoman – Read first paragraph
  • If you haven’t even started to develop apps or client side web parts using JavaScript then you need to take a look at resources geared towards connecting to SharePoint lists and libraries using the REST end points or the client side JavaScript object model. There are some course that I highly recommend. 

    Pluralsight Courses for SharePoint Client Object Model

  • David Mann
  • http://www.pluralsight.com/courses/developing-sharepoint-2013-javascript
  • http://www.pluralsight.com/courses/developing-sharepoint-2013-javascript-part2
  • http://www.pluralsight.com/courses/developing-sharepoint-2013-javascript-part3
  • Rob Windsor
  • http://www.pluralsight.com/courses/sharepoint-2013-client-object-model-rest
  • Rob Windsor, & Sahil Malik
  • http://www.pluralsight.com/courses/sp2010-client-object-model
  • Can I just use Visual Studio? PLEASE!@#!@ Do I need to learn all the front end tooling?

    The framework can be intimidating for the people not comfortable with the technologies listed. For the most part you really just need to learn a couple of things and you can get by. TypeScript is not one of those things. Good news for those most comfortable working in Visual Studio. They have documentation on how to get setup via Visual Studio 2015 (Update 3) here.  The bad news is that is still uses Node.js and the Gulp build tasks to compile and build the project. We will just have to wait and see if the process is improved with future releases.

    Ok I got all that…

    After you’ve covered all this then you should walk through the examples. The documentations is very good at explaining how to get everything installed and get up and running with your first Hello World web part. My only piece of advice is to read through everything 1 step at a time. It took me a few reads before I was able to get the first web part to load inside SharePoint.

    Final Thoughts:

    • Get an Office 365 Developer Account
    • Learn the fundamental technologies in play – Node.js, Gulp, Yeoman, Node Package Manager (npm)
    • Become a TypeScript master
    • Changes, Updates, Fixes will be coming frequently with a release time frame of 2017 (so they say)
    • Preview is missing a few things that a traditional deployment would have
      • Examples on how to deploy files to SharePoint / not CDN
      • Examples on how to deploy assets (List Definitions, Content Types, Site Columns) => Notes on Solutions Packaging
    Advertisements

    Bootstrap Navigation for Office 365 & SharePoint 2013 using jQuery and REST

    Introduction

    In my last post I talked about making the SharePoint navigation render in a Bootstrap friendly way. I suggested that you could do this by changing the markup of the navigation using an ASP:Repeater to display the nodes. This works great if you’re on an on-prem environment but on Office 365 you cannot use this method because the masterpage cannot contain code blocks. If you haven’t read that it might be worth taking a look at – Bootstrap Responsive Navigation in SharePoint .

    This post will demonstrate an Office 365 safe version that use JavaScript, jQuery, and REST to retrieve the navigation from SharePoint’s navigation provider. Once we have that we’ll render that out to the screen client side. With this approach we can use the out of the box Managed Meta Data navigation provider for the navigation, and we can control the exact rendering so that it fits Bootstrap’s model for a navigation bar.

    The code can be downloaded directly from my git-hub account – https://github.com/tom-daly/sp2013-bootstrap-nav

    Getting Started

    It’s very simple to get started you only need to do a few things.

    1. Add the link to the topNavigation.js in your SharePoint masterpage

    1. In the Master Page, add the container below where the navigation will be pushed into. You will need to determine where you want the navigation to go, I’ll have a full example at the very end.

    1. Switch the SharePoint Navigation to use the Managed Meta Data Navigation
      1. In Site Settings
      2. Look and Feel -> Navigation

    Once you complete those steps you’ll have your navigation displayed in that container.

    Other Details

    Changing Rendering

    The rendering of the menu is defined in the renderNavigationNodes function. This is currently the format the Bootstrap likes. If you visit the Bootstrap website and take a look at the first example

    http://getbootstrap.com/components/#navbar

    The red box is exactly what the code is injecting. So you would wrap all that however you want it to appear. Follow the examples there are plenty out there.

    2 Level Flyouts

    Currently this supports only 2 levels, a top level and 1 flyout. This is just what Bootstrap v3.3.5 supports and that’s what I’m sticking with currently. If you want more levels then it’s up to you to figure that part out. It can be done and there are other 3rd parties implementing 3rd or 4th level flyouts after the fact. The code is recursive and will support as many levels as you have, you just need to handle the front end portion.

    The REST Call

    The query that I am using can be seen below. The /web/navigation/topbar endpoint – flat out sucks. It won’t show if a node is hidden and it didn’t behave. This is the only one I could get to work reliably.

    Changing Target Container

    If you want to change where the navigation goes, instead of “#my-top-navigation” then you can edit the topNavigation.js file and at the bottom change the selector to another ID preferably.

    Sample Files

    I have a few more sample files that might be of use to look at.

    Base.css – Sample CSS file (helps with some Bootstrap/SharePoint resets)

    Bootstrap.Master – Sample masterpage used in this example. In this sample I create my own header and not s4-titlerow. Using this html structure you’ll get the collapsible navigation that is popular on most web sites.

    Conclusion

    The whole goal of this script was to have a way for an Office 365 site to use the Bootstrap navigation. Although there are still limitations it’s doable. And you don’t even necessarily need to touch the master page. You could attach the scripts and css via other methods which I won’t go in to. So if you’re an Office 365 purist who doesn’t want to customize the master page you would want to take an approach to this. I hope this helps someone out there. If there are problems with the script you can report the issues through the github page. https://github.com/tom-daly/sp2013-bootstrap-nav/issues

    Delete a Workflow on the Host Web from the App Web

    below is some sample code to delete a workflow on the host web from your app web. This would be useful in the event you want your App to remove a workflow that you have deployed to the host web.

    NOTE: you must include a reference to “/_layouts/15/SP.WorkflowServices.js”

    this file can be downloaded here

       1: var deleteWorkflow = function (workflowName) {

       2:

       3:     //Using the App Web as the client context

       4:     clientContext = new SP.ClientContext.get_current();

       5:

       6:     //Get the host web URL from the query string params

       7:     //I have a function getHostWebUrl() - which is not included.

       8:     //http://stackoverflow.com/questions/901115/how-can-i-get-query-string-values-in-javascript

       9:     var hostWebUrl = getHostWebUrl();

      10:

      11:     //Using the hostWebContext as an AppContextSite

      12:     hostWebContext = new SP.AppContextSite(clientContext, hostWebUrl);

      13:

      14:     //Get the Workflow Services Manager for the Host web, NOTE: you initialize it with the clientContext & the hostWebContext Web)

      15:     var hostWebWorkflowServicesManager = new SP.WorkflowServices.WorkflowServicesManager.newObject(clientContext, hostWebContext.get_web());

      16:     var hostWebWorkflowDeploymentService = hostWebWorkflowServicesManager.getWorkflowDeploymentService();

      17:     var hostWebDefinitionsCollection = hostWebWorkflowDeploymentService.enumerateDefinitions(false);

      18:

      19:     //load all the workflow definitions from the host web 

      20:     clientContext.load(hostWebDefinitionsCollection);

      21:     clientContext.executeQueryAsync(function () {

      22:

      23:         //get the enumerator for all the workflow definitions

      24:         var workflowDefinitions = hostWebDefinitionsCollection.getEnumerator();

      25:         while (workflowDefinitions.moveNext()) {

      26:

      27:             //set the current definition to a variable

      28:             var workflowDefinition = workflowDefinitions.get_current();

      29:

      30:             //uncomment to see all the workflows that it finds

      31:             //console.log("Found Workflow : " + workflowDefinition.get_displayName());

      32:

      33:             //looking for a match on the name of the workflow

      34:             if (workflowDefinition.get_displayName() === workflowName) {

      35:                 //using the host web workflow deployment service to delete the workflow definition

      36:

      37:                 hostWebWorkflowDeploymentService.deleteDefinition(workflowDefinition.get_id());

      38:                 //this comment will just display what workflow WILL BE deleted 

      39:                 console.log("Deleted workflow : " + workflowName + ", " + workflowDefinition.get_id().toString());

      40:

      41:                 //if more than one workflow with the same name is found it will delete all of them

      42:                 //the delete will get queued up and executed by the .executeQueryAsync

      43:                 //if you don't want that then you have to break out of the while loop

      44:             }

      45:         }

      46:         //this query will execute the delete action

      47:         clientContext.executeQueryAsync(function () {

      48:             //Successfully deleted   the workflow

      49:         }, function (sender, args) {

      50:             console.log("Unable to delete workflow : " + workflowName);

      51:             console.log("<span style='color:red'>Reason : " + args.get_message() + "</span>");

      52:         });

      53:     }, function (sender, args) {

      54:         console.log("Failed to load workflows");

      55:         console.log("<span style='color:red'>Reason : " + args.get_message() + "</span>");

      56:     });

      57: };

    Provision a Workflow to the Host Web from the App Web

    below is some sample code to provision a workflow on the host web from your app web. This sample code is in one block for demo purposes. It makes 4 asynchronous calls to SharePoint to complete successfully.

    In the event you’d want to define in your App a workflow that would run on a list in the host web this is the code you would use to deploy (move it from App to Host) and attach it to a list in the host web.

    NOTE: you must include a reference to “/_layouts/15/SP.WorkflowServices.js”

    this file can be downloaded here

       1: var attachWorkflow = function(workflowName, workflowListName, workflowHistoryListName, workflowTasksListName) {

       2:

       3:      //Using the App Web as the client context

       4:      clientContext = new SP.ClientContext.get_current();

       5:

       6:      //Get the host web URL from the query string params

       7:      //I have a function getHostWebUrl() - which is not included.

       8:      //http://stackoverflow.com/questions/901115/how-can-i-get-query-string-values-in-javascript

       9:      var hostWebUrl = getHostWebUrl();

      10:

      11:      //Using the hostWebContext as an AppContextSite

      12:      hostWebContext = new SP.AppContextSite(clientContext, hostWebUrl);

      13:

      14:      //Get the Workflow Services Manager for the Host web, NOTE: you initialize it with the clientContext & the hostWebContext Web)

      15:      var hostWebWorkflowServicesManager = new SP.WorkflowServices.WorkflowServicesManager.newObject(clientContext, hostWebContext.get_web());

      16:      var hostWebWorkflowDeploymentService = hostWebWorkflowServicesManager.getWorkflowDeploymentService();

      17:      var hostWebSubscriptionService = hostWebWorkflowServicesManager.getWorkflowSubscriptionService();

      18:

      19:      //Get the Workflow Services Manager for the App web 

      20:      var workflowServicesManager = new SP.WorkflowServices.WorkflowServicesManager(clientContext, clientContext.get_web());

      21:      var workflowDeploymentService = workflowServicesManager.getWorkflowDeploymentService();

      22:      var workflowDeploymentServiceDefinitions = workflowDeploymentService.enumerateDefinitions(false);

      23:

      24:      //Load all the Workflow Definitions from the App web

      25:      clientContext.load(workflowDeploymentServiceDefinitions);

      26:      clientContext.executeQueryAsync(function () {

      27:

      28:          //Get the enumerator for all the workflow definitions in the App web

      29:          var workflowDefinitions = workflowDeploymentServiceDefinitions.getEnumerator();

      30:          while (workflowDefinitions.moveNext()) {

      31:

      32:              //set the current definition to a variable

      33:              var workflowDefinition = workflowDefinitions.get_current();

      34:              //test, trying to locate your workflow in the App web

      35:              if (workflowDefinition.get_displayName() === workflowName) {

      36:

      37:                  //now grab the xaml definition of the selected workflow

      38:                  var workflowXaml = workflowDefinition.get_xaml();

      39:

      40:                  //define a new workflow and assign it xaml & name

      41:                  var newWorkflow = new SP.WorkflowServices.WorkflowDefinition.newObject(clientContext, hostWebContext.get_web());

      42:                  newWorkflow.set_xaml(workflowXaml);

      43:                  newWorkflow.set_displayName(workflowName);

      44:

      45:                  //using the host webs Workflow Deployment Service, save the workflow.

      46:                  hostWebWorkflowDeploymentService.saveDefinition(newWorkflow);

      47:

      48:                  //load the new workflow and initialize the Id

      49:                  clientContext.load(newWorkflow, "Id");

      50:                  //this query will save your workflow to the host web

      51:                  clientContext.executeQueryAsync(function() {

      52:

      53:                      //publish the workflow on the host web

      54:                      hostWebWorkflowDeploymentService.publishDefinition(newWorkflow.get_id());

      55:

      56:                      //getting all the lists that the workflow will attach to

      57:                      //these lists already created on the host web

      58:                      var targetList = hostWebContext.get_web().get_lists().getByTitle(workflowListName);

      59:                      var historyList = hostWebContext.get_web().get_lists().getByTitle(workflowHistoryListName);

      60:                      var tasksList = hostWebContext.get_web().get_lists().getByTitle(workflowTasksListName);

      61:

      62:                      //loading the lists & initialize the Id

      63:                      clientContext.load(targetList, "Id");

      64:                      clientContext.load(historyList, "Id");

      65:                      clientContext.load(tasksList, "Id");

      66:

      67:                      //this query will publish the workflow and get the required list id's for the next step

      68:                      clientContext.executeQueryAsync(function() {

      69:

      70:                              //creating a new workflow subscription

      71:                              var subscription = new SP.WorkflowServices.WorkflowSubscription(clientContext, hostWebContext.get_web());

      72:

      73:                              //setting some properties of the subscription

      74:                              subscription.set_name(workflowName + "-ItemAdded");

      75:                              subscription.set_enabled(true);

      76:                              subscription.set_definitionId(newWorkflow.get_id().toString());

      77:                              subscription.set_eventSourceId(targetList.get_id());

      78:                              subscription.set_eventTypes(["ItemAdded"]);

      79:

      80:                              subscription.setProperty("TaskListId", tasksList.get_id().toString());

      81:                              subscription.setProperty("HistoryListId", historyList.get_id().toString());

      82:                              subscription.setProperty("FormData", "");

      83:

      84:                              //attaching the subscription to the target list 

      85:                              hostWebSubscriptionService.publishSubscriptionForList(subscription, targetList.get_id());

      86:

      87:                              //this query will execute the creation of the new subscription and adding attaching the subscription to the target list on the host web

      88:                              clientContext.executeQueryAsync(function() {

      89:                                  console.log("Workflow : \"" + workflowName + "\" successfully added to list : \"" + workflowListName + "\"");

      90:                              }, function(sender, args) {

      91:                                  console.log("Workflow : " + workflowName + " not successfully added to list : " + workflowListName);

      92:                                  console.log("<span style='color:red'>Reason : " + args.get_message() + "</span>");

      93:                              });

      94:                          },

      95:                          function(sender, args) {

      96:                              console.log("Failed to get workflow list IDs");

      97:                              console.log("<span style='color:red'>Reason : " + args.get_message() + "</span>");

      98:                          });

      99:

     100:                  }, function(sender, args) {

     101:                      console.log("Failed to create the workflow definition");

     102:                      console.log("<span style='color:red'>Reason : " + args.get_message() + "</span>");

     103:                  });

     104:              }

     105:          }

     106:      }, function (sender, args) {

     107:          console.log("Could not find workflow : " + workflowName);

     108:          console.log("<span style='color:red'>Reason : " + args.get_message() + "</span>");

     109:      });

     110:  };

    Deleting a List on the Host Web from the App Web with JavaScript Object Model (JSOM)

    Below is some sample code of how to delete a list on the host web from the app web via SharePoint JavaScript object model
     
       1: function DeleteList(listName) {

       2:  

       3:     //Using the App Web as the client context

       4:     clientContext = new SP.ClientContext.get_current();

       5:  

       6:     //Get the host web URL from the query string params

       7:     //I have a function getHostWebUrl() - which is not included.

       8:     //http://stackoverflow.com/questions/901115/how-can-i-get-query-string-values-in-javascript

       9:     var hostWebUrl = getHostWebUrl();

      10:     //Using the hostWebContext as an AppContextSite

      11:     hostWebContext = new SP.AppContextSite(clientContext, hostWebUrl);

      12:  

      13:     //get the list using the host web context

      14:     var list = hostWebContext.get_web().get_lists().getByTitle(listName);

      15:     list.deleteObject();

      16:     

      17:     //Always use the context of the app web to do the work or load and executing

      18:     clientContext.executeQueryAsync(function() {

      19:         console.log("Deleted List : \"" + listName + "\"");

      20:     }, function(sender, args) {

      21:         console.log("Failed to delete list : " + listName);

      22:         console.log("<span style='color:red'>Reason : " + args.get_message() + "</span>");

      23:     });

      24:  

      25: }

      26:  

      27: // parameters : List Name

      28: DeleteList("My Test List");

    Provisioning a List on the Host Web from the App Web with JavaScript Object Model (JSOM)

    Below is some sample code of how to provision a list from the app web via SharePoint JavaScript object model

       1: function CreateList(title, url, templateType, hidden) {

       2:  

       3:     //Using the App Web as the client context

       4:     clientContext = new SP.ClientContext.get_current();

       5:  

       6:     //Get the host web URL from the query string params

       7:     //I have a function getHostWebUrl() - which is not included.

       8:     // Some Ideas Here: http://stackoverflow.com/questions/901115/how-can-i-get-query-string-values-in-javascript

       9:     var hostWebUrl = getHostWebUrl();

      10:     //Using the hostWebContext as an AppContextSite

      11:     hostWebContext = new SP.AppContextSite(clientContext, hostWebUrl);

      12:  

      13:     //Create List Code

      14:     var listCreation = new SP.ListCreationInformation();

      15:     listCreation.set_title(title);

      16:     listCreation.set_templateType(templateType);

      17:     listCreation.set_url(url);

      18:  

      19:     //must use the hostWebContext to get the list in that site

      20:     var lists = hostWebContext.get_web().get_lists();

      21:     var list = lists.add(listCreation);

      22:     list.set_hidden(hidden);

      23:     list.set_onQuickLaunch(false);

      24:     list.update();

      25:  

      26:     //Always use the context of the app web to do the work or load and executing

      27:     clientContext.load(list);

      28:     clientContext.executeQueryAsync(function() {

      29:         console.log("Created List : \"" + title + "\"");

      30:     }, function(sender, args) {

      31:         console.log("Failed to create list : " + title);

      32:         console.log("<span style='color:red'>Reason : " + args.get_message() + "</span>");

      33:     });

      34:  

      35: }

      36:  

      37: // parameters : List Name, List Url, Template Type (can be ListTemplateType or Template ID, Hidden)

      38: CreateList("My Test List", "Lists/MyTestList", SP.ListTemplateType.genericList, false);

      39: CreateList("My Test List", "Lists/MyTestList", 171, false);

    Permanently Removing the Recent Node from Quick Launch for SharePoint 2013

    # Update 5/19/2015 – This article is a javascript/css (wsp) based solution.

    A reader Håkan Nilsson has suggested a simple solution to permanently hide the Recent node. 

    • Create an empty SharePoint Group
    • In Site Settings -> Look and Feel -> Navigation, Edit the Recent node in Current Navigation
    • Add your blank group to the Audience field.

    this method is super easy for an end user to implement without deploying a sandbox solution below. But feel free to continue reading if you are interested in that method. 

    Introduction

    Yesterday I was tasked to remove the “Recent” node from the quick launch for a customers SharePoint online site. I’ve decided to release the solution in hopes that other people could take advantage of it. The picture below will show you the “Recent” node.

    Recent Node

    This node was undesired by the user because they wanted to have control over the quick launch navigation. They mentioned that every time they add a new list or library then new items will be added under the “Recent” node. When they go and delete it from the Navigation section in Look & Feel, it will just re-appear when they create a new list or library. Basically they wanted it gone.

    Approach

    I chose an approach similar to this blog article http://www.jasperoosterveld.com/2013/02/sharepoint-2013-remove-recent-in-quick.html however, I needed it to work on sandbox for o365 and SPO. I also decided to sharpen my JavaScript Client Object model skills and write a small script that will remove the “Recent” node whenever it is created.

    I paired this with the jQuery as suggested in the article to hide the “Recent” node. Except I made one modification to it you can see below. I’ve added the id #sideBox to have this only search in the Quick Launch. I explain more about why I remove and hide it in the next section.

    this > jQuery(“.ms-core-listMenu-item:contains(‘Recent’)”).parent().hide();

    to this > jQuery(“#sideBox .ms-core-listMenu-item:contains(‘Recent’)”).parent().hide();

    The Solution

    The entire solution & the pre-built .wsp can be downloaded from CodePlex – http://rr.codeplex.com

    The sandbox solution is composed of two components:

    1. RemoveRecent.js – JavaScript Client Object Model code which removes the “Recent” node from the quick launch if detected on page refresh.
    2. HideRecent.js – jQuery code to hide the “Recent” node

    Why remove and hide?

    Since the RemoveRecent.js runs client side there is a case where you may see the “Recent” node. For example you just finished creating a new list and the page refresh. The code to remove the node in RemoveRecent.js is called asynchronously. The list or library is removed from the back end but on the client side is not updated. This could be handled with a refresh but that causes a double refresh once you create a new list and there is a delay between the refreshes. I felt that this created a bad user experience. The answer to that was to also hide the node using HideRecent.js

    Hope this may be useful to some of you out there.