使用 VS2013 和 AngularJS 构建 Dilbert SharePoint 托管应用





0/5 (0投票)
SharePoint 托管应用,用于显示和评价 Dilbert 漫画。
引言
想法是什么? 创建一个 SharePoint 应用,用于显示 Dilbert 漫画并允许 SharePoint 用户对这些漫画进行评分。
解决方案是什么? SharePoint 托管的应用,它使用 AngularJS 来获取和显示每日 Dilbert 漫画,并使用 AngularJs 和 JSOM (Javascript 对象模型) 来读取和写入 SharePoint 应用 Web。
所以,场景是这样的
- Dilbert 漫画的 RSS 源在 Web 上,不在 SharePoint 中
- SharePoint 托管的应用 (仅限 JavaScript) 将获取此源并显示漫画
- SharePoint 应用 Web 将有一个自定义列表来存储评分
- 将为每部漫画显示评分
- SharePoint 用户将能够对每部漫画进行评分
- AngularJs 将用作开发应用的框架 (获取和显示数据)
背景
上个月,我写了一篇关于使用 Visual Studio、JqPlot 和 AngularJS 创建图表 SharePoint 托管应用的文章。这是一个简单的应用,它从宿主 Web 读取数据并根据该数据渲染图表。我对其中一些解决方案并不满意 (所有 Javascript 都在一个文件中,angular 服务与控制器重叠,不易阅读或维护)。这是一个进步。在本文中,我将向您展示如何构建一个具有混合数据源 (RSS 源和 SharePoint) 列表的 SharePoint 托管应用。
解决方案
Sharepoint 应用
应用是使用 Visual Studio 2013 创建的。过程与我之前的文章完全相同,新建 SharePoint 项目 (App for SharePoint 2013),应用类型为 SharePoint 托管应用。此应用在应用清单中不需要任何特殊权限。
Data
SharePoint 列表
如前所述,漫画评分将保存在名为 DilbertRating 的 SharePoint 自定义列表中。想法是为每部漫画 (每天一部) 维护一行列表。每个投票 (1 到 10 的评分) 将在名为 Rating 的列中汇总,而投票者数量 (其计数) 将保存在名为 Votes 的列中。因此,一部漫画的平均评分将是两个列相除的结果:Rating 和 Votes。为了简化解决方案,没有检查特定用户是否已经投票,因此一个人可以无限次投票。
该列表包含 5 列
- Title – 默认系统列,文本类型
- Rating – 自定义列,数字类型 (用于存储总投票数)
- Votes – 自定义列,数字类型 (用于存储投票数)
- Strip Id – 自定义列,文本类型 (用于存储漫画标题,例如“Dilbert 2014-04-24”)
- Strip Date – 现有列,日期类型,用于存储漫画日期
列创建
Rating、Votes 和 StripId 列是在应用中创建的。这是一个非常直接的过程。右键单击项目名称,然后选择添加新项。在新建项对话框中选择 Site Column (下图)
对于 Rating 列,我在 VS2013 XML 编辑器中将类型更改为 Number,并将组更改为 CPU。
<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<Field
ID="{e284742d-784e-4e28-b909-3ea081b483f6}"
Name="Rating"
DisplayName="Rating"
Type="Number"
Required="FALSE"
Group="CPU">
</Field>
</Elements>
我对 Votes 列进行了相同的操作。
对于 StripId 列,我只将组更改为 CPU。
列表实例
右键单击项目名称,然后选择添加新项。在新建项对话框中选择 List。在下一个屏幕 (下图) 上输入名称 DilbertRating,并保留第一个选项 (Create a customizable list template…) 的选中状态。单击 Finish。
列表设计器窗体将打开。Title 列已添加到我们的列表中。我们需要做的是将我们之前添加的列 (Rating、Votes 和 StripId) 添加到我们的列表中。我们还将向列表添加 Article date 列,但对于本项目,我们将将其重命名为“Strip Date”。因此,此列的显示名称将是“Strip Date”,内部名称将保持为 ArticleStartDate。最后,我们的列表应该如下图所示
我们的列表准备就绪。
结论:当有人安装我们的应用时,此列表将在应用 Web 中创建。这就是为什么不需要额外的权限。
注意:请仔细检查应用功能。列、列表模板和列表实例定义应在功能设计器的功能面板中的项目内。
使用代码,也就是有趣的部分
我们将使用第三方工具的 angular (angular-sanitize.min.js 和 angular.js) 和 bootstrap。我将它们放入 lib 文件夹。
我们还将把 Controllers.js 和 Service.js 文件添加到应用的 Scripts 文件夹中。
Jquery 和 SharePoint 内容已默认包含。
架构
在 Scripts 文件夹的 Controllers.js 文件中将有一个控制器 DilbertAppCtrl。我们应用的行为将在此处定义。
在 Service.js 文件中,我们将定义两个服务:FeedService 和 SPService。
FeedService 的职责是与 Dilbert 的 RSS 源通信并获取数据。FeedService 非常通用。它不知道源 URL。源的 URL 将在调用服务方法时注入。因此,此服务也可用于其他情况。
另一个服务是 SpService。它的职责是与 SharePoint 通信 (从 SP 列表读取评分,将新评分写入列表)。
应用视图将在 Pages 文件夹的 Default.aspx 页面中定义。
脚本文件 App.js 将将整个应用粘合在一起。
视图
计划一次显示一部漫画。所以我们的布局将看起来像这样:漫画标题、漫画图片、日期、带有投票选项的评分信息,以及底部的导航按钮。
我们的视图很简单,在 PlaceHolderMain 内容占位符内,我们有
<div class="container-fluid" ng-app="DilbertApp">
<div data-ng-controller="DilbertAppCtrl">
<div class="row-fluid"><h4>Dilbert demo</h4></div>
<div class="row-fluid">
<h5><a target="_blank" href="{{currItem.link}}">{{currItem.title}}</a></h5> <p class="text-left">{{currItem.contentSnippet}}</p>
<span class="small">{{currItem.publishedDate}}</span>
<div ng-bind-html="currItem.content"></div>
Rating is {{currItemRating}} <br/>
<div fundoo-rating rating-value="currItemRating" max="10"
on-rating-selected="saveRatingToServer(rating)"></div>
<button type="button" ng-click="prev()" ng-show="position > 0">Prev</button>
<button type="button" ng-click="next()" ng-show="position < feeds.length-1">
Next</button>
</div>
</div>
</div>
由于我们正在为 SharePoint 创建应用,因此无法将 ng-app 指令添加到 html 元素,因此所有内容都包含在 Main 占位符的 div 元素内。因此,第一个 div 定义了我们的应用 – RSSFeedApp。
在第二个 div 中,我们定义了将要使用的 FeedCtrl 控制器。
然后是模板。我们正在从 Web 获取漫画列表,但一次只显示一部。currItem 对象将保存当前选中的项目 (漫画)。对于这个当前项目,我们将显示
- 它的标题 (currItem.title)
- 指向 Dilbert 网站的链接 (currItem.link)
- 漫画日期 (currItem.publishedDate)
- 图片 (currItem.content)
- 评分 currItemRating
导航按钮在底部。有两个,一个上一页按钮和一个下一页按钮。它们有点击事件 (向前或向后移动) 和 ng-show 指令。Prev 按钮仅在位置大于 0 (0 是第一个位置) 时显示,Next 按钮仅在位置属性小于漫画总数减 1 时显示。
关于评分指令的几点说明。我们为此目的使用了 fundoo rating。这是评分的标记
<div fundoo-rating rating-value="currItemRating" max="10" on-rating-selected="saveRatingToServer(rating)"></div>
我们将 curritemRating (Rates/Number of votes 除法的四舍五入整数) 属性绑定到它。我们将最大值设置为 10,并在有人对漫画评分时调用 saveRatingToServer 事件。
控制器 (Controller)
DilbertAppCtrl 控制器具有以下属性
- Feeds – Dilbert 漫画集合
- feedSrc - Dilber RSS 源的 URL
- position – 因为我们一次只显示一部漫画,所以我们需要 position 属性来知道当前显示的是哪部漫画
- currItem – 当前显示的漫画对象
- currItemRating – 当前选定漫画的评分
- ratings – 从 SharePoint 列表读取的评分集合
除了属性之外,我们的控制器还有以下方法
- GetFeeds – 调用 FeedService 的 parseFeed 方法并从 RSS 源获取漫画到 feeds 集合。它还将当前项 (currItem) 设置为集合中的第一个对象。
- GetRatings – 调用 SpService 的 GetData 方法。它从 SharePoint 获取最后 14 个评分 (因为 RSS 源包含最后 14 部漫画) 到 ratings 集合。此外,此方法在数据获取完成后会刷新当前项的评分。
- SetRatings – 负责计算当前显示项评分的方法。
-
saveRatingToServer – 负责保存新评分的方法。它调用方法 (在 SpService 中定义) 以将数据保存在 SharePoint 列表中。
-
$scope.saveRatingToServer = function (rating) { $window.alert('Rating selected - ' + rating); var stripId=$scope.feeds[$scope.position].title; SpService.CheckRating($scope, stripId).then( function (res) { $scope.ratings[stripId].id = res.id; $scope.ratings[stripId].votes = res.votes + 1; $scope.ratings[stripId].rating = res.rating + rating; if (res.id == 0) SpService.AddData(stripId, $scope.ratings[stripId], $scope); else { SpService.UpdateData(stripId, $scope.ratings[stripId], $scope); } $scope.currItemRating = Math.round($scope.ratings[stripId].rating / $scope.ratings[stripId].votes); } );
- 重要的是要注意,此方法使用链式 Promise。首先,我们调用 SpService 的 CheckRating 方法来检查是否有人在此期间对我们的漫画进行了评分,然后,我们将结果传递给匿名函数 (“then(“ 之后的函数),根据是否存在评分,调用 SPService 的相应方法 (对于不存在的评分调用 AddData,对于已存在的评分调用 UpdateData)。
使用链式 Promise 比使用回调函数进行异步调用更具可读性且更易于处理。
- Prev – 方法。如果位置不是在集合的开头,则将 currItem 设置为集合中的前一个对象。
- Next– 方法。如果位置不是在集合的末尾,则将 currItem 设置为集合中的下一个对象。
服务
如前所述,我们有两个服务 FeedService (解析 RSS 源) 和 SpService (从 SharePoint 读取数据并写回数据)。
FeedService 非常直接。它只有一个方法 parseFeed,带有一个参数 url。
dilbertServices.factory('FeedService', ['$http', '$q', function ($http, $q) {
return {
parseFeed: function (url) {
var deferred = $q.defer();
$http.jsonp('//ajax.googleapis.ac.cn/ajax/services/feed/load?v=1.0&num=50&callback=JSON_CALLBACK&q=' +
encodeURIComponent(url)).success(
function (data, status) {
deferred.resolve(data);
}).error(function (data, status) {
deferred.reject(data);
});
return deferred.promise;
}
}
}]);
请注意,我们使用 Promise 和 $q 服务来处理异步调用。
SpService 稍微复杂一些。它有 4 个方法
- GetData – 从应用 Web 中的 SharePoint 列表获取数据 (评分)。它与我之前文章中读取数据的方法非常相似,唯一的区别是使用了 $q 服务和 Promise (服务中不再改变 $scope 数据)。
请注意这部分。我们只从 RSS 源获取最后 14 行,因此我们只需要 Ratings 列表中的最后 14 个评分。
- CheckRating – 检查在此期间是否有人对该漫画进行了评分
- UpdateData – 更新先前评分的漫画的评分
- AddData – 为先前未评分的漫画添加评分。
所有方法都使用 JSOM 与 SharePoint 进行通信
UpdateData 和 AddData 非常相似 (代码示例如下)。
var context = new SP.ClientContext(appweburl);
var web =context.get_web();
context.load(web);
var oList = web.get_lists().getByTitle('DilbertRating');
var oListItem = oList.getItemById(obj.id);
oListItem.set_item('Title', guid);
oListItem.set_item('StripId', guid);
oListItem.set_item('Rating', obj.rating);
oListItem.set_item('Votes', obj.votes);
oListItem.update();
var listItemInfo = new SP.ListItemCreationInformation();
var listItem = oList.addItem(listItemInfo);
App.js
将整个应用粘合在一起的文件。
这是关键行 (将 App 实例化为 DilbertApp,并附带我们的控制器和服务)
var App = angular.module('DilbertApp', ['ngSanitize','dilbertControllers','dilbertServices'])
除此之外,此文件还定义了另一个将用于评分的指令。我为此目的选择了 fundooRating (链接在此)。因此,对于评分,我们使用的是在 App.js 文件中定义的第三方指令。
最终结果
项目显示
集合中的第一部漫画及评分
第二部漫画 (注意 Prev 按钮) 在评分时。
评分后的第二部漫画。
评分列表
如果您不知道如何访问它,它就在您的应用中,位于 Lists/DilbertRating 地址。在我的服务器上,它位于
http://app-c543c3600f43bf.abcapps.com/DilbertApp/Lists/DilbertRating 地址
结论
就是这样。此处描述的评分系统可以轻松地在其他场景中实现 (任何非 SharePoint 数据显示在 SharePoint 中)。
此外,链式 Promise 比创建回调金字塔更简单、更清晰。因此,使用 angularjs 作为 SharePoint 的框架为应用开发带来了新的质量。