Wednesday, February 18, 2015

Better SharePoint Content Authoring:: Improving Navigation To Lists/Libraries

Once again, I’ll start with a Picture that represents the purpose of this Post. As you can imagine, I try to make it easy for my content authors to Navigate to different lists and libraries where the user is supposed to navigate to manage the content.

image

Sure you can ask the users to go to “View All Site Content” or access some of these links for “Site Settings” but you are able to save thousands of clicks over a period of time by making these links available in “Site Actions” menu. They don’t have to remember to go to “All Site Content” to manage lists and “Site Settings” to manage the term store or the navigation. That’s very inconvenient for my content authors.

Doing this is actual simple. All you need to do is:

1. Understand that the Site Actions can be extended programmatically and declaratively. I’ll talk about declarative extension in this post. You can get a fair idea about what you can do by observing how SharePoint is doing it. And those customizations can be found here

image

2. You can have your own customizations by putting your entries into Master Page Gallery –> Editing Menu –> CustomSiteAction.xml

For some of above customizations, the content of CustomSiteAction.xml in master page gallery would look like this:

<?xml version="1.0" encoding="utf-8" ?>
<Console>
  <references>
    <reference TagPrefix="cms"
      assembly="Microsoft.SharePoint.Publishing, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"
      namespace="Microsoft.SharePoint.Publishing.WebControls.EditingMenuActions" />
 
  </references>
  <structure>
 
   
 
    <ConsoleNode
       DisplayText="Ashish's Extended Menu"
       Description="Helps you navigate to different areas of the site."
       UseResourceFile="false"
       MenuGroupId="310"
       Sequence="211"
       ImageUrl="/_layouts/images/ActionsSettings.gif"
       UserRights="BrowseDirectories|ManagePermissions|ManageWeb|ManageSubwebs|AddAndCustomizePages|ApplyThemeAndBorder|ManageAlerts|ManageLists|ViewUsageData"
       RequiredRightsMode="Any"
       PermissionContext="CurrentSite"
       NavigateUrl="javascript:"
       UIVersion="4"
       ID="ASHISH_Manage">
      <ConsoleNode NavigateUrl="_layouts/AreaNavigationSettings.aspx"
           DisplayText="Quick Launch Links"
           Description="Manage Quick Launch Area Links"
           PermissionContext="CurrentSite"
           UseResourceFile="false"
           IsSiteRelative="true"
           ImageUrl="/_layouts/images/ActionsSettings.gif"
           ID="NavSettings"
           MenuGroupId="310"
           Sequence="101"
           UserRights="ManagePermissions|ManageWeb|ManageSubwebs|AddAndCustomizePages|ApplyThemeAndBorder|ManageAlerts|ManageLists|ViewUsageData"
           RequiredRightsMode="Any"/>
      <ConsoleNode NavigateUrl="PublishingImages/Forms/Thumbnails.aspx"
          DisplayText="Images"
          Description="Takes you to images library"
          PermissionContext="CurrentSite"
          UseResourceFile="false"
          IsSiteRelative="true"
          ImageUrl="/_layouts/images/ital.png"
          ID="BrowseImages"
          MenuGroupId="310"
          Sequence="102"
          UserRights="AddListItems|EditListItems"
          RequiredRightsMode="Any"/>
      <ConsoleNode NavigateUrl="Lists/faqs"
             DisplayText="Frequently Asked Querstions"
             Description="Manage FAQs displayed on the site."
             PermissionContext="CurrentSite"
             UseResourceFile="false"
             IsSiteRelative="true"
             ImageUrl="/_layouts/images/itgen.png"
             ID="BrowseFAQS"
             MenuGroupId="310"
             Sequence="103"
             UserRights="AddListItems|EditListItems"
             RequiredRightsMode="Any"/>
      <ConsoleNode NavigateUrl="Lists/Announcements"
         DisplayText="Announcements"
         Description="Manage Announcements Displayed on the site."
         PermissionContext="CurrentSite"
         UseResourceFile="false"
         IsSiteRelative="true"
         ImageUrl="/_layouts/images/announce.gif"
         ID="BrowseAnnouncements"
         MenuGroupId="310"
         Sequence="104"
         UserRights="AddListItems|EditListItems"
         RequiredRightsMode="Any"/>
      
      <ConsoleNode NavigateUrl="Pages/Forms/AllItems.aspx"
            DisplayText="Pages"
            Description="Takes you to pages library"
            PermissionContext="CurrentSite"
            UseResourceFile="false"
            IsSiteRelative="true"
            ImageUrl="/_layouts/images/itdl.png"
            ID="BrowsePages"
            MenuGroupId="310"
            Sequence="106"
            UserRights="AddListItems|EditListItems"
            RequiredRightsMode="Any"/>
 
 
    </ConsoleNode>
 
  </structure>
 
