Introduction
This article is meant to provide guidance and insight into working with AngularJS 1.x and Bootstrap in a restricted SharePoint hosting environment. Do you work in a SharePoint environment where add-ins and solutions are not allowed? A common UI element that people like is an images slider. This article gives an example of what to do in a restricted development environment when you really "need" something cool on your SharePoint page. The following code is meant as a starting point. Significant changes can be made to the slider via manipulation of the JavaScript code and CSS rules. Any column names or other labeling not associated with the Angular directives or Bootstrap library/CSS can be changed to better fit your use scenario. "<" ">" symbols denote content (e.g. URL and GUID) particular to your environment.
Background
With SharePoint 2016 around the corner, migrations to Office 365 a serious consideration for some and the desire to not "re-code" applications companies are advocating against Solution and Feature development. Furthermore, the creation and investment in add-in development has a "just another Silverlight" effect on some. This trepidation can initially be experienced by developers as a total bummer but there is a solution. My go to solution is to create Angular Single Page Application (SPA)s. Angular2 is revolutionary but 1.x can still address most of your needs. With AngularJS Bootstrap and the 2013 REST API, there are few limitations to what you can achieve. My mantra here is to use SharePoint base functions (Security permissions, Lists, etc.) and render UI elements with JavaScript.
Requirements
- angular.js (recommend min)
- bootstrap.js (recommend min)
- bootstrap.css (recommend min)
- SharePoint Picture List with the following additional columns:
Comment
: Single line of text LinkURL
: Hyperlink or Picture ListOrder
: Choice (add numbers starting with 0) Initiative
: Lookup (this column will provide the querystring association – as needed)
Using the Code
A great way to save time is to minimize modifications to the base Bootstrap css. However, depending on your needs, you may find this an impossible thing to do. Stay away from modifying the Bootstrap file and add the needed changes to your SPA.
<style type="text/css">
.carousel { }
.carousel-indicators li {background-color: #000\9; border: 1px solid #000000 !important;}
.carousel-indicators .active {background-color: #000000 !important}
.carousel-caption {color:#000000 !important; text-shadow:none !important; padding-top:0px !important}
.outlineMyCarousel {border: 1px solid #808080!important; padding:1px !important}
</style>
Normally, I would separate JavaScript functions and variables to a separate "utility" file but for simplicity, in this article, I have combined my JavaScript with the AngularJS used.
Please see:
<script type="text/javascript">
function openNewSliderImageDialog(tUrl, tTitle) {
var options = {
url: tUrl,
title: tTitle
}; SP.UI.ModalDialog.showModalDialog(options);
}
function getParameterByName(name) {
name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
results = regex.exec(location.search);
return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
}
var cd1 = 'SliderImages/Forms/Thumbnails.aspx';
var initiativeName = getParameterByName('Init');
var listTitle = 'SliderImages';
var dTarget = '#myCarousel';
var spDomain = "https://oursites.myngc.com/ENT/Innovation/";
var imgSource = spDomain + 'SliderImages/';
var app = angular.module('sliderApp', []);
app.controller('sliderCtrl', function ($scope, $http, sliderSvcs) {
$scope.CurrentReviewDate = new Date();
$scope.dTarget = dTarget;
sliderSvcs.getEntries(listTitle).then(function (result) {
$scope.sliderItems = result;
});
$scope.srcURL = spDomain;
$scope.imgSource = imgSource;
$scope.cd1 = $scope.srcURL + cd1;
});
app.service('sliderSvcs', ['$http', '$q', function ($http, $q) {
$http.defaults.headers.common.Accept = "application/json;odata=verbose";
$http.defaults.headers.post['Content-Type'] = 'application/json;odata=verbose';
this.getEntries = function (listTitle) {
var dfd = $q.defer();
$http.defaults.headers.post['X-HTTP-Method'] = ""
var query = "?$select=Author/Title,Initiative/Title,FieldValuesAsText/FileRef,
ListOrder,Title,Comment&$expand=Author,Initiative,
FieldValuesAsText&$orderby=ListOrder asc&$filter=Initiative/Title
eq '" + initiativeName + "'";
var restUrl = spDomain + "_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 items");
});
return dfd.promise;
}
}]);
</script>
Finally, the html component.
<!--
<div ng-cloak ng-app="sliderApp" ng-controller="sliderCtrl">
<div>
<a href="#" onclick="openNewSliderImageDialog
('<mydomain>/_layouts/15/Upload.aspx?List={<sliderPhotoListGuid>}
&RootFolder=&IsDlg=1','New Slider Image');
" style="height: 30px; width: 30px;"
data-toggle="tooltip" data-placement="left"
title="Add a new Slider Image">
<i class="fa fa-plus-square-o fa-2x"
aria-hidden="true"></i></a>
<a id="sliderList" ng-href="{{cd1}}"
data-toggle="tooltip"
data-placement="left" title="View Slider Image list">
<i class="fa fa-list fa-2x" aria-hidden="true"
style="height: 30px; width: 30px;"></i></a>
</div>
<div id="myCarousel"
class="carousel slide outlineMyCarousel" data-ride="carousel">
<ol class="carousel-indicators" style="color: #000000 !important">
<li ng-repeat="item in sliderItems" data-target="{{dTarget}}"
data-slide-to="{{item.ListOrder}}"
ng-class="{active:!$index}"></li>
</ol>
<div class="carousel-inner">
<div class="item"
ng-class="{active:!$index}" ng-repeat="item in sliderItems">
<img ng-src="{{item.FieldValuesAsText.FileRef}}" alt=""
class="img-responsive" style="min-height: 200px; min-width: 300px !important;
max-height: 200px; min-width: 300px; text-shadow: none !important" />
<div class="carousel-caption">
<h3>{{item.Title}}</h3>
<p>{{item.Comment}}</p>
<p><i>-{{item.Author.Title}}</i></p>
</div>
</div>
</div>
<a class="left carousel-control"
href="#myCarousel" role="button" data-slide="prev">
<span class="glyphicon glyphicon-chevron-left"
aria-hidden="true"></span>
<span class="sr-only">Previous</span>
</a>
<a class="right carousel-control"
href="#myCarousel" role="button" data-slide="next">
<span class="glyphicon glyphicon-chevron-right"
aria-hidden="true"></span>
<span class="sr-only">Next</span>
</a>
</div>
</div>
Points of Interest
Accessing SharePoint list data with REST API brings more data to us than meets the eye. View in XML your data and then experiment with RoleAssignments
, AttachmentFiles
,
ContentType<br />
FieldValuesAsHtml
,
FieldValuesAsText
and
FieldValuesForEdit
. Similar to manipulating Lookup data, when you reference these sources, you usually find what you need. See how I use "
FieldValuesAsText
/
FileRef
" in the code above. Consider the following:
- Access the XML of your REST API (e.g. <mydomain>/_api/web/lists/getByTitle('Announcements')/items)
- See article: Getting document name using REST (not title) while filtering document library http://sharepoint.stackexchange.com/questions/126003/getting-document-name-using-rest-not-title-while-filtering-document-library
- See article: Get to know the SharePoint 2013 REST service
https://msdn.microsoft.com/en-us/library/office/fp142380.aspx - See article: Accelerate SharePoint Development with AngularJS
http://www.sharepointbriefing.com/spcode/accelerate-sharepoint-development-with-angularjs.html
SharePoint Integration
- Place the SPA file (e.g. imageSlider.html) in a SharePoint document library, e.g. https://<mydomain>/Style%20Library/
- Place the required reference libraries in a SharePoint document library, e.g. https://<mydomain>/Style%20Library/js/
- On the page where the slider will appear:
- Place the required references in a Script Editor. <script src="<mydomain>/Style%20Library/js/jquery-1.12.0.min.js"></script> <script src="<mydomain>/Style%20Library/js/bootstrap.min.js"></script> <link href="<mydomain>/Style%20Library/css/bootstrap_custom.min.css" rel="stylesheet" />
- Insert a Content Editor Web Part (CEWP) where you want the slider to appear on the SharePoint page layout, insert the URL to the SLA file in the Content Link element and set Chrome Type to None (if desired).