Monday, May 11, 2015

Nintex 2013 Workflow Action: "Commit pending changes"


Commit pending changes is not well known action of Nintex, However very useful when you have scenario like I faced. 

I had a requirement like first to update the list item, and immediately need to take the count of updated items.

I spent quite amount of time to achieve this, First tried by doing simple workflow

1. Update list item

2. Query list (query the same list which updated)




Here after running this, started getting uncertain results. Sometime it works and fails.

Started applying workarounds to that

Tried to apply “Pause for” action in between, like below image.







Result was again uncertain, it pause was getting struck, was not resuming the workflow even after specified time.

I goggled and found many issues are reported for the same issue,

https://community.nintex.com/thread/2979

Third workaround was to add some loop which can add some pause before query, somehow even after safe loop, result was uncertain.


Finally found the impressive feature of Nintex, which has saved from all these hassles.

“Commit Pending Changes” – How exactly it is working ?

SharePoint workflow not starting the workload immediately, that means if I am adding update list item action, so its not sure that it will go and update the item at the same time, SharePoint doing batch job for it. As a result we are getting wrong result while querying it immediately.

As well other important thing to remember that The SharePoint workflow engine doesn't necessarily commit batched operations in the order they are displayed on the designer

Commit Pending Changes will wait/pause the workflow or say it waits until the workflow commits all other batch job started then after will move further.





This is very useful when you want workflow engine to follow the order you specified in workflow designer, or where the scenario where dependency on next to next actions.

Impressed with this Workflow action, Hope this piece of information will save your time!








Wednesday, April 29, 2015

Nintex 2013: Show display name in email notification for person field


In Nintex 2013, requirement was to show the user's "Display name" in email. looks a very simple issue by looking at the definition. However, to achieve this in workflow have to choose proper way to implement.
Two options are available to get this.

1. Using "Call web service" action
2. Using " Query user profile"


To move ahead with first step, we need to use Regular expression, web service and query xml  as described here in https://community.nintex.com/thread/1353 lots of configuration require to make this work. as well to store credentials of admin to call the web service, need to make the constant in Nintex management.

So In my implementation, I choose second option, fairly simple to use.

Steps to use "Query user profile" action to get display name:

1. Add "Query user Profile" from actions tab




 2. Double click to configure it, and select the person field , here selecting "Created By" field of the custom list




3. Expand the properties to retrieve, and select "Name" , click on Add



4. Select the variable name where you want to save query result, here using string variable named "CreatedByDisplayName"

5. Now, add the email notification action and from insert reference add the workflow variable.



6. That's all we are done.

No configuration/web service call/credentials require to store.

Hope this will help you!


Sunday, March 29, 2015

SharePoint 2013 List CRUD operation using Angular and REST API

This article explains how to perform the CRUD operation on a SharePoint List using Angular JavaScript and the REST API.

Create an empty SharePoint 2013 solution, In that add the module of Pages, JS & CSS.

In pages module add new item, Test.aspx.
In JS module, add new Javascript file with name Controller.js , CommonServices.js & SharePointListModule.js
Other Javascripts needs to add:
1. angular.min.js
2. anugular.min.js.map

Project structure will look something like below:





In SharePointListModule.js will create a App of angular, Paste the below code.

 //App creation  
 var app = angular.module("SharePointAngApp", [])  