</Console>




Note that you can show/hide these links based on the permissions. I think this is a great way to make a difference in life of your SharePoint content authors.




Saturday, February 14, 2015

AngularJS: Building a Directive that Helps Choose a Date

This is my first AngularJS posts! I am so excited to get it started…

I just finished working on my third AngularJS project. I have used AngularJS in Provider Hosted, SharePoint Hosted and an IIS web application now. I must say that my AngularJS skills and code got better with every project. My last two projects included all best practices mentioned by John Papa: https://github.com/johnpapa/angularjs-styleguide

I wrote a whole bunch of directives and I will talk about them in future posts. I’ll start with very simple (and smart :)) directive. Basically, it helps my users to choose a date quickly. I used a date picker that comes with the UI bootstrap: http://angular-ui.github.io/bootstrap/ to get started and provided my custom directive on top of it which helps you select today’s with one click and move the date forward or backward by a configurable interval . See picture below:

SNAGHTML26a9d4b

You can use the directive like this:

<div st-date-provider st-config="{title:'Today', daysOffset:1}" st-model="vm.filterC.deliveryDate"></div>

The st-config represents a JavaScript object with following properties


















ParameterPurpose
titleRepresents the title that you want to display to represent “Today”
daysOffsetNumber of days you want to allow your users to move forward or backward.
minusTitleText you want to display for backward button. Optional.

For example, you can set daysOffset = 7 and set minusTitle to “- Week”
plusTitleText you want to display for forward button. Optional

Another way you can set it up is:

<div st-date-provider st-config="{title:'Today', daysOffset:7,  minusTitle:'Week', plusTitle:'Week'}" st-model="vm.filterC.deliveryDate"></div>



image


The dependencies for this directive is:


1. moment.js library for date manipulation


 


And finally the code for the Directive is:


app.controller('stDateProviderCtrl', ['$scope', 'common', function ($scope, common) {
       
$scope.provideDate = provideDate;
       
function provideDate(multiply) {
           
if (angular.isNumber($scope.stConfig.daysOffset)) {
               
if (!angular.isDate($scope.stModel)) {
                   
$scope.stModel = common.getToday();
                   
return;
               
}


               
$scope.stModel = moment($scope.stModel).add('days', $scope.stConfig.daysOffset * multiply).startOf('day').toDate();
           
}

       
}
   
}]);
   
app.directive('stDateProvider', ['config', function (config) {
       
var directive = {
           
restrict: 'EA',
           
controller: 'stDateProviderCtrl',
           
scope: {
               
stModel: "=",
               
stConfig: "="

           
},
           
template:   '<span>' + 
                            '<span class="pointer" ng-click="provideDate(-1)"><i class="fa fa-chevron-circle-left"></i></span>'
+
                            '<span class="pointer" ng-if="stConfig.minusTitle" ng-click="provideDate(-1)"> {{stConfig.minusTitle}}</span>'
+
                            '<span class="pointer" style="margin-left:10px;margin-right:10px" ng-click="provideDate(1)">{{stConfig.title}}</span>'
+
                            '<span class="pointer" ng-if="stConfig.plusTitle" ng-click="provideDate(1)">{{stConfig.plusTitle}} </span>'
+
                            '<span class="pointer" ng-click="provideDate(1)"><i class="fa fa-chevron-circle-right"></i></span>'
+
                        '</span>'
       
};
       
return directive;

   
}]);

 

Please leave your comments!


