Sunday, May 3, 2015

Angular JS on SharePoint 2013

Now-a-days Angular JS has become popular and certainly there is reason for that. If you search in Internet you can find really good examples of Angular JS.
Since SharePoint is my bread and butter for some time, I thought of using it on SharePoint, I have found few examples in the internet but none of them was working correctly for me. I had to make few modifications and it worked on SharePoint. The main reason it was incorrect angular tags. So I’ll explain this as a first thing.

Relation between Angular JS and HTML page

I’ll not give you details of how Angular JS works; I assume you must have some sort of idea about it. But I found below relation was working in all of my SharePoint pages. Note the DIV tags ng-app=”myApp”, ng-controller=”ConactController” in the HTML side and their relation with the JavaScript in the below diagram

Preparation

Before you start development you need to prepare the environment, i.e. getting the necessary JS files I have used Angular JS, JQuery and Bootstrap CSS for the examples, but bootstrap is not essential.
I have downloaded most of the files and placed them inside “SiteAssets” library; you may want to put it in a more orderly folder structure.

Sometimes I have used CDN also e.g.: http://getbootstrap.com/2.3.2/assets/css/bootstrap.css . Similarly you can use Google CDN for jQuery and Angular JS. You can download Angular JS from https://angularjs.org/ and bootstrap from here: http://getbootstrap.com/
Below is a screenshot of the Site Assets library in my SharePoint.




Page1-Page6 JS files are specific for the SharePoint examples shown in Page1 to 6 (see them in thleft navigation). All other files are common.

For some reason the JS was not working until I activated SharePoint Publishing feature on the site.I’m not sure if that’s really needed though. But for a better looking site you might want to use publishing pages rather than wiki pages.


I have added all HTML script in a script editor on each page.
Create a page named Page1„ Edit the page„³Add a script editor in the page


Once you add the script editor, go to Edit Snippet and paste the HTML there. A sample screenshot is given below.


I have created a custom list name Employee which has been used in many examples below. Notice the EmpImage column. The Images are stored in the Images library, this library should be already created when you activate SharePoint publishing feature. You can use some other library too.


I have created a custom list name Employee which has been used in many examples below. Notice the EmpImage column. The Images are stored in the Images library, this library should be already created when you activate SharePoint publishing feature. You can use some other library too.



Page1 Example

Now to the actual examples, this is a really simple example, just to give you an idea. You enter some data and a popup shows your answer.


I have created a page name Page1.aspx, added a script editor and pasted below HTML into the script editor. Same technique used for all the examples.

