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!



5 comments:

  1. Hi Thanks for sharing the article.

    Could you please share the project code base?

    Thanks in advance.

    ReplyDelete
    Replies
    1. Hi Anuja,

      So bloody thorough! Ah! So happy and blissed out! I feel redeemed by reading out #topic. Keep up the good work!

      I’m a Spanish student of electronics and I join in computer science programming arduino. Later I study something of java by myself and now I want to use Linux and learn bash.
      It consists of delaying block allocation until the data is going to be written to the disk, unlike some other file systems, which may allocate the necessary blocks before that step.
      Now I am using ubuntu GNOME for 2 weeks ago, but I don’t really like it(WHY DONT HAVE DESKTOP??), so I want to try other distros. I use the computer for use Firefox, programming arduino and java, freeoffice, play League of Legends and for some programs of electronics (I still have problems with wine, but I hope I can fix it).
      But great job man, do keep posted with the new updates.

      Kind Regards,
      Kevin

      Delete
  2. Poorly written code with heaps of errors

    ReplyDelete
  3. Thank's, should I need to use request executor ? how can I make CORS request ?

    ReplyDelete