del.icio.us Tags: ,

Better SharePoint Content Authoring – Hide Sections that have no data

Presenting another trick for an improved SharePoint Content Authoring experience. As they say, picture says thousands words, I will present the problem and the solution in picture first:

Consider this page and the problem:

SNAGHTML16daf3

the solution:

SNAGHTML1a0e78

But what about this page in edit mode?

SNAGHTML223795

I know some of you may be thinking that this is so easy to do using JavaScript. Personally, I don’t like that approach because you will have to hide it based on element targeting and DOM manipulation. I think that’s not a robust solution because.. 1) it breaks when the element targeting fails (ID, or class name of DOM element changes 2) If you don’t implement it right, you will see “Description” first and then it  will magically disappear. That’s unacceptable for me.

Let’s talk about a better approach…

Well, my approach is it wrap the field heading (“Description” in this case)  into a Panel control which checks if the provided field has a value. If it has a value show the field heading panel, otherwise hide it.

It’s better because…..

1) It does not have two problems that I described above with JQuery approach

2) Unlike JavaScript approach, this solution is reusable. My favorite reason.

Here is my code for the panel look like:

public class FieldHeadingPanel : Panel, INamingContainer, IParserAccessor
   
{
       
private bool _shouldRender = false;
       
private bool _evaluated = false;

       
public string FieldGuid { get; set; }
       
public bool ShowInEditMode { get; set; }
       
protected override void AddParsedSubObject(object obj)
       
{
           
if (!_evaluated)
           
{
               
_evaluated = true;
               
this.ShouldRender();
           
}

           
if (_shouldRender)
           
{
               
base.AddParsedSubObject(obj);

           
}
       
}

       
private void ShouldRender()
       
{
           
_shouldRender = false;
           
try
           
{
               
if (!PublishingPage.IsPublishingPage(SPContext.Current.ListItem) || string.IsNullOrEmpty(FieldGuid))
               
{

                   
_shouldRender = false;
               
}
               
else
               
{
                   
// check if edit mode
                    if (SPContext.Current.FormContext.FormMode == Microsoft.SharePoint.WebControls.SPControlMode.Edit && ShowInEditMode)
                   
{
                       
_shouldRender = true;
                       
return;
                   
}
                   
SPField field = SPContext.Current.ListItem.Fields[new Guid(FieldGuid)];
                   
_shouldRender = !string.IsNullOrEmpty(field.GetFieldValueAsHtml(SPContext.Current.ListItem[new Guid(FieldGuid)]));


               
}
           
}
           
catch (Exception ex)
           
{
               
AppContext.FromSPContext().Logger.LogException(ex);
               
           
}
          
           

       
}
   
}

Basically, the panel will render content inside it only if the provided field has a value in it. I have tested it with Person, Text, Note and Summary Links field controls and it works. I’ll modify the code if it does not work for certain fields.

This class is part of my SharePoint Reusable Application framework which I use from one project to another.

You will put the following markup in your Page Layout.

   1:  <Framework:PageModePanel runat="server" ShowMode="Display" id="pnlDisplayOnly" >
   2:          <BEX:FieldHeadingPanel runat="server" FieldGuid="9da97a8a-1da5-4a77-98d3-4bc10456e700" ShowInEditMode="true">
   3:          <h2>
   4:              Description
   5:          </h2>
   6:     </Framework:FieldHeadingPanel>



Hope this helped you, feel free to leave comments!

Thursday, February 12, 2015

Better SharePoint Content Authoring – Page Creating Experience – Part 1

There is no doubt that Publishing is one of the greatest feature of SharePoint, but its unfortunate that authoring content (or should I say most) is not easy in SharePoint. Although Authors can be trained, I wish SharePoint provided a better authoring experience. I have worked in several publishing sites now and I try to implement several “authoring patterns” which makes the life of Authoring folks easier.

How about the most important thing for any Publishing Site – the experience of creating a page? Here is what I think about it!

SNAGHTMLbd811f 

So what’s wrong with this:

1. There is no way to choose what type of page you want to create.

2. There is no choice to choose layout of the page.

What that means is that, SharePoint will use the default content type and page layout to create the page. The author will then need to choose the right page layout from the ribbon. Seriously, I can understand the pain here, I wonder what was the SharePoint team thinking when making “New Page” functionality available from “Site Actions” menu. Sure, there are different ways to create a page which allows you to choose the page layout but then, why two different experiences?

Anyways, I like the Authors to create the new page from Site Actions Menu, so I generally prefer to replace that page with my own page. Essentially, I allow the users to choose the content type and optionally a page layout. The end result looks like this:

SNAGHTML10292b3

I will blog about the implementation of this pattern in Part 2 of this series. If you are curious now, it’s a two step process: 1) Create a custom Application Page with code-behind to create the page and redirect to edit page 2) Replace the “New Page” link in the “Site Actions” menu.

Friday, January 23, 2015

Single Page Apps in SharePoint – Lessons Learnt…

 

Recently I developed a single page SharePoint Hosted App with Angular JS and SharePoint Client Side Object Model. In this post, I am sharing the lessons learnt and some design suggestions to those who are interested in taking this route.

Feel free to leave your comments, if you have any questions about these ideas.

Get rid of the complex Url…

First, an Angular SPA’s routing mechanism requires the views to be loaded with {Your Main Page}#{view name} syntax. But when SharePoint launches the App, it provides several parameters which are required to call back to SharePoint services. Honestly, I hate those parameters so first time my app is launched, I store those into cookies and redirect to my SPA home page without those parameters. This makes the App URLs much more SPA friendly. It’s hard to put the code here because there are many moving pieces, but if you are interested in this approach, I recommend following course by Andrew:

Building SharePoint Apps as Single Page Apps with AngularJS

In addition to what Andrew’s approach, I also provided an deep linking ability in my app so that I can include links to item pages in emails being sent out from the App.

Store Configuration in Hidden List…

I prefer to store some absolutely critical but configurable items in a hidden configuration list which the support people (including me in some cases) can modify to slightly tweak the application behavior. Example of such configuration is given below:

SNAGHTML5a6c971

As you can see, I store the configuration about my Managed Metadata Controls, shared mail box, subject and body of my mailto links, whether or not the names are lync enabled and so on… I wrote the app to take these values instead of hard-coding them into the app. After all, I feel bad if the business has to come to me for every little change that they want to make so I always try to find a balance between amount of time it takes to make it configurable vs. the value it provides.

Store Data outside app-web (personal choice)…

You can store the data inside the app-web or outside of the app web, for instance, into the Host Web. I know this is a big topic and there are lot of conflicting opinions. Honestly, it drives me crazy that the data inside the app web is not searchable, that it gets deleted every time you re-install the app and that you lose rich functionality like Alerts. I don’t know about you, but these limitations are not acceptable to me. Why should I lose my data every time I redeploy the app into my development site? Am I supposed to enter the test data again (of course, you can write some script to insert the test data)?

Since I stored the data outside the app, I included links to this lists from my an administration page in my App (see below). Navigating through different things is not easy in SharePoint, I have heard a lot of complaints about it, so providing a one click access to all important things goes a long way, my users loved it.

SNAGHTML5b644b2

The question is how do these lists get created and configured? A truly independent app can provide a configuration page from which you can trigger JavaScript (REST/JSOM) scripts and have these lists created/configured. If you want to be more sophisticated, you can check for a flag on host web (Hint: Web Property) indicating a missing configuration and  then complete run the configuration script and turn on the flag so that it does not run again.

Considering tight deadlines though, I used custom PowerShell Scripts which uses a CSOM object model to create this lists and libraries. You may wonder why CSOM object model? It’s because I wanted it run them without accessing the servers’ powershell console, imagine O365!

Get hold of People Picker and Managed Metadata Controls…

There is a shortage of SPA friendly People Picker and the Managed Metadata pickers. I chose to get Javascript based implementation of these controls from /OfficeDev/Pnp (link below)

https://github.com/OfficeDev/PnP/tree/master/Components

And then, I wrote Angular JS Directives on top of these controls. If you want to know more about this, leave a comment, I can share some code pieces.

