本文是SharePoint 2010客户端AJAX应用系列的一部分。上一次我们讨论了如何在SharePoint 2010中编写一个简单的ASP.Net AJAX模板来显示列表数据。
本次我们将演示如何与jQuery进行整合,在一块虚拟布告牌(Corkboard)上以可拖动的索引卡片的方式显示每个列表项,并且我们将获取X和Y坐标返回给SharePoint。
布告牌上的卡片
首先,为了使我们的应用更有趣一些,在PageHead里加上一些CSS样式设置(放在一个单独的CSS样式文件里的话当然更好)以便使我们的user stories项以卡片的方式显示在布告牌上。
<style type="text/css"> .sys-template { display:none; } .userStoryBackground { background-image: url('Images/corkboard.png'); 695px; height:397px; } .userStoryCard { border: 1px solid #777777; 208px; height:140px; cursor: move; background-image: url('Images/blankcard.png'); margin:4px; } .userStoryDescription { font-size: 17px; padding: 4px 5px 5px 5px; } .userStoryTitle { font-size: 15px; font-weight: bold; padding: 2px 5px 0px 5px; } </style>
然后,更新Main里的内容以使用这些样式:
<asp:Content ID="Main" ContentPlaceHolderID="PlaceHolderMain" runat="server"> <div id="userStoriesList" class="sys-template userStoryBackground"> <div class="userStoryCard"> <div class="userStoryTitle">{{ 标题 }}</div> <div class="userStoryDescription"><div>{{ 描述 }}</div> </div> </div> </asp:Content>
结果看起来是这样的:
看起来不错,但我们需要绝对位置的卡片,还要使它们可被拖动。首先,我们在SharePoint 的User Stories列表中添加X和Y两个数字类型的字段。下一步,我们在CSS样式里设置userStoryCard类的position为absolute。最后,我们把X和Y的值通过下面的方式添加到内容部分:
<div id="userStoriesList" class="sys-template userStoryBackground" xmlns:sys="javascript:Sys"> <div class="userStoryCard" sys:style="{{ 'left:'+X+'px;top:'+Y+'px;'}}"> <div class="userStoryTitle">{{ 标题 }}</div> <div class="userStoryDescription"><div>{{ 描述 }}</div> </div> </div>
在这里有两个有趣的地方。一是在顶部声明了sys命名空间,将用在下面的style属性里。这里我们不能使用常规的style属性的原因是,DataView默认情况下无法替换某些HTML属性(如果你有兴趣,可以看看Bertrand La Roy的这篇文章)。sys命名空间可以使DataView跳过这个HTML限制。
第二个有趣的地方是组成style属性值的字符串,是一个绑定表达式。 同时,还可以在绑定表达式中使用任何你喜欢的JavaScript。这样一来,就可以组合出非常强大的功能。
现在,如果我们在SharePoint里手动为每个user story列表项设置X和Y值,其所对应的卡片将显示在正确的位置。但是我们仍然缺少卡片拖动的功能。
利用jQuery使卡片可拖动
jQuery让拖动实现起来简单的让人难以置信。第一步是下载并引用draggable jQuery UI插件(这里我用的是jquery-ui-1.8.2.custom.min.js)。然后,我们只需在将要启用拖动功能的DOM元素上调用draggable() 函数即可。
这应该是很简单,但如果我们从Sys.onReady函数内部调用draggable()会发现没有效果。原因是,ASP.Net AJAX模板引擎尚未得到一个机会来呈现新的DOM元素。幸运的是DataView中有一个相当于JavaScript OnRendered的事件,配合该事件就能达到目的:
<script src="/_layouts/Scripts/jQuery/jquery-1.4.1.js" type="text/javascript"></script> <script src="/_layouts/Scripts/plugins/jquery-ui-1.8.2.custom.min.js" type="text/javascript"></script> <script src="/_layouts/Scripts/MicrosoftAjax/Start.js" type="text/javascript"></script> <script src= "/_layouts/Scripts/MicrosoftAjax/MicrosoftAjax.js" type="text/javascript"></script> <script type="text/javascript"> Sys.require([ Sys.components.dataView, Sys.components.openDataContext, ]); var dataContext; var dataView; Sys.onReady(function () { dataContext = Sys.create.openDataContext({ serviceUri: "/_vti_bin/ListData.svc", mergeOption: Sys.Data.MergeOption.appendOnly }); dataView = Sys.query("#userStoriesList").dataView({ dataProvider: dataContext, fetchOperation: "UserStories", feachParameters: { orderby: '标题' }, autoFetch: "true", rendered:onRendered }).get(0); }); function onRendered() { $(".userStoryCard").draggable(); } </script>
顺便说一下,你可能已经注意到此代码已更新,与上一篇中的代码稍微有些不同 。最大的变化是,我们现在使用openDataContext而不是openDataServiceProxy。原因是,openDataContext知道如何将数据写回到SharePoint(我们稍后会完成这个功能)。
但更重要的是我们现在可以拖动页面中所有的user story卡片了:
jQuery可以做的事情确实相当惊人。但是,我们仍然有一个问题。
保存不可见的属性
问题是,我们每次刷新页面后,卡片漂亮的布局就消失了,一切又恢复成一个叠一个堆放在左上角的样子。
保存X和Y坐标的一种方法是使用在线绑定语法,我会在下一篇博文中更详细地对其进行讨论。然而,对于不可见的属性更恰当的方法是手动编辑模板引擎底层的内存中的JSON对象。此外,这将使我们对幕后发生的事情更多一点了解。
第一步是要注册当用户停止拖动卡片的“事件”。
function onRendered() { $(".userStoryCard").draggable({ stop:onDragStop }); }
一旦我们挂接好onDragStop函数后,所有有趣的事情就要发生了:
function onDragStop(event, ui) { var userStoryCard = ui.helper[0]; var selectedUserStoryJsonObject = dataView.findContext(userStoryCard).dataItem; var newX = ui.position.left; var newY = ui.position.top; Sys.Observer.setValue(selectedUserStoryJsonObject, "X", newX); Sys.Observer.setValue(selectedUserStoryJsonObject, "Y", newY); dataContext.saveChanges(); }
第一行从jQuery UI中获取用户刚刚完成拖动的DOM元素。第二行获取DOM元素所代表的内存中的JSON对象。该接下来两行代码获取卡片被拖放到的目标位置的X和Y坐标(相对于他们的父DOM元素)。接下来的代码很奇怪。
Sys.Observer.setValue看起来很复杂。我们为什么不能直接写成selectedUserStoryJsonObject.X = newX?因为如果我们这么写,DataView将不会检测到我们进行的修改,dataContext.saveChanges()将不会发送任何数据回服务器。
Sys.Observer.setValue将会通知所有有关各方值已被更改,在这里是DataView。这项通知技术成就了一个非常酷的特性。ASP.Net AJAX模板中的DataView可以批量修改。修改完成后调用dataContext.saveChanges()时,如果我们用Fiddler抓包查看,就会看到,它只发送已经修改过的记录回SharePoint。这项特性可以大大节省网络流量,加快应用程序的响应。
现在放上这段代码后,我们就可以在页面上自由移动user story卡片了,当我们刷新页面或几天后我们再回来时,卡片的布局仍然保持不变。因为已经写到SharePoint里了!
总结
在这篇博文中,我已经演示了如何使ASP.Net AJAX模板与jQuery很好的交互,以及如何直接修改内存中的JSON对象,并将其写回到SharePoint。我们的应用开始看起来像一个有用的应用程序了。比较显著的一点是我们没有编写多少代码。接下来你会看到如何实现主-子关系,以及如何使用户可以编辑列表项。
参考资料
Client Side AJAX Applications in SharePoint 2010 – Part 4: jQuery Integration and Persistence