2010年10月20日,星期三

SP2010 AJAX–第2部分:使用JavaScript 客户OM + jQuery处理列表

  1. 将jQuery精简为基本要素 (技术)
  2. 使用JavaScript 客户OM处理列表(技术) - 本文
  3. 结合使用jQuery AJAX和HTTP处理程序 (技术)
  4. 从HTTP处理程序返回JSON (技术)
  5. 为客户端OM和jQuery启用Intellisense (小费)
  6. 调试jQuery / 的JavaScript (小费)
  7. 构建AJAX应用程序时的有用工具 (小费)
  8. 将现有应用程序迁移到jQuery / AJAX

在上次查看了用于更新页面的基本jQuery技术之后,现在我们开始与SharePoint交流。当然,对于希望在SharePoint 2010上构建AJAX应用程序的开发人员而言,客户端对象模型是显而易见的工具– it’整个目的是简化从JavaScript或Silverlight调用服务器方法的过程,或者‘offline’.Net代码 管理 客户端OM,并在这些环境中使用SharePoint。它通过提供一个层来实现此目的,该层负责处理从例如Web服务调用SharePoint Web服务的复杂过程。 的JavaScript。这意味着在构建SharePoint应用程序时’回发到服务器/完全刷新页面比其他方式容易。本文介绍了一些示例并说明了一些重要的性能提示。

请注意,本文重点介绍客户端OM的JavaScript / ECMAScript风格。其他两种风格(Silverlight和托管代码)的语法略有不同,因此,如果您交叉引用MSDN /其他文章,请确保您’重新看对的事情!

如果您要考虑一些关键事项’第一次查看客户端OM:

  • 异步编程模型 –步骤的基本顺序是获取对 ClientContext (如同 SPContext),读取/更新数据,然后调用 ClientContext.ExecuteQueryAsync()。最后一种方法可以触发实际发生的事情(即对服务器的请求),然后您传递2‘callback’成功/失败时运行的方法名称。
    • 这类似于其他断开连接的编程,也类似于jQuery’我们的AJAX方法’我会看下一篇文章
  • 在将使用的对象上调用.Load() –尽管SharePoint对象层次结构故意与服务器上的对象层次结构相同,但不同之处在于必须调用 ClientContext.Load(myJavaScriptVariable) 在代表您要使用的SharePoint对象(网站/网站/列表/列表项/其他)上的每个变量上。这样可以确保将该对象的详细信息传递给客户端。但是,我们’以后还会看到你应该 *只要* 呼叫 ClientContext.Load() 在您真正需要的物体上。

例子

  1.   提取一些数据(当前网站的标题)

    一个简单的例子开始– we’ll在jQuery中(jQuery按钮单击事件)检索当前网站的标题,并将其显示在JavaScript警报框中。这几乎就变得很简单,非常适合说明回调模型。一个重要的‘pattern’要注意的是,我们’重新声明要加载的全局JavaScript变量,因为’全局变量也可以在成功回调中使用: 

    客户端OM_Demo1_Off
    客户端OM_Demo1_On
    <fieldset id="fldDemo1">
        <legend>Demo 1 - fetching web title</legend>
        <div id="Div1" class="demoRow">
                <div class="demoControls">
                    <button id="btnDemo1" type="button">Fetch web title</button>
                </div>
        </div>
    </fieldset>
    <script type="text/javascript">
        var currentWeb;
     
        $('#btnDemo1').click(function () {
            var ctx = new SP.ClientContext.get_current();
     
            currentWeb = ctx.get_web();
            ctx.load(currentWeb);
     
            ctx.executeQueryAsync(getWebSuccess, getWebFailure);
        });
     
        function getWebSuccess(sender, args) {
            alert('Title of current web is: ' + currentWeb.get_title());
        }
     
        function getWebFailure(sender, args) {
            alert('Failed to get web. \nError: ' + args.get_message() + '\nStackTrace: ' + args.get_stackTrace());
        }
    </script>
  2.  从列表/库中获取项目

    Something we may definitely want to do 在 some point 与 the 客户OM is retrieve the items in a list or library. The same 模式 is used but we effectively navigate down the SharePoint hierarchy to the list in question, 和 then run a CAML query (no LINQ in the 客户OM). Notice that there is no ‘indexer’属性以在客户端上获得单个列表– we 呼叫 .get_lists() 返回网络中的所有列表,然后调用 .getByTitle(‘My list name’) 在结果集合上–与服务器端对象模型相比偶尔出现差异的一个很好的例子。在回调中,我们有一个可以枚举的集合,每个项目都有一个名为‘FileLeafRef’(您以前可能在CAML查询中看到过)的文件名:

    ClientOM_Demo2_Off
    客户端OM_Demo2_On
    <fieldset id="fldDemo2">
        <legend>Demo 2 - fetching list items</legend>
        <div id="demo2Row" class="demoRow">
                <div class="demoControls">
                    <button id="btnDemo2" type="button">Fetch items</button>
                </div>
                <div class="demoResults">
                    <span id="demo2Result" />
                </div>
                <div class="clearer" />
        </div>
    </fieldset>
    <script type="text/javascript">
        var allDocs;
     
        $('#btnDemo2').click(function () {
            var ctx = new SP.ClientContext.get_current();
     
            var targetList = ctx.get_web().get_lists().getByTitle('Shared Documents');
            var query = SP.CamlQuery.createAllItemsQuery();
     
            allDocs = targetList.getItems(query);
            ctx.load(allDocs);
     
            ctx.executeQueryAsync(Function.createDelegate(this, getDocsAllItemsSuccess), 
                Function.createDelegate(this, getDocsAllItemsFailure));
        });
     
        function getDocsAllItemsSuccess(sender, args) {
            var listEnumerator = allDocs.getEnumerator();
            while (listEnumerator.moveNext()) {
                $('#demo2Result').append(listEnumerator.get_current().get_item("FileLeafRef") + '<br />');
            }
        }
     
        function getDocsAllItemsFailure(sender, args) {
            alert('Failed to get list items. \nError: ' + args.get_message() + '\nStackTrace: ' + args.get_stackTrace());
        }
    </script>

    有关此示例的其他几点注意事项:

    - 一世’m使用稍微复杂一点的语法 Function.createDelegate(this,myFunctionName)ClientContext.ExecuteQueryAsync()。 MSDN示例使用此语法,但据我所知’并非绝对必要,并且会使代码有些混乱。当然,我的测试表明代码仍然可以正常工作,并且args / sender变量仍传递给了回调,这就是我’d wondered about.
    -的 SP.CamlQuery.createAllItemsQuery() 该方法在客户端OM中提供了一个方便的快捷方式,可以为列表中的所有项目创建CAML查询。

  3.  通过查询获取列表项(提前输入)

    让’把最后一个例子变成更有用和更酷的东西–页面上的项目列表,当您在框中输入文件名时会动态过滤。这是一种非常有用的模式,对于您可能已经看到的许多很酷的事物,例如,基本上是相同的机制。 Google即时搜索。实际上,我们只需要更改几件事即可实现此目的–而不是按钮单击事件,我们响应文本框’s 键控 事件,而不是返回所有项目的CAML查询,我们需要CONTAINS查询,该查询将值从文本框中拖放到子句中:

    客户端OM_Demo3_On1
    客户端OM_Demo3_On2
    <fieldset id="fldDemo3">
        <legend>Demo 3 - fetching list items 与 query</legend>
        <div id="demo3Row" class="demoRow">
                <div class="demoControls">
                    <label for="txtFilenameContains">Filename contains:</label>
                    <input type="text" id="txtFilenameContains" />
                </div>
                <div class="demoResults">
                    <span id="demo3Result" />
                </div>
                <div class="clearer" />
        </div>
    </fieldset>
    <script type="text/javascript">
        var selectedDocs;
     
        $('#txtFilenameContains').keyup(function (event) {
            filterDocs();
        });
     
        function filterDocs() {
            var ctx = new SP.ClientContext.get_current();
     
            var docLib = ctx.get_web().get_lists().getByTitle('Shared Documents');
            var query = new SP.CamlQuery();
            query.set_viewXml("<View><Query><Where><Contains><FieldRef Name='FileLeafRef'/><Value Type='Text'>" + $('#txtFilenameContains').val() + "</Value></Contains></Where></Query></View>");
     
            selectedDocs = docLib.getItems(query);
            ctx.load(selectedDocs);
     
            ctx.executeQueryAsync(getDocsWithQuerySuccess, getDocsWithQueryFailure);
        }
     
        function getDocsWithQuerySuccess(sender, args) {
            $('#demo3Result').empty();
            var listEnumerator = selectedDocs.getEnumerator();
            while (listEnumerator.moveNext()) {
                $('#demo3Result').append(listEnumerator.get_current().get_item("FileLeafRef") + '<br />');
            }
        }
     
        function getDocsWithQueryFailure(sender, args) {
            alert('Failed to get list items. \nError: ' + args.get_message() + '\nStackTrace: ' + args.get_stackTrace());
        }
    </script>

  4.  将新列表项添加到列表

    向SharePoint添加新数据通常围绕创建一个 某事创作信息 对象(例如 WebCreationInformation,ListCreationInformation 等),然后填充项目的值,然后调用 .update().executeQueryAsync()。一世n this example I’m using the ListItemCreationInformation 对象,将新项目添加到‘Tasks’根据在文本框中输入的标题列出当前网络中的列表:

    客户端OM_Demo4_Off
    客户端OM_Demo4_On1
    ClientOM_Demo4_Result
    <fieldset id="fldDemo4">
        <legend>Demo 4 - add list items</legend>
        <div id="demo4Row" class="demoRow">
            <div><span class="demoLabel">Task title:</span><input id="txtTaskTitle" type="text" /></div>
            <div><button id="btnAddTask" type="button">Add task</button></div>
            <div><span class="demoLabel">Result:</span><span id="addResult" /></div>
        </div>
    </fieldset>
    <script type="text/javascript">
        var newTask;
     
        $('#btnAddTask').click(function () {
            var taskTitle = $('#txtTaskTitle').val();
            var taskDesc = $('#txtTaskDescription').val();
     
            var ctx = new SP.ClientContext.get_current();
            var taskList = ctx.get_web().get_lists().getByTitle('Tasks');
            // use ListItemCreationInformation to provide values..
            var taskItemInfo = new SP.ListItemCreationInformation();
            newTask = taskList.addItem(taskItemInfo);
            newTask.set_item('Title', taskTitle);
            // could set other fields here in same way..
            newTask.update();
     
            ctx.load(newTask);
            ctx.executeQueryAsync(addTaskSuccess, addTaskFailure);
     
            function addTaskSuccess(sender, args) {
                $('#addResult').html("Task " + newTask.get_item('Title') + " added to the 任务 list");
            }
     
            function addTaskFailure(sender, args) {
                alert('Failed to add new task. \nError: ' + args.get_message() + '\nStackTrace: ' + args.get_stackTrace());
            }
        });
    </script>

性能/编写高效的客户端OM代码

既然我们了解了JavaScript 客户OM的基础,我们应该知道我们如何编写代码会对性能产生巨大影响。前面我们提到您应该只打电话给 ClientContext.Load() 在您将实际使用的对象上,始终出色的Steve Peschka提到了这一点 他的系列着重于 管理 客户OM。本质上,您调用.Load()的对象越多,越多的数据通过网络传递给客户端–我看这个的首选方式是 萤火虫 (Firefox附加组件),但Fiddler也可以正常工作。两者都将显示JSON格式的响应(N.B. JSON是我在本系列稍后讨论的内容):

查看JsonResponse
那么什么样的事情会有所作为呢?好吧,主要有两个:

  • 在它的对象上调用.Load()’s not needed
  • 返回的属性(例如列表项的字段)多于所需的属性

首先,请考虑在获取某些列表项的方式中,我们可以编写一些代码以两种方式获取列表:

// bad way..
var web = ctx.get_web();
var lists = ctx.get_web().get_lists();
var targetList = lists.getByTitle('Shared Documents');
ctx.load(web);
ctx.load(lists);
ctx.load(targetList);
/* although we can't see the surrounding code, the 'web' 和 'lists' objects 
   actually weren't used on the client for anything.. */
// better way:
var targetList = ctx.get_web().get_lists().getByTitle('Shared Documents');

在这两种情况下,我们稍后都会 ctx.load(listItems) 线。但是,在第二种情况下,Web,列表和目标列表的JSON不会通过网络发送–实际列表项仅使用JSON。这将大大减少数据(I’稍后再显示数字)。

对于第二个(为对象返回更多的属性),这显然与 SQL选择 * vs SELECT [mySingleColumn] 查询。我们避风港’尚未显示如何过滤返回的属性,但是’s 非常 important – first let’看一下到目前为止我们一直在做什么:

var query = SP.CamlQuery.createAllItemsQuery();
demo5listItems = targetList.getItems(query);
// bad way - requesting all properties here..
ctx.load(demo5listItems);

限制查询中返回的字段的语法 的JavaScript 客户端OM看起来像这样– here I’m仅获取文件名:

var query = SP.CamlQuery.createAllItemsQuery();
demo5listItems = targetList.getItems(query);
// better way:
ctx.load(demo5listItems, 'Include(FileLeafRef)');

When I was testing the different code 模式s, this is what I saw –注意我只在查询返回8个项目的测试:

图片

我的观察结果是:

  • 这些数字都不是令人恐惧的,但是接下来我们’在这里只谈论8个项目!但是,比例是 非常 有趣的是,如果(说)退回100件商品,那么显然数据量意味着 正确编写代码的好处
  • 比例-最大的数据集是 15次 最小的尺寸
  • 客户端OM中的自动压缩可节省您的资产。的‘Uncompressed size’列在技术上是不相关的,因为数据是 总是 电线上压缩–但是在某些时候它已经解压缩了,我们可以看到这里的数据到底有多大(8个列表项的61KB确实非常糟糕)。

所以重申一下,只打电话 ClientContext.Load() 在您需要的对象上,并确保限制返回的属性。希望您能看到Client OM和jQuery是构建SharePoint应用程序的好工具,’t postback hell.

下次: 结合使用jQuery AJAX和HTTP处理程序

4条评论:

安迪·邦斯(Andy Buns)说过...

您提到了压缩-是Web服务器压缩,还是客户端对象模型完成了某些工作?'s own? (I can'想象不到JavaScript中的解压缩效果很好)

克里斯·奥'Brien说过...

@安迪

It'这个问题很好,我不知道'不知道细节。我认为它'是Client.svc WCF服务的功能(即在实现中的代码中完成),尽管我注意到默认情况下,托管目录的IIS压缩处于打开状态。

无论如何,我'd确信让客户端需要解压缩有效负载最终比通过有线传输大量数据更好。当然,我在键入时过滤列表的示例反应足够快,并且每次击键都会调用一次。

干杯,

克里斯。

匿名 said...

嘿,克里斯。我有人在这里提到您的压缩评论 SPServices网站上的线程。一世'm not sure what you'重新引用。您只是在谈论GZIP,还是在客户端OM流量中发生了其他事情?我不'没有简单的测试方法。

谢谢,
M.

克里斯·奥'Brien说过...

嘿马克

我刚刚检查了一下,实际上它是本机浏览器的GZIP压缩。您可以通过在Fiddler / 萤火虫中隔离来自客户端OM调用的响应并查看Content-Encoding来判断。

感谢您的提示,我've一直打算去看那个;)

HTH,

克里斯。