HTML
This few lines of HTML render the questions and popup answers too.
<script src="http://code.jquery.com/ui/1.10.3/jquery-ui.min.js"></script>
<script src="http://SERVERNAME/sites/arindamc/Subsite1/SiteAssets/angular.min.js"></script>
<script src="http://SERVERNAME/sites/arindamc/Subsite1/SiteAssets/Page1.js"></script>
<link rel="stylesheet" type="text/css" href="http://getbootstrap.com/2.3.2/assets/css/bootstrap.css">
<style type="text/css"> .auto-style1 { color: #339933;}</style>
<div ng-app="myApp">
<div ng-controller="ContactController">
<div id="PutYourContentInside">
<ol>
<li ng-repeat="question in questions">
<span>{{question.text}}</span>
<br />
<input type="text" ng-model="question.answer"/>
</li>
</ol>
<input type="submit" value="Submit" ng-click="addAnswers($event)"/>
</div>
</div></div> 


JavaScript:
The JavaScript used here is saved as Page1.js in the site asserts library. So this script just passes the questions to the 'ContactController'module and gets back the answers. This JavaScript was saved as Page1.js in the Site Assets library.
var myApp = angular.module('myApp', []);
var uid = (function () {
var id = 1;
return function () {
if (arguments[0] === 0)
id = 1;
return id++; }})();
var siteUrl = GetSiteUrl();
myApp.controller('ContactController', function ($scope) {
$scope.questions = [
{ text: 'How would you like your eggs done?', answer: "" },
{ text: 'What kind of bread would you like?', answer: "" },
{ text: 'What kind of drink would you like?', answer: "" } ];
$scope.addAnswers = function ($event) {
$event.preventDefault();
var ans = "";
for (i in $scope.questions) {
ans = ans + $scope.questions[i].answer + ", ";
$scope.questions[i].answer = "";
}
$scope.$apply();
alert("Thank you for your answers : " + ans);
}});
function GetSiteUrl() {
var urlParts = document.location.href.split("/");
return urlParts[0] + "//" + urlParts[2] + "/" + urlParts[3] + "/" + urlParts[4];


Page2 Example
This is a simple data grid operation.
HTML
<script src="http://code.jquery.com/ui/1.10.3/jquery-ui.min.js"></script>
<script src="http://SERVERNAME/sites/arindamc/Subsite1/SiteAssets/angular.min.js"></script>
<script src="http://SERVERNAME/sites/arindamc/Subsite1/SiteAssets/Page2.js"></script>
<link rel="stylesheet" type="text/css" href="http://SERVERNAME/sites/arindamc/Subsite1/SiteAssets/bootstrap.css">
<style type="text/css">
.auto-style1 { color: #339933; }
</style>
<div ng-app="myApp">
<div ng-controller="ContactController">
<label><span class="auto-style1"><strong>Enter data.</strong></span><br />
<br /> Name</label>
<input type="text" name="name" ng-model="newcontact.name"/>
<label>Email</label>
<input type="text" name="email" ng-model="newcontact.email"/>
<label>Phone</label>
<input type="text" name="phone" ng-model="newcontact.phone"/>
<br/>
<input type="hidden" ng-model="newcontact.id"/>
<br>
<input type="button" value="Save It" ng-click="saveContact()" class="btn btn-primary" /><br />
&nbsp;<table class="table table-striped table-bordered">
<thead>
<tr>
<th>Name</th>
<th>Email</th>
<th>Phone</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="contact in contacts">
<td>{{ contact.name }}</td>
<td>{{ contact.email }}</td>
<td>{{ contact.phone }}</td>
<td>
<a href="javascript:void(0)" ng-click="edit(contact.id)">edit</a> |
<a href="javascript:void(0)" ng-click="delete(contact.id)">delete</a>
</td>
</tr>
</tbody>
</table></div></div>
JavaScript:
var myApp = angular.module('myApp', []);
var uid = (function () {
var id = 1;
return function () {
if (arguments[0] === 0)
id = 1;
return id++;
}
})();
myApp.controller('ContactController', function ($scope) {
$scope.contacts = [{ id: 0, 'name': 'Arindam', 'email': 'Arindam@myemail.com', 'phone': '123-2343-44' }];
$scope.saveContact = function () {
if ($scope.newcontact.id == null) {
$scope.newcontact.id = uid();
$scope.contacts.push($scope.newcontact);
}
else {
for (i in $scope.contacts) {
if ($scope.contacts[i].id == $scope.newcontact.id) {
$scope.contacts[i] = $scope.newcontact;
}
}
}
$scope.newcontact = {};
}
$scope.delete = function (id) {
for (i in $scope.contacts) {
if ($scope.contacts[i].id == id) {
$scope.contacts.splice(i, 1); $scope.newcontact = {};
}
}
}


$scope.edit = function (id) {
for (i in $scope.contacts) {
if ($scope.contacts[i].id == id) {
$scope.newcontact = angular.copy($scope.contacts[i]);
}
}
}
});

Page3 Example

In this example I have done some real SharePoint operations, I have created a custom list named Employee and then read the list items in a tabular mode. Now there is more than one way of doing list operation in client side coding. Here I have used the list service but this is not just normal list service call, I have used Angula JS’s capability of calling a http method and parse data. Latter I have another example with async client calling.
Check how small amount of coding was required for this. You must have noticed that bootstrap CSS was used for the table formatting. 

<script src="http://SERVERNAME/sites/arindamc/Subsite1/SiteAssets/jquery-2.1.3.min.js"></script>
<script src="http://SERVERNAME/sites/arindamc/Subsite1/SiteAssets/angular.min.js"></script>
<script src="http://SERVERNAME/sites/arindamc/Subsite1/SiteAssets/bootstrap.min.js"></script>
<script src="http://SERVERNAME/sites/arindamc/Subsite1/SiteAssets/Page3.js"></script>
<link rel="stylesheet" type="text/css" href="http://SERVERNAME/sites/arindamc/Subsite1/SiteAssets/bootstrap.min.css">
<style type="text/css">
.auto-style1 {
color: #339933;
}
</style>
<div ng-app="SharePointAngApp" class="row">
<div ng-controller="spCustomerController" class="span10">
<table class="table table-striped table-bordered">
<tr>
<th>Employee ID</th>
<th>Employee Name</th>
<th>City</th>
</tr>
<tr ng-repeat="customer in customers">
<td>{{customer.Id}}</td>
<td>{{customer.EmpName}}</td>
<td>{{customer.EmpCity}}</td>
</tr>
</table>
</div>
</div>
JavaScript:
var webSiteURL = GetSiteUrl();
//alert(webSiteURL);
var myAngApp = angular.module('SharePointAngApp', []);
myAngApp.controller('spCustomerController', function ($scope, $http) {
$http({
method: 'GET',
url: webSiteURL + "/_api/web/lists/getByTitle('Employee')/items?$select=Id,EmpName,EmpCity",
headers: { "Accept": "application/json;odata=verbose" }
}).success(function (d, s, h, c) {
$scope.customers = d.d.results;
});
});
function GetSiteUrl() {
var urlParts = document.location.href.split("/");
return urlParts[0] + "//" + urlParts[2] + "/" + urlParts[3] + "/" + urlParts[4] + "/" + urlParts[5];
}



Page4 Example
In this example I’m adding a new item to the Employee list, client site async call have been used for this.
HTML
<script src="http://code.jquery.com/ui/1.10.3/jquery-ui.min.js"></script>
<script src="http://SERVERNAME/sites/arindamc/Subsite1/SiteAssets/angular.min.js"></script>
<script src="http://SERVERNAME/sites/arindamc/Subsite1/SiteAssets/Page4.js"></script>
<link rel="stylesheet" type="text/css" href="http://SERVERNAME/sites/arindamc/Subsite1/SiteAssets/bootstrap.css">
<style type="text/css">
.auto-style1 {
color: #339933;
}
</style>
<div ng-app="myApp">
<div ng-controller="ContactController">
<div id="PutYourContentInside">
<ol>
<li ng-repeat="question in questions">
<span>{{question.text}}</span>
<br />
<input type="text" ng-model="question.answer"/>
</li>
</ol>
<input type="submit" value="Submit" ng-click="addAnswers($event)"/>
</div>
</div>
</div>

JavaScript:
var myApp = angular.module('myApp', []);
var uid = (function () {
var id = 1;
return function () {
if (arguments[0] === 0)
id = 1;
return id++;
}
})();
var siteUrl = GetSiteUrl();
//alert(siteUrl);
myApp.controller('ContactController', function ($scope) {
$scope.questions = [
{ text: 'What is the new Employee name?', answer: "" },
{ text: 'Where does he live?', answer: "" }
];
$scope.addAnswers = function ($event) {
$event.preventDefault();
var clientContext = new SP.ClientContext(siteUrl);
var web = clientContext.get_web();
var list = web.get_lists().getByTitle('Employee');
// create the ListItemInformational object
var listItemInfo = new SP.ListItemCreationInformation();
// add the item to the list
var listItem = list.addItem(listItemInfo);
// Assign Values for fields
listItem.set_item('EmpName', $scope.questions[0].answer);
listItem.set_item('EmpCity', $scope.questions[1].answer);


listItem.update();
clientContext.executeQueryAsync(
Function.createDelegate(this, onQuerySucceeded),
Function.createDelegate(this, onQueryFailed)
);
};
onQuerySucceeded = function () {
alert('Thank you, one record added to Employee list.');
}
onQueryFailed = function (sender, args) {
alert('Request failed. ' + args.get_message() + '\n' + args.get_stackTrace());
}
});


function GetSiteUrl() {
var urlParts = document.location.href.split("/");
return urlParts[0] + "//" + urlParts[2] + "/" + urlParts[3] + "/" + urlParts[4] + "/" + urlParts[5];
}


Page5 Example
Here I’m reading data again from Employee list; I have used JQury’s capability of calling a service and get the response in JSON format, for Image parsing we need to do some special coding pls note that in the JS file.
HTML
<script src="http://SERVERNAME/sites/arindamc/Subsite1/SiteAssets/jquery-2.1.3.min.js"></script>
<script src="http://SERVERNAME/sites/arindamc/Subsite1/SiteAssets/angular.min.js"></script>
<script src="http://SERVERNAME/sites/arindamc/Subsite1/SiteAssets/Page5.js"></script>
<div ng-app="myApp">
<div ng-controller="ContactController">
<div ng-repeat="p in Products">
<table style="background-color:#f07432">
<tr><td align = "center"><b>Employee Name: {{p.EmpName}}</b> </td></tr>
<tr><td align = "center"><img ng-src={{p.EmpImage}} /> </td></tr>
<tr><td align = "center"><b> Employee City:{{p.EmpCity}}.</b></td></tr>
</table>
<hr />
</div></div></div>


JavaScript:
var myApp = angular.module('myApp', []);
var webSiteURL = GetSiteUrl();
myApp.controller('ContactController', function ($scope) {
$scope.loadREST = function () {
jQuery.ajax({
url: webSiteURL + "/_vti_bin/listdata.svc/Employee?$select=EmpName,EmpCity,EmpImage",
type: "GET",
dataType: "json",
async: "true",
headers: { "Accept": "application/json;odata=verbose" },
success: function (data) {
var newData = [];
jQuery.each(data.d.results, function (index, value) {
//alert(value.EmpImage);
var pImage = value.EmpImage;
if (pImage != null) {
prImage = pImage.substring(0, pImage.indexOf(','));


newData.push({ EmpName: value.EmpName, EmpCity: value.EmpCity, EmpImage: prImage });
}
});
$scope.$apply(function () {
$scope.Products = newData;
});
},
error: function () {
alert("error");
}
});


};
$scope.loadREST();
});
function GetSiteUrl() {
var urlParts = document.location.href.split("/");
return urlParts[0] + "//" + urlParts[2] + "/" + urlParts[3] + "/" + urlParts[4] + "/" + urlParts[5];
}
Page6 Example
This example is adopted from this awesome article: Chart SharePoint Hosted App with AngularJs and JqPlot
Please go ahead and check the details there. The scenario here is to have data inside SharePoint List (custom list, one column for title and one column for value), to access that data, to use angular (http://angularjs.org) for data-binding part and jqPlot (http://www.jqplot.com ) for chart rendering. Also, one chart in this example is using ui-chart directive (http://angular-ui.github.io/ui-chart/), the directive that lets you use jqPlot with Angular

Note: The original article was written as an App on SharePoint 2013, I have converted it to fully client side coding, no Visual studio was used. For that I have provided the HTML below, also I had to make some changes the App.JS which is the main JS file for this (like Page1.Js for Page1.aspx in previous articles).
I have also copied the whole script folder as SiteAsset\ChartApp in the SharePoint portal. The content was copied from the Your Download path \ChartApp\Scripts folder. This has duplicate angular.js/JQuey-min.js etc. which you need to take care in actual scenario.
I also copied App.Css from ChartApp\Content folder to SiteAsset\ChartApp in the SharePoint portal. The site assets now look like below

HTML
<script type="text/javascript" src="http://inbaghpc00156/sites/arindamc/Subsite1/SiteAssets/ChartApp/jquery-1.9.1.min.js"></script>
<script type="text/javascript" src="http://inbaghpc00156/sites/arindamc/Subsite1/SiteAssets/ChartApp/lib/angular/angular.js"></script>
<script type="text/javascript" src="http://inbaghpc00156/sites/arindamc/Subsite1/SiteAssets/ChartApp/lib/jqplot/jquery.jqplot.js"></script>
<script type="text/javascript" src="http://inbaghpc00156/sites/arindamc/Subsite1/SiteAssets/ChartApp/lib/jqplot/plugins/jqplot.pieRenderer.js"></script>
<script type="text/javascript" src="http://inbaghpc00156/sites/arindamc/Subsite1/SiteAssets/ChartApp/lib/jqplot/plugins/jqplot.barRenderer.min.js"></script>
<script type="text/javascript" src="http://inbaghpc00156/sites/arindamc/Subsite1/SiteAssets/ChartApp/lib/jqplot/plugins/jqplot.categoryAxisRenderer.min.js"></script>
<script type="text/javascript" src="http://inbaghpc00156/sites/arindamc/Subsite1/SiteAssets/ChartApp/lib/jqplot/plugins/jqplot.pointLabels.min.js"></script>
<script type="text/javascript" src="http://inbaghpc00156/sites/arindamc/Subsite1/SiteAssets/ChartApp/lib/chart.js"></script>
<script type="text/javascript" src="http://inbaghpc00156/sites/arindamc/Subsite1/SiteAssets/ChartApp/Common.js"></script>
<link rel="stylesheet" type="text/css" href="http://inbaghpc00156/sites/arindamc/Subsite1/SiteAssets/ChartApp/lib/jqplot/jquery.jqplot.css"/>
<!-- Add your CSS styles to the following file -->
<link rel="Stylesheet" type="text/css" href="http://inbaghpc00156/sites/arindamc/Subsite1/SiteAssets/ChartApp/App.css" />
<!-- Add your JavaScript to the following file -->
<script type="text/javascript" src="http://inbaghpc00156/sites/arindamc/Subsite1/SiteAssets/ChartApp/App.js"></script>
<div>
<p id="message">
</p>
</div>
<div ng-app="myChartingApp" ng-controller="DemoCtrl">
<div ui-chart="someData" chart-options="myChartOpts" style="width:600px; height:300px;" ></div>
<div id="barchart" style="width:600px; height:300px;"></div>
<div id="linechart" style="width:600px; height:300px;"></div>
</div>

JavaScript
The below script is the App.js, I did not have to modify any other JS file. I also have commented the original code so that you can understand my changes.
'use strict';
// The allAnnouncements variable is used by more than one
// function to retrieve and process the results.
var allData;
var hostweburl;
var appweburl;
//alert('inside');
var myChartingApp = angular.module('myChartingApp', ['ui.chart'])
.value('charting', {
pieChartOptions: {
seriesDefaults: {
// Make this a pie chart.
renderer: jQuery.jqplot.PieRenderer,
rendererOptions: {
// Put data labels on the pie slices.
// By default, labels show the percentage of the slice.
showDataLabels: true
}
},
legend: { show: true, location: 'e' }
}
});
myChartingApp.service('$SharePointJSOMService', function ($q, $http) {
this.getData = function ($scope, listTitle) {
var deferred = $q.defer();
hostweburl = 'http://inbaghpc00156/sites/arindamc/Subsite1';
// decodeURIComponent(
// getQueryStringParameter("SPHostUrl")
// );
appweburl = 'http://inbaghpc00156/sites/arindamc/Subsite1';
// decodeURIComponent(
// getQueryStringParameter("SPAppWebUrl")
// );
//alert(hostweburl);
//alert(appweburl);
// resources are in URLs in the form:
// web_url/_layouts/15/resource
var scriptbase = hostweburl + "/_layouts/15/";
// Load the js files and continue to the successHandler
$.getScript(scriptbase + "SP.Runtime.js",
function () {
$.getScript(scriptbase + "SP.js",
function () {
$.getScript(scriptbase + "SP.RequestExecutor.js",
function () {
// var clientContext = new SP.ClientContext(siteUrl);
// var web = clientContext.get_web();
// var list = web.get_lists().getByTitle('Employee');
var context = new SP.ClientContext(appweburl);
//var factory = new SP.ProxyWebRequestExecutorFactory(appweburl);
// context.set_webRequestExecutorFactory(factory);
// var appContextSite = new SP.AppContextSite(context, hostweburl);
//var web = appContextSite.get_web();
var web = context.get_web();
context.load(web);
var list = web.get_lists().getByTitle(listTitle);
var camlQuery = SP.CamlQuery.createAllItemsQuery();
this.listItems = list.getItems(camlQuery);
context.load(this.listItems);
context.executeQueryAsync(
Function.createDelegate(this, function () {
var ListEnumerator = this.listItems.getEnumerator();
while (ListEnumerator.moveNext()) {
var currentItem = ListEnumerator.get_current();
console.info(currentItem.get_item('Title') + currentItem.get_item('ID'));
var Title = currentItem.get_item("Title");
var Value = currentItem.get_item("Value");
var b = [Title, Value];
$scope.someData[0].push(b);
$scope.ticks.push(Title);
$scope.barvalues.push(Value);
//$scope is not updating so force with this command
$scope.$apply();
}
$scope.setBarChart();
$scope.setLineChart();
}),
Function.createDelegate(this, function (sender, args) {
deferred.reject('Request failed. ' + args.get_message() + '\n' + args.get_stackTrace());
})
);
}
);
}
);
}
);
return deferred.promise;
};
});
myChartingApp.controller('DemoCtrl', function ($scope, charting, $SharePointJSOMService) {
//alert('controller');
$scope.someData = [[]];
$scope.ticks = [];
$scope.barvalues = [];
var promise = $SharePointJSOMService.getData($scope, 'PieChartData');
promise.then(
function (message) {
alert(message);
},
function (reason) {
alert(reason);
}
);
$scope.myChartOpts = charting.pieChartOptions;
$scope.setBarChart = function () {
//alert('setBarChart');
var plot2 = $.jqplot('barchart', [$scope.barvalues], {
seriesDefaults: {
renderer: $.jqplot.BarRenderer,
pointLabels: { show: true, location: 'e', edgeTolerance: -15 },
},
axes: {
xaxis: {
renderer: $.jqplot.CategoryAxisRenderer,
ticks: $scope.ticks
}
}
});
}
$scope.setLineChart = function () {
//alert('setLineChart');
var plot = $.jqplot('linechart', [$scope.barvalues], {
title: 'Sales 2014',
axesDefaults: {
labelRenderer: $.jqplot.CanvasAxisLabelRenderer
}, seriesDefaults: {
rendererOptions: {
smooth: true
}
},
axes: {
yaxis: {
label: 'Sales', min: 0
},
xaxis: {
label: 'Months', min: 1, max: $scope.barvalues.length
}
}
,
series: [{ color: '#5FAB78' }]
});
}
});

Output

POINTS TO BE NOTED
 I have masked the actual server name with SERVERNAME is all JS references, you need to use correct URL.
 If you are using CDN, make sure internet is available in your server
function GetSiteUrl()- is a very basic method, you need to modify it based on the depth of the SharePoint portal URL you are using, for me the URL is http://SERVRNAME/arindamc/Subsite1, so I used until urlParts[4] in the return statement. 

I Hope its help for learning. 


1 comment: