Working with SASS style sheets in Office 365 / SharePoint 2013

My new favorite thing is creating all my style sheets with SASS. SASS makes writing my style sheets faster, easier, cleaner, and more reusable. Don’t ask me why I chose SASS, but I liked it just a little better than LESS even though they are very similar.  You can go do research for yourself and decide which you’d like to use.  SASS – http://sass-lang.com/ LESS – http://lesscss.org/.

SASS is nothing new however it is very difficult to work with when using with SharePoint 2010/2013 .. or Office 365. Why is it difficult? Because SASS needs to be compiled to CSS and none of your typical SharePoint tools do that.

 

Current Scenarios:

  1. If you are working on O365 you might be working with SharePoint Designer which won’t do anything for you.
  2. Maybe you have Visual Studio you can use Web Essentials or Mindscape Web Workbench. Both of these will compile the SASS to CSS
  3. You can use a stand alone compiler like Koala, Propos, & Scout (for Windows) … but configuring them to work can be tricky.
  4. You can use an online compiler – SassMeister | The Sass Playground! , but then you will be doing a lot of copy / pasting / uploading.

 

This article is going to show you how to use Sublime Text 2 on your desktop, to write and compile SASS, which will automatically be saved to O365. No copy / paste or uploading needed!

 

General Approach

  • Map network drive to O365 site
  • Use a desktop editor to create SASS
  • Use a desktop compiler to create the CSS
  • Use Scout to monitor CSS changes locally and copy file to network drive

 

Pre-Requisites

1. Java v7 – http://www.java.com/en/download [Scout has issue with any other version, https://github.com/mhs/scout-app/issues/173]

2. O365 Site

 

Step 1 – Install Sublime Text 2 & SASS Builder

1. go to http://www.sublimetext.com/2 and download and install

2. go to https://github.com/jaumefontal/SASS-Build-SublimeText2 (Follow Instructions there skip to step 3)

Installing Ruby

Install Ruby library (for windows) – http://rubyinstaller.org/downloads/

  • check box to add Ruby executables to your PATH

Start Command Prompt with Ruby

 image

In the console type the following

  • gem install sass
  • IF you get this error

image

Take a deep breath – you need to download the cert and add it to your RubyGem’s certificate directory.

image

  • go to that folder in windows in windows explorer
  • open up a subfolder rubygems\ssl_certs
  • copy the AddTrustExternalCARoot-2048.pem to the rubygems\ssl_certs
  • Retry “gem install sass” –SUCCESS!

image 

Installing SASS Build System

The easiest way to install this package is through Package Control.

1. Download and install the Package Control Plugin. Follow the instructions on the website

2. Open the command panel: Control+Shift+P (Linux/Windows) or Command+Shift+P (OS X) and select ‘Package Control: Install Package‘.

image

3. When the packages list appears type ‘SASS‘ and you’ll find the SASS Build System. Select to install it.

image 

4. Set the Build System to SASS,  In Tools –> Build System –> SASS

image

5. Now you can compile your SASS files! Launch your build with Control+B (Linux/Windows) or Command+B (OS X).

**NOTE** if you are not getting you are not getting colors on your SASS – click the bottom right corner and set the Language as SASS

image

 

Step 2 – Map Network Folder to O365

The key to this part is to first log into your O365 site w/ Internet Explorer. I then go to any folder and click the button to Open with Explorer. Then I can usually successfully map the network drive.

PRE-REQUISITES – Your site must be in the trusted sites. I actually add a few *.microsoft[sites] http://blogs.technet.com/b/sharepoint_made_easy/archive/2013/03/20/map-network-drive-webdav-with-sharepoint-online-o365.aspx

IE > Tools > Internet Options > Security > Trusted Sites [click sites button]

image 

1. Log into your site with Internet Explorer – be sure to check ‘Keep me signed in’

image

2. Open up any document library [might look different. this screenshot from small resolution virtual machine]

image

3. Next, do the standard map network drive per your Operating System. I’m running Windows 7.

basically right click on Computer and click Map Network Drive

image

4. Enter in the site url w/ any subfolder you want [root is fine]. I also check connect using different credentials.

4.1 Enter in your O365 credentials

image

5. SUCCCESS !

image

Step 3 – Install & Setup Scout

For this you will need to create a local working folder for all CSS. for example I will create a folder on the desktop called ‘MySassExample’

1. Download and Install Scout @ http://mhs.github.io/scout-app/

2. Once Scout installs, Open it up and click the + in the bottom left to add a new project

image

3. Navigate to your local working folder

image

4. Under Configure, Stylesheet Directories –> set the Input Folder to your local working folder & the Output folder to your network mapped drive (o365)

I set it to output to the Style Library of my site.

image

5. Click the Play Button

image

 

Step 4 – The Final Step, Verifying it all works!

1. With Scout running, Go to your local working folder.

2. Create a new .SCSS file or open an existing one in Sublime Text 2

3. Create some SASS

image

4. Save, then CTRL+B to build it. You should get the write ~file output.
 image

5. In Scout, you should see …. overwrite .css

Sometimes you need to give it a few seconds to save the file to o365, the network drive tends to take longer to save files

image

** NOTE if you are using an existing file make sure you check it out via SharePoint. Otherwise it won’t save and you’ll see errors in Scout when it tries **

 