Centralize SharePoint Data Access…

While this is a no-brainer, I still see developers writing random JavaScript on scattered across the pages. Make a point to centralize all data access in logically separated repositories. Angular provides a great way to build Services, make a use of them and put your logic in repository services created in Angular.

Cache.. Cache.. Cache..

The data that is not frequently updated should be cached. I liked Angular Local Storage module (link below) which allowed me to store things like quick links, contacts, currently logged in user’s info and many other things into a local session store. It also allows you to use both HTML5 local storage and traditional cookie storage, use them as per your need.

https://github.com/grevory/angular-local-storage

This greatly improved the responsiveness of my pages. I also put refresh icon where such data was displayed on screen which allowed users to refresh the data from server on-demand.

Allow Business to change Email Templates

Emails are so common into a custom SharePoint Apps and yet, many developers hard code the email templates into the code. I believe email templates should be allowed to be changed by power users (let your power users feel powerful), so I built custom admin pages which allows my users to change the body of my email templates. On the email template edit screens, I provided them a list of special placeholders (example $$CURRENT_USER_ID$$, $$ITEM_TITLE$$, etc) which my application replaces with real values before sending out emails. That way, they had complete control over the subject and body of emails, the well-documented placeholders allowed them to make those template truly dynamic and useful.

How to display gentle reminders for incomplete SharePoint User Profile?

Ability to find people in organization and skills is one of my favorite feature of SharePoint. However, it's obvious that it's useful only when employees complete and keep their profiles up to date. At minimum, I see a great value in completing user's office location, department, contact information, manager information, profile picture, "About Me" and skills information. While some of these pieces of information (like department, manager, office location etc.) comes from Active Directory (or other any source for that matter), information such as "About Me", Profile Picture, "Ask me about" are best filled (and should beJ) out by users. But many employees don't take a few moments to provide this information and there is no way (as far as I know) to send them automatic reminders about their incomplete profile. Well, who likes to be overwhelmed with those reminders?

So I thought of giving them a gentle reminder when they visit a certain page, like an intranet home page. The reminder could be in the form of short-living (5 seconds), one liner message that appears on top right corner when one or more profile property is not filled out. Example below:

I created a Web Part which can be dropped on the page where you want the users to see these gentle reminders. The web part can be configured with:

  1. Tokenized Error Message. Allowed tokens are {0} which will be replaced with link to user profile and {1} which will be replaced with comma separated list of properties which are NOT filled out
  2. Comma separated list of profile properties that you want to check for completeness

Well, it can be enhanced with other things, but enough to get started right now:

  1. Start Date and End Date (imagine you are running a "profile complete" campaign)
  2. Time Frame (display messages only during mornings?)

