为jquery模板添加包含html的脚本(append script containing html for jquery template)

当我尝试解释这个时,请耐心等待,因为它有点复杂,我对JQuery完全不熟悉并试图了解它。

我和我的团队正在为一个SharePoint项目开发WebParts。 WebPart包含一个用户控件(SearchControl),它有一个链接打开一个包含另一个用户控件(SelectorControl)的对话框(使用jquery ui)。

SelectorControl包含:

使用JQuery模板填充的下拉列表(objTemplates) 使用另一个JQuery模板填充的表; 和 一个只读文本输入,用于显示表中当前选定的项目

SelectorControl代码:

<div class="objSelector"> <script id="objSelectorTemplate" type="text/x-jquery-tmpl"> <div class="table"> <div class="row"> <div class="labelInputGroup"> <label for="objTemplates">Obj Template</label> <select id="objTemplates"> {{each ObjectTemplates}} <option value="${ObjectTemplateId}">${Name}</option> {{/each}} </select> </div> </div> <div><br></div> <div class="row"> <div> <table class="tableGrid"> <thead> <tr> <th>Object Name</th> </tr> </thead> <tbody id="listPlaceholder"> </tbody> </table> <div id="pagerPlaceholder"></div> </div> </div> <div id="templatePlaceholder"> </div> <div><br></div> <div class="row"> <input type="hidden" id="localSelectedObjectId" class="hidden" /> <label for="localSelectedObject">Selected Object</label> <input type="text" class="dealName" id="localSelectedObject" readonly="readonly" placeholder="No object selected"/> </div> </div> </script> <script id="listTemplate" type="text/x-jquery-tmpl"> <tr> <td><a class="objectItem" id="${Id}" href="#">${Name}</a></td> </tr> </script> <div id="pagerControl"> <uc1:PagerControl ID="PagerControl1" runat="server" /> </div> </div>

在使用从WCF服务检索的数据创建对话框之前填充下拉列表,该服务随后绑定到第一个模板。

“更改”事件绑定到下拉列表,使用live来填充表(第二个模板),方法是调用WCF服务并传递下拉列表中当前选定的项。

在下拉列表中触发更改事件时,将调用LoadObject:

function LoadObjects(event) { var dialogDom = event.data.DialogDom; var searchObj = GetSearchFilters(); // removed irrelevant code here var listTemplate = searchDom.find('#listTemplate'); var templatePlaceholder = dialogDom.find('#templatePlaceholder'); templatePlaceholder.empty(); templatePlaceholder.append(listTemplate); var pagerControl = searchDom.find('#pagerControl'); var pagerPlaceholder = dialogDom.find('#pagerPlaceholder'); pagerPlaceholder.append(pagerControl); var list = GenerateList( dialogDom, $('<div></div>'), searchObj, Connection('MyUiService', 'GetObjects')); }

GenerateList()方法使用各种方法创建一个JQuery对象,包括从WCF服务获取对象数据并将其绑定到模板的方法(listTemplate)

PagerControl代码还包含一个模板,该模板与listTemplate同时填充。

listTemplate和pager的代码必须放在objectSelectorTemplate脚本之外,然后在以后复制,以防止在解析页面以填充下拉列表时删除它。

以上所有工作都很好,第一次打开对话框并从下拉列表中选择一个项目。 可以在没有任何问题的情况下更改表中数据中的页面,但只要下拉列表中的所选项发生更改,就不再加载表数据。 如果我单步执行代码,我可以看到append()(在LoadObjects方法中)将listTemplate脚本插入到listPlaceholder时会删除它,这意味着当对话框再次加载表数据时,脚本不再是那里。 为了解决这个问题,我尝试添加一个克隆:

templatePlaceholder.append(listTemplate.clone());

这成功阻止了listTemplate脚本被删除,但是,克隆的副本包含脚本标记但不包含其内容,因此表数据仍未加载。 我搜索了SO并找到了John Resig对这个问题的回答 :

所以我将上面的代码更改为:

var listTemplateCopy = jQuery.extend({}, listTemplate); templatePlaceholder.append(listTemplateCopy);

这成功地将脚本标记及其内容复制到listTemplateCopy,但append()删除了listTemplate以及listTemplateCopy。 起初我假设这是因为extend用于将两个(或更多)对象合并到一个对象中,但是从John的响应和我在文档中读到的内容,它听起来像是你应该使用的扩展创建jquery对象的副本。

任何人都可以向我解释到底发生了什么,并推荐上述解决方案?

我有几个想法是:

将脚本标记移动到仅包围模板代码。 然后我可以使用第二个模板代码,而不是需要附加它。 绑定将通过绑定到下拉列表模板然后绑定到表来完成 使用不同的方式填充下拉列表而不是模板来简化页面并删除“必要性”以附加脚本,因为整个批次周围不会有脚本标记

在采用这些方法之前,我想了解代码目前正在做什么。

Please bear with me as I try to explain this as it's somewhat complicated and I am totally new to JQuery and trying to get my head around it.

My team and I are working on WebParts for a SharePoint project. The WebPart contains a user control (SearchControl), which has a link to open a dialog (using jquery ui) that contains another user control (SelectorControl).

SelectorControl contains:

a drop-down list (objTemplates) that is populated using JQuery Templates a table that is populated using another JQuery Template; and a readonly text input to display the currently selected item from the table

SelectorControl code:

<div class="objSelector"> <script id="objSelectorTemplate" type="text/x-jquery-tmpl"> <div class="table"> <div class="row"> <div class="labelInputGroup"> <label for="objTemplates">Obj Template</label> <select id="objTemplates"> {{each ObjectTemplates}} <option value="${ObjectTemplateId}">${Name}</option> {{/each}} </select> </div> </div> <div><br></div> <div class="row"> <div> <table class="tableGrid"> <thead> <tr> <th>Object Name</th> </tr> </thead> <tbody id="listPlaceholder"> </tbody> </table> <div id="pagerPlaceholder"></div> </div> </div> <div id="templatePlaceholder"> </div> <div><br></div> <div class="row"> <input type="hidden" id="localSelectedObjectId" class="hidden" /> <label for="localSelectedObject">Selected Object</label> <input type="text" class="dealName" id="localSelectedObject" readonly="readonly" placeholder="No object selected"/> </div> </div> </script> <script id="listTemplate" type="text/x-jquery-tmpl"> <tr> <td><a class="objectItem" id="${Id}" href="#">${Name}</a></td> </tr> </script> <div id="pagerControl"> <uc1:PagerControl ID="PagerControl1" runat="server" /> </div> </div>

The drop-down list is populated just before the dialog is created using data retrieved from a WCF service that's then bound to the first template.

A 'change' event is bound to the drop-down list using live to populate the table (second template) by calling a WCF Service and passing through the currently selected item in the drop-down list.

When the change event is triggered on the drop-down list, LoadObjects is called:

function LoadObjects(event) { var dialogDom = event.data.DialogDom; var searchObj = GetSearchFilters(); // removed irrelevant code here var listTemplate = searchDom.find('#listTemplate'); var templatePlaceholder = dialogDom.find('#templatePlaceholder'); templatePlaceholder.empty(); templatePlaceholder.append(listTemplate); var pagerControl = searchDom.find('#pagerControl'); var pagerPlaceholder = dialogDom.find('#pagerPlaceholder'); pagerPlaceholder.append(pagerControl); var list = GenerateList( dialogDom, $('<div></div>'), searchObj, Connection('MyUiService', 'GetObjects')); }

The GenerateList() method creates a JQuery object with various methods including ones that get the objects data from a WCF service and bind it to the template (listTemplate)

The PagerControl code also contains a template, which is populated at the same time as listTemplate.

The code for listTemplate and the pager had to be put outside of the objectSelectorTemplate script and then copied in later to prevent it from being removed when the page is parsed for the populating of the drop-down list.

All of the above works fine the first time the dialog is opened and an item is selected from the drop-down list. It is possible to change pages within the data in the table without any problem but as soon as the selected item in the drop-down list changes, the table data is no longer loaded. If I step through the code, I can see that append() (in the LoadObjects method) removes the listTemplate script when it inserts it into listPlaceholder, which means that when the dialog goes to load the table data again, the script is no longer there. To get around this, I tried appending a clone instead:

templatePlaceholder.append(listTemplate.clone());

This successfully prevented the listTemplate script from being removed, however, the cloned copy contained the script tags but not its contents so the table data still didn't get loaded. I searched through SO and found John Resig's response to this question:

So I changed the above code to:

var listTemplateCopy = jQuery.extend({}, listTemplate); templatePlaceholder.append(listTemplateCopy);

This successfully copied the script tag and its contents to listTemplateCopy but append() removes listTemplate as well as listTemplateCopy. At first I assumed this was because extend is intended for merging two (or more) objects into a single object but from John's response and from what I've read in the documentation, it sounded like extend is what you're supposed to use to create a copy of a jquery object.

Can anyone explain to me what exactly is going on and recommend a solution to the above?

A couple of ideas I had was to:

shift the script tags to only surround the template code. I could then have the second template code in place rather than needing to append it. Binding would be done by binding to the drop-down list template then to the table use a different manner to populate the drop-down list rather than a template to simplify the page and remove the "necessity" to have scripts being appended, as there wouldn't be a script tag surrounding the whole lot

I would like to gain an understanding of exactly what the code is currently doing though before pursuing either of those approaches.

最满意答案

这绝不是一个优雅的答案,但实质上,这是IE8的脚本引擎及其前身的失败。 如果您的项目有要求支持这些浏览器,那么代码将会更加难以理解,jQuery将不会有太大帮助。 您会注意到您在代码中首次使用.clone()实际上在IE9,Firefox,Chrome和Safari中运行良好。 请允许我解释一下这个特殊的困难。

您的要求是复制DOM对象。 $.extend()在这个实例中是不合适的,因为它将简单地克隆包装DOM目标集的jQuery对象,而不是实际的DOM目标。 这意味着包装器的新副本仍然具有相同的DOM元素,而不是附加目标的副本,而是移动目标。 出于这个原因,您最初使用.clone()假设是正确的。 代码无法按预期运行的原因超出了您的控制范围。

IE9使用其前身的Node.cloneNode()方法纠正了明显的缺陷,其中在script元素的情况下不保留Node对象的innerHTML成员。 这是jQuery的.clone()调用使用的方法。 在IE8及更早版本的任何脚本块上执行一系列经典JavaScript将显示:

alert(document.getElementById("listTemplate").cloneNode(true).innerHTML);

此警报将包含所有其他主要浏览器中的内容。 在IE8及以下版本中并非如此。 “ true ”参数执行深层复制只是为了证明一个观点,但实际上这并不是必需的,因为script元素在其childNodes集合中没有Nodes 。

解决方法是丑陋的,我不完全确定它将按预期运行,因为我没有你的系统进行测试。 您必须显式修改占位符元素的innerHTML属性。 你可以考虑的一件事是包括一个defer属性,当动态插入IE时,它会使脚本块表现得更好(参见MSDN - innerHTML属性 ,“当使用innerHTML插入脚本时,你必须在脚本元素中包含DEFER属性” )。 这将使您的JavaScript看起来像以下内容:

var listTemplate = searchDom.find('#listTemplate'); var myHtml = '<script defer="defer" type="text/x-jquery-tmpl">'; myHtml += listTemplate.html(); myHtml += "</script" + ">"; var templatePlaceholder = dialogDom.find('#templatePlaceholder'); templatePlaceholder.html(myHtml);

我已经假设listTemplate和templatePlaceholder是jQuery对象,所以如果我弄错了,我道歉。 但是,这里的基本思想是将整个脚本块构建为HTML字符串,并用您构建的字符串替换占位符中的HTML。

我无法保证IE会正确解释脚本块并使其可用于您的jQuery模板,但检查我选择的受害者占位符的innerHTML确实表明,至少它已被生成的脚本块正确替换。

我有兴趣知道这是否有效。

This is not by any means going to be an elegant answer, but essentially, this is a failing of IE8's scripting engine and its predecessors. If your project has requirements that these browsers are supported, then the code's going to get a bit uglier to look at and jQuery won't be much help. You'll notice your first use of .clone() in code actually functions just fine in IE9, Firefox, Chrome, and Safari. Allow me to explain the particular difficulty.

Your requirement is to replicate a DOM object. $.extend() is inappropriate in this instance as it will simply clone a jQuery object that wraps a DOM target set, rather than the actual DOM target(s). That means that the new copy of the wrapper still has the same DOM element(s) targeted, and instead of appending a copy of the target(s), it moves the target(s). For this reason, your initial assumption to use .clone()was correct. The reason the code is failing to behave as expected is beyond your control.

IE9 corrects a glaring deficiency with the Node.cloneNode() method of its predecessors where the innerHTML member of the Node object is not preserved in the case of script elements. This is the method used by jQuery's .clone() call. Executing a line of classic JavaScript on any script block in IE8 and earlier will reveal as much:

alert(document.getElementById("listTemplate").cloneNode(true).innerHTML);

This alert will have content in every other major browser. Not so in IE8 and below. The "true" argument performs a deep copy just to prove a point, but truthfully this is not necessary as script elements have no Nodes in its childNodes collection.

The workaround is ugly and I'm not entirely certain it will function as intended since I don't have your system to test with. You will have to explicitly modify the innerHTML attribute of your placeholder element. One thing you might consider is including a defer attribute which tends to make script blocks behave better when dynamically inserted in IE (see MSDN - innerHTML Property at, "When using innerHTML to insert script, you must include the DEFER attribute in the script element"). It would make your JavaScript look something like the following:

var listTemplate = searchDom.find('#listTemplate'); var myHtml = '<script defer="defer" type="text/x-jquery-tmpl">'; myHtml += listTemplate.html(); myHtml += "</script" + ">"; var templatePlaceholder = dialogDom.find('#templatePlaceholder'); templatePlaceholder.html(myHtml);

I've made assumptions that listTemplate and templatePlaceholder are jQuery objects so if I am mistaken, I apologize. However, the basic idea here is literally building the full block of script as an HTML string and replacing the HTML inside your placeholder with the string you've built.

I cannot guarantee IE will interpret the script block correctly and make it available to your jQuery template, but inspection of the innerHTML of a victim placeholder of my choosing did reveal that, at the very least, it was replaced correctly with the generated script block.

I'd be interested to know if this works.

更多推荐