For doing a CRUD operation with SharePoint List, will create an Angular Service to make a SharePoint REST API calls,



 app.service('testService', ['$http', '$q', function ($http, $q) {//common services}]);

This testService having dependency of $http & $q
Information related to $http & $q  https://docs.angularjs.org/api/ng/service/$http , https://docs.angularjs.org/api/ng/service/$q

Following code showing how to get, add, delete & update tasks from specified SharePoint list,

 //Common service for SharePoint CRUD operations  
 app.service('testService', ['$http', '$q', function ($http, $q) {  
   var formDigest = $('#__REQUESTDIGEST').val();  
   //Define the http headers  
   $http.defaults.headers.common.Accept = "application/json;odata=verbose";  
   $http.defaults.headers.post['Content-Type'] = 'application/json;odata=verbose';  
   $http.defaults.headers.post['X-Requested-With'] = 'XMLHttpRequest';  
   $http.defaults.headers.post['If-Match'] = "*";  
   $http.defaults.headers.post['X-RequestDigest'] = formDigest;  
   //Get the tasks  
   this.getTasks = function (listTitle) {  
     var dfd = $q.defer();  
     $http.defaults.headers.post['X-HTTP-Method'] = ""  
     var query = "?$select=Title,Status,Priority,ID,Body";  
     var restUrl = _spPageContextInfo.webAbsoluteUrl + "/_api/web/lists/getbytitle('" + listTitle + "')/items" + query;  
     $http.get(restUrl).success(function (data) {  
       dfd.resolve(data.d.results);  
     }).error(function (data) {  
       dfd.reject("error getting tasks");  
     });  
     return dfd.promise;  
   }  
   //Create a task , for now static text, we can pass input fields to add apropriate value   
   this.addTask = function (listTitle) {  
     var dfd = $q.defer();  
     $http.defaults.headers.post['X-HTTP-Method'] = "";  
     var restUrl = _spPageContextInfo.webAbsoluteUrl + "/_api/web/lists/getbytitle('" + listTitle + "')/items";  
     $http.post(restUrl, {  
       __metadata: {  
         type: "SP.List"  
       },  
       Title: "New Tasks"  
     }).success(function (data) {  
       //resolve the new data  
       dfd.resolve(data.d);  
     }).error(function (data) {  
       dfd.reject("failed to add task");  
     });  
     return dfd.promise;  
   }  
   //Update a task  
   this.updateTask = function (listTitle, task) {  
     var dfd = $q.defer();  
     $http.defaults.headers.post['X-HTTP-Method'] = "MERGE";  
     var restUrl = _spPageContextInfo.webAbsoluteUrl + "/_api/web/lists/getbytitle('" + listTitle + "')/items(" + task.ID + ")";  
     $http.post(restUrl, {  
       __metadata: {  
         type: "SP.List"  
       },  
       Title: task.Title  
     }).success(function (data) {  
       //resolve something  
       dfd.resolve(true);  
     }).error(function (data) {  
       dfd.reject("error updating task");  
     });  
     return dfd.promise;  
   }  
   //Delete a task  
   this.deleteTask = function (listTitle, task) {  
     var dfd = $q.defer();  
     $http.defaults.headers.post['X-HTTP-Method'] = "DELETE";  
     var restUrl = _spPageContextInfo.webAbsoluteUrl + "/_api/web/lists/getbytitle('" + listTitle + "')/items(" + task.ID + ")";  
     $http.post(restUrl)  
       .success(function (data) {  
         //resolve something  
         dfd.resolve(true);  
       }).error(function (data) {  
         dfd.reject("error deleting task");  
       });  
     return dfd.promise;  
   }  
 }]);  

Now, its time to consume all these services by creating an angular controller.
 app.controller('spTasksController', function ($scope, $http, testService) {  
 //functions to consume the service methods});  

Full code to consume the above service methods is below, paste it in Controller.js
 //Controller to featch data from Tasks list  
 app.controller('spTasksController', function ($scope, $http, testService) {  
   $scope.editing = false;  
   //example populating tasks  
   testService.getTasks("Workflow Tasks").then(function (result) {  
     $scope.tasks = result;  
   });  
   //example to add task  
   $scope.addTask = function (listName) {  
     testService.addTask(listName).then(function (result) {  
       //update the scope with new task created  
       $scope.tasks.push(result);  
       $scope.task = {};  
       $scope.$apply();  
     });  
   };  
   // example to update task  
   $scope.updateTask = function (listName, item) {  
     testService.updateTask(listName, item).then(function (result) {  
       $scope.editing = $scope.tasks.indexOf(item);  
       $scope.$apply();  
     });  
   };  
   $scope.editItem = function (index) {  
     $scope.editing = $scope.tasks.indexOf(index);  
   }  
   $scope.saveField = function (index) {  
     if ($scope.editing !== false) {  
       $scope.editing = false;  
     }  
   };  
   // example to delete task  
   $scope.deleteTask = function (listName, item,index) {  
     testService.deleteTask(listName, item).then(function (result) {  
       $scope.tasks.splice(index, 1);  
       $scope.$apply();  
     });  
   };  
 });  

Now, its time to bind the view. add the below piece of code in test.aspx
 <%@ Page Language="C#" MasterPageFile="~masterurl/default.master" Inherits="Microsoft.SharePoint.WebPartPages.WebPartPage,Microsoft.SharePoint,Version=15.0.0.0,Culture=neutral,PublicKeyToken=71e9bce111e9429c" meta:progid="SharePoint.WebPartPage.Document" meta:webpartpageexpansion="full" %>  
 <%@ Register TagPrefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>  
 <%@ Register TagPrefix="Utilities" Namespace="Microsoft.SharePoint.Utilities" Assembly="Microsoft.SharePoint, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>  
 <%@ Import Namespace="Microsoft.SharePoint" %>  
 <%@ Assembly Name="Microsoft.Web.CommandUI, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>  
 <%@ Register TagPrefix="WebPartPages" Namespace="Microsoft.SharePoint.WebPartPages" Assembly="Microsoft.SharePoint, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>  
 <asp:Content ContentPlaceHolderID="PlaceHolderPageTitle" runat="server">  
   <SharePoint:EncodedLiteral runat="server" Text="<%$Resources:wss,multipages_homelink_text%>" EncodeMethod="HtmlEncode" />  
   -   
      <SharePoint:ProjectProperty Property="Title" runat="server" />  
 </asp:Content>  
 <asp:Content ContentPlaceHolderID="PlaceHolderPageImage" runat="server">  
   <img src="/_layouts/15/images/blank.gif?rev=23" width='1' height='1' alt="" />  
 </asp:Content>  
 <asp:Content ContentPlaceHolderID="PlaceHolderPageTitleInTitleArea" runat="server">  
   <label class="ms-hidden">  
     <SharePoint:ProjectProperty Property="Title" runat="server" />  
   </label>  
 </asp:Content>  
 <asp:Content ContentPlaceHolderID="PlaceHolderTitleAreaClass" runat="server">  
   <SharePoint:UIVersionedContent runat="server" UIVersion="<=3">  
     <contenttemplate>  
           <style type="text/css">  
             td.ms-titleareaframe, .ms-pagetitleareaframe {  
               height: 10px;  
             }  
             div.ms-titleareaframe {  
               height: 100%;  
             }  
             .ms-pagetitleareaframe table {  
               background: none;  
               height: 10px;  
             }  
             .ms-viewheadertr ms-vhltr {  
               background: rgb(246,248,249); /* Old browsers */  
               /* IE9 SVG, needs conditional override of 'filter' to 'none' */  
               background: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/Pgo8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgdmlld0JveD0iMCAwIDEgMSIgcHJlc2VydmVBc3BlY3RSYXRpbz0ibm9uZSI+CiAgPGxpbmVhckdyYWRpZW50IGlkPSJncmFkLXVjZ2ctZ2VuZXJhdGVkIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjAlIiB5MT0iMCUiIHgyPSIwJSIgeTI9IjEwMCUiPgogICAgPHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2Y2ZjhmOSIgc3RvcC1vcGFjaXR5PSIxIi8+CiAgICA8c3RvcCBvZmZzZXQ9IjUwJSIgc3RvcC1jb2xvcj0iI2U1ZWJlZSIgc3RvcC1vcGFjaXR5PSIxIi8+CiAgICA8c3RvcCBvZmZzZXQ9IjUxJSIgc3RvcC1jb2xvcj0iI2Q3ZGVlMyIgc3RvcC1vcGFjaXR5PSIxIi8+CiAgICA8c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9IiNmNWY3ZjkiIHN0b3Atb3BhY2l0eT0iMSIvPgogIDwvbGluZWFyR3JhZGllbnQ+CiAgPHJlY3QgeD0iMCIgeT0iMCIgd2lkdGg9IjEiIGhlaWdodD0iMSIgZmlsbD0idXJsKCNncmFkLXVjZ2ctZ2VuZXJhdGVkKSIgLz4KPC9zdmc+);  
               background: -moz-linear-gradient(top, rgba(246,248,249,1) 0%, rgba(229,235,238,1) 50%, rgba(215,222,227,1) 51%, rgba(245,247,249,1) 100%); /* FF3.6+ */  
               background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(246,248,249,1)), color-stop(50%,rgba(229,235,238,1)), color-stop(51%,rgba(215,222,227,1)), color-stop(100%,rgba(245,247,249,1))); /* Chrome,Safari4+ */  
               background: -webkit-linear-gradient(top, rgba(246,248,249,1) 0%,rgba(229,235,238,1) 50%,rgba(215,222,227,1) 51%,rgba(245,247,249,1) 100%); /* Chrome10+,Safari5.1+ */  
               background: -o-linear-gradient(top, rgba(246,248,249,1) 0%,rgba(229,235,238,1) 50%,rgba(215,222,227,1) 51%,rgba(245,247,249,1) 100%); /* Opera 11.10+ */  
               background: -ms-linear-gradient(top, rgba(246,248,249,1) 0%,rgba(229,235,238,1) 50%,rgba(215,222,227,1) 51%,rgba(245,247,249,1) 100%); /* IE10+ */  
               background: linear-gradient(to bottom, rgba(246,248,249,1) 0%,rgba(229,235,238,1) 50%,rgba(215,222,227,1) 51%,rgba(245,247,249,1) 100%); /* W3C */  
               filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#f6f8f9', endColorstr='#f5f7f9',GradientType=0 ); /* IE6-8 */  
             }  
             .ms-vh2 {  
               font-size: 13px;  
               font-weight: 500;  
             }  
           </style> <!--[if gte IE 9]>  
  <style type="text/css">  
   .gradient {  
     filter: none;  
   }  
  </style> <![endif]-->  
     </contenttemplate>  
   </SharePoint:UIVersionedContent>  
 </asp:Content>  
 <asp:Content ContentPlaceHolderID="PlaceHolderAdditionalPageHead" runat="server">  
   <meta name="CollaborationServer" content="SharePoint Team Web Site" />  
   <SharePoint:StyleBlock runat="server">  
     .s4-nothome {  
      display:none;  
 }  
   </SharePoint:StyleBlock>  
   <script type="text/javascript" src="../JS/jquery.min.js">//<![CDATA[//]]></script>  
   <script type="text/javascript" src="../JS/angular.min.js"></script>  
   <script type="text/javascript" src="../JS/SharePointListModule.js"></script>  
   <script type="text/javascript" src="../JS/Controllers.js"></script>  
   <script type="text/javascript" src="../JS/Common.Services.js"></script>  
 </asp:Content>  
 <asp:Content ContentPlaceHolderID="PlaceHolderSearchArea" runat="server">  
   <SharePoint:DelegateControl runat="server"  
     ControlId="SmallSearchInputBox" />  
 </asp:Content>  
 <asp:Content ContentPlaceHolderID="PlaceHolderLeftActions" runat="server" />  
 <asp:Content ContentPlaceHolderID="PlaceHolderLeftNavBar" runat="server">  
   <div class="page">  
     <div class="row">  
       <div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">  
         <div class="navHolder" id="fisCustomNav">  
           <ul class="nav-site" id="fisNavroot">  
           </ul>  
           <ul class="nav-device-main">  
             <li></li>  
           </ul>  
         </div>  
       </div>  
     </div>  
   </div>  
 </asp:Content>  
 <asp:Content ContentPlaceHolderID="PlaceHolderPageDescription" runat="server">  
 </asp:Content>  
 <asp:Content ContentPlaceHolderID="PlaceHolderBodyAreaClass" runat="server">  
   <SharePoint:StyleBlock runat="server">  
     .ms-bodyareaframe {  
      padding: 0px;  
 }  
   </SharePoint:StyleBlock>  
 </asp:Content>  
 <asp:Content ContentPlaceHolderID="PlaceHolderMain" runat="server">  
   <form class="form-horizontal" name="taskForm" novalidate>  
     <div class="page" ng-app="SharePointAngApp">  
       <div class="row">  
         <div class="col-lg-12 col-md-12 col-sm-12 col-xs-12 replacement-btn-list">  
           <h3 class="text-bold text-color-dark ">List of Tasks:</h3>  
         </div>  
       </div>  
       <div class="row">  
         <div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">  
           <div class="table-responsive replacement-list" ng-controller="spTasksController">  
             <table class="table">  
               <tr>  
                 <th>Tasks</th>  
                 <th>Status</th>  
                 <th>Prority</th>  
                 <th>Update</th>  
               </tr>  
               <tbody>  
                 <tr ng-repeat="task in tasks">  
                   <td><span ng-hide="editMode">{{task.Title}}</span><input type="text" ng-show="editMode" ng-model="task.Title" /></td>  
                   <td>  
                     <div>{{task.Status}}</div>  
                   </td>  
                   <td>  
                     <div>{{task.Priority}}</div>  
                   </td>  
                   <td><span>  
                     <input type="button" ng-hide="editMode" ng-click="editMode = true; editItem(task) " value="Edit" /></span>  
                     <span>  
                       <input type="button" ng-show="editMode" ng-click="editMode = false; updateTask('Workflow Tasks',task)" value="Save"></span></td>  
                   <td>  
                     <div>  
                       <input type="button" ng-click="deleteTask('Workflow Tasks',task,$index)" value="Delete" /></div>  
                   </td>  
                 </tr>  
                 <tr>  
                   <td>  
                     <input type="button" ng-click="addTask('Workflow Tasks')" value="Add" /></td>  
                 </tr>  
               </tbody>  
             </table>  
           </div>  
         </div>  
       </div>  
     </div>  
   </form>  
 </asp:Content>  


If you noticed in parent div i have added :  <div class="page" ng-app="SharePointAngApp">
and in child div added ng-controller

  <div class="table-responsive replacement-list" ng-controller="spTasksController">

Once we add the controller, we can access all the scope variables in child elements.

Output will looks like below:




Hope this will help you to build SharePoint applications using Angualr.
Happy learning!



Thursday, February 12, 2015

“Response no longer required” error in nintex workflow

Error: “Response no longer required”



With Nintex , requirement was to create a workflow which passes through multiple approval process.
Workflow having Felxi tasks with customized task forms.
After publishing everything works well in development environment.
Time was to deploy the workflow at test environment, deployed workflow with manual import/export published it no error reported, its published successfully.

When first approval task starts, it started giving email notification something like below:

"Response no longer required"
A task regarding this item no longer requires your response:
ItemName
Click here to view the workflow status

First was not sure, from where this notification coming up from workflow steps, as I haven't set any message like this in workflow, No error reported in workflow tasks, or in workflow history.
Invested almost a whole day to find the exact cause.
Finally found in flexi task configuration window under "Not require Notification"




Now I tried to find in Nintex community regrading what exactly "Not require Notification" is doing.


Not Require Notification will get sent in following scenario:

Sent when the user no longer needs to respond to the task. This can occur when:
•A task is assigned to multiple users but only one is required to respond.
•The workflow is terminated prior to the task being processed.
•An error occurs in the workflow at run time. 

Source : https://community.nintex.com/community/build-your-own/blog/2014/12/31/nintex-approval-action-comparision


At last, I got some hint from last point "
An error occurs in the workflow at run time" some run time error, my search headed towards what could be the error, created sample one step workflow with flexi task which ran well on test environment.
Means no configuration/installation issue of Nintex. only difference was customized task form.
Finally culprit found, its a customized task forms.


We need to manually import and export every flexi task's customized task forms to make it up and running.
After importing all the customized task form manually this "“Response no longer required” error is resolved.


Tuesday, January 13, 2015

Nintex Forms: Cannot open designer "An error occurred during an ajax call."

Recently I have installed the Nintex Workflow Forms and configured it.
I have activated the web application feature and site collection features and all, however when I try to customize a list forms using Nintex designer I started getting error message that says

 "An error occurred during an ajax call."

When I did IISReset, it seemed to have fixed it.

hope this small piece of information will save your time!

Monday, January 12, 2015

Nintex Workflow 2013, unable to publish workflow : soap:serverserver not able to process request object reference not set



Being a newbie with Nintex, I have installed and configured Nintex Workflow and Nintex Workflow Forms. easy to follow all the steps described on Nintex support page.

After doing all the necessary configuration, tried to create very simple one step workflow to test.

There I faced weird issue, I was able to save the workflow, however while publishing it was giving below error.


Error was very generic, wasted couple of hours to find the exact cause as in workflow logic there was nothing. simple one step workflow it was.

So pointer was going to configuration only, something in configuration is not proper which throwing this error.

Solution :
1. Go to Central Admin
2. Click on Nintex Worklfow Management
3. Database setup -> Add Content Database ( I already configured it, still created new)
4. Configuration Database click Edit
5. Update the connection string with newly created database.
6. IISRESET

And we are good to go.

Hope this will save your time!






Tuesday, August 26, 2014

How to query SP2013 Managed metadata term store with JSOM

This post will show how to read the Taxonomy Term Store with JSOM.
Taxonomy Term store can be accessed in a SharePoint App, with permission scope of Taxonomy.
To be able to use the JSOM API for Taxonomy you need to reference SP.Taxonomy.js found in _layouts/15/ the best way to reference this would be in the document ready.

 $(document).ready(function () {  
 var scripRef = _spPageContextInfo.webServerRelativeUrl + "/_layouts/15/";  
 $.getScript(scripRef+ "SP.Runtime.js",  
 function () {  
 $.getScript(scripRef+ "SP.js", function () {  
 $.getScript(scripRef+ "SP.Taxonomy.js", function () {  
 context = SP.ClientContext.get_current();  
 //Call your code here.  
 getTermStores();  
 });});});})  

Read from Taxonomy

 function getTermStores() {  
   session = SP.Taxonomy.TaxonomySession.getTaxonomySession(context);  
   termStores = session.get_termStores();  
   context.load(session);  
   context.load(termStores);  
   context.executeQueryAsync(  
 function(){  
 termStoreEnum = termStores.getEnumerator();  
    var termStores = "TermStores: /n";  
    while (termStoreEnum.moveNext()) {  
     var currentTermStore = termStoreEnum.get_current();  
     var termStoreID = currentTermStore.get_id();  
     var termStoreName = currentTermStore.get_name();  
 termStores += "Name: " + termStoreName + " ID:" + termStoreID;  
 }  
 }, function(){  
 //failure loading.  
 });  
 }