Wrap Up

The concludes how to setup Scout & Sublime Text 2 to create and update SASS styles sheets which compile to .css and automatically push to O365. The hardest thing I found is mapping the drive successfully. There are many helpful articles on the different errors you might get trying to do so.  It may seen like a lot of steps to get going, but if you have used SASS or LESS before you know how powerful it is and how much time you might potentially save. This blog was meant to help get this setup and show you how you can get started. There are other programs like Scout, but Scout was the only one that I could get to work the way I liked. There are also other editors that you could use if you don’t like Sublime Text 2. Best of luck!

 

Resources

More on Scout – http://www.impressivewebs.com/sass-on-windows-with-scout-app/

Errors Mapping Network Drive – http://blogs.technet.com/b/sharepoint_made_easy/archive/2013/03/20/map-network-drive-webdav-with-sharepoint-online-o365.aspx

SASS – http://sass-lang.com/

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);

Content Query Web Part + Calendar = Mad Problems

I recently had to assemble a content query web part (my least favorite thing) pulling data from a Calendar list and experienced hours of pain trying to get it to pull the locations field as well as the start/end time consistently. I share my pain so that other won’t have to endure, and hopefully some tips so that you can skip all the BS and just make a quick web part that should have took about 30 minutes.

I had an extremely painful time working with the Location field for some reason. It would show on ctrl-f5 or the first load of the web part. Then disappear or show intermittently. It was basically unreliable until I used the ID and renamed the fields as described below.

Getting the fields right

Step 1 – Calendar list settings

If you go to the list settings and copy or inspect the link to the Start Time field you’ll see something like this.

/_layouts/15/FldEdit.aspx?List=%7BE84FD6F2%2D11AD%2D443B%2D84A2%2DA6E1B198458D%7D&Field=EventDate

This is good to give you what the field name is but we actually just want the field ID. Next I move up to the parent content type and click “Event”

image

In these columns I copy or inspect Start Time field again and you’ll see this

/_layouts/15/ManageContentTypeField.aspx?ctype=0x010200D536EDB0AB69A047B200ABF60FFE33EF&List=e84fd6f2-11ad-443b-84a2-a6e1b198458d&Field=EventDate&Fid=%7B64cd368d%2D2f95%2D4bfc%2Da1f9%2D8d4324ecb007%7D

This is what I want – I’ll get the actual field ID. using a url decoder [http://meyerweb.com/eric/tools/dencoder/]

Fid=%7B64cd368d%2D2f95%2D4bfc%2Da1f9%2D8d4324ecb007%7D

Fid={64cd368d-2f95-4bfc-a1f9-8d4324ecb007}

Step 2 – Content Query

Once you’ve exported the content query so we can edit it in notepad, search for CommonViewFields

Add the Start Time field to this property

<property name=”CommonViewFields” type=”string”>{64cd368d-2f95-4bfc-a1f9-8d4324ecb007},DateTime;</property>

Next search for DataColumnRenames, add the following. The EventStartDateTime is what you would use in the xslt to pull this property. You can call it what you like at this point.

<property name=”DataColumnRenames” type=”string”>{64cd368d-2f95-4bfc-a1f9-8d4324ecb007},EventStartDateTime;</property>

Below is a snippet of me using the EventStartDateTime in my xslt. I’m assigning it to a variable to i can further manipulate it.

image

Step 3 – Repeat

You can repeat the following steps for a fool proof method of getting the fields you want into your content query web part. If you use the column ID and rename it you should have no issues.

 

Location Field

{288f5f32-8462-4175-8f09-dd7ba29359a9},Text;

Start Time

{64cd368d-2f95-4bfc-a1f9-8d4324ecb007},DateTime;

End Time

{2684f9f2-54be-429f-ba06-76754fc056bf},DateTime;

 

<property name=”CommonViewFields” type=”string”>{288f5f32-8462-4175-8f09-dd7ba29359a9},Text;{64cd368d-2f95-4bfc-a1f9-8d4324ecb007},DateTime;{2684f9f2-54be-429f-ba06-76754fc056bf},DateTime;</property>

<property name=”DataColumnRenames” type=”string”>{2684f9f2-54be-429f-ba06-76754fc056bf},EventEndDateTime;{288f5f32-8462-4175-8f09-dd7ba29359a9},EventLocation;{64cd368d-2f95-4bfc-a1f9-8d4324ecb007},EventStartDateTime;</property>

 

Troubleshooting

From this day forward I vowed to always include this debugging template in my ItemStyle.xsl. It will basically just dump the raw data to your screen, so you can see what fields you have

   1: <xsl:template name="DisplayRawData" match="Row[@Style='DisplayRawData']" mode="itemstyle">

   2:   <xsl:for-each select="@*">

   3:     <xsl:value-of select="name()"/>

   4:     <xsl:text> = </xsl:text>

   5:     <xsl:value-of select="."/>

   6:     <br/>

   7:   </xsl:for-each>

   8:   <br/>

   9:   <br/>

  10:   <br/>

  11: </xsl:template>

If you set the Item style: DisplayRawData, you’ll get a dump of the fields and values that are available. In the screen shot below you’ll notice then fields that we’ve renamed and they data

image

Permanently Removing the Recent Node from Quick Launch for SharePoint 2013

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.