Most of the code resides in JavaScript, a snippet of which is given below:

   1:  <SharePoint:ScriptLink ID="ScriptLink3" name="SP.Runtime.js" runat="server"
   2:   
   3:     ondemand="false" localizable="false" loadafterui="true" />  
   4:   
   5:  <SharePoint:ScriptLink ID="ScriptLink1" name="SP.js" runat="server"
   6:   
   7:      ondemand="false" localizable="false" loadafterui="true" />  
   8:   
   9:  <SharePoint:ScriptLink ID="ScriptLink2" name="SP.UserProfiles.js" runat="server"
  10:   
  11:      ondemand="false" localizable="false" loadafterui="true" />  
  12:   
  13:  <script type="text/javascript">  
  14:   
  15:  "use strict";  
  16:   
  17:      $(function () {  
  18:   
  19:  var personProperties;  
  20:   
  21:  var propertiesToCheck = "COMMA SEPARATED LIST OF PROPS";  
  22:   
  23:  var notifyMessage = "TOKENIZED ERROR MESSAGE GOES HERE";//
  24:   
  25:  // Ensure that the SP.UserProfiles.js file is loaded before the custom code runs.
  26:   
  27:          SP.SOD.executeOrDelayUntilScriptLoaded(getUserProperties, 'core.js');  
  28:   
  29:   
  30:  function getUserProperties() {  
  31:   
  32:   
  33:  // Get the current client context and PeopleManager instance.
  34:   
  35:  var clientContext = new SP.ClientContext.get_current();  
  36:   
  37:  var peopleManager = new SP.UserProfiles.PeopleManager(clientContext);  
  38:   
  39:   
  40:  // Get user properties for the target user.
  41:   
  42:  // To get the PersonProperties object for the current user, use the
  43:   
  44:  // getMyProperties method.
  45:   
  46:   
  47:              personProperties = peopleManager.getMyProperties();  
  48:   
  49:   
  50:  // Load the PersonProperties object and send the request.
  51:   
  52:              clientContext.load(personProperties);  
  53:   
  54:              clientContext.executeQueryAsync(onRequestSuccess, onRequestFail);  
  55:   
  56:          }  
  57:   
  58:  // This function runs if the executeQueryAsync call succeeds.
  59:   
  60:  function onRequestSuccess() {  
  61:   
  62:  var emptyProps = new Array();  
  63:   
  64:              $.each(propertiesToCheck, function (index, prop) {  
  65:   
  66:  if (SP.ScriptUtility.isNullOrEmptyString(personProperties.get_userProfileProperties()[prop.name])) {  
  67:   
  68:                      emptyProps.push(prop.title);  
  69:   
  70:                  }  
  71:   
  72:              });  
  73:   
  74:  if (emptyProps.length > 0) {  
  75:   
  76:                  SP.UI.Notify.addNotification(notifyMessage.replace("{0}", personProperties.get_userUrl()).replace("{1}", emptyProps.join(", ")), false)  
  77:   
  78:              }  
  79:   
  80:   
  81:   
  82:          }  
  83:   
  84:   
  85:  // This function runs if the executeQueryAsync call fails.
  86:   
  87:  function onRequestFail(sender, args) {  
  88:   
  89:   
  90:              SP.UI.Notify.addNotification("Error in Profile Notify Widget: " + args.get_message());  
  91:   
  92:   
  93:          }  
  94:   
  95:      });  
  96:   
  97:   
  98:  </script>  





  1.  


I think this is a simple but very powerful technique which may help our clients get the most out of SharePoint User Profiles. Hope this helps!

Monday, February 3, 2014

SharePoint 2013: Workflow Manager Installation and configuration issues…

Installing and configuring Workflow Manager 1.0 can be frustrating, if you do not follow the right steps. I installed Workflow Manager 1.0 and ran the default configuration successfully. After installation, I figured that I need to install following cumulative update in the order specified below.

SNAGHTML206d45

After installation of above CUs, I was able to create a WF Manager based site workflow and publish it successfully and I thought I cracked it!

But when I tried to browse the site workflow, the page threw an error. The ULS log suggested that there was an error in Data Access Layer. So I fired up the SQL Server Profiler and figured out some database entries were missing in following table. See the screen below for the table and the error text:

image 

Microsoft.Workflow.TestServiceHost.exe Error: 0 : System.IO.InvalidDataException: A required Workflow Configuration 'WorkflowServiceScopeSnapshotProcessBatchSize' is not present. Please add this configuration value.

I put the entry my self, don’t ask how I came up with values, I put random large numbers because I couldn’t wait to see my workflow working. I put about 4 entries in that table before and finally, the error changed to something else. To be precise, now the error was a missing parameter in a Stored Procedure and finally, I gave up thinking that there must be something terribly wrong here… I decided to uninstall it


Uninstalling Workflow Manager in Single Server environment:


http://social.msdn.microsoft.com/Forums/windowsazure/en-US/86162367-5d43-4f8e-81d4-12440d9dbcde/error-when-running-workflow-form-list-item-workflow-manager-sharepoint-2013?forum=wflmgr


Steve mentioned the steps to uninstall Workflow Manager from the dev environment and I was able follow the steps and uninstall it. For the sake of completeness, I provide a screenshot of un-installation steps below (thank to Steve)


image


followed by successful installation…


After uninstalling, I tried following video tutorial to install and configure Workflow Manager Successfully!


Please consider every single step in this tutorial as important, if you want to see the workflow manager up and running in your dev environment.


http://msdn.microsoft.com/en-US/library/dn201724