MVC 3,部分视图和jquery的复用,不冲突DOM

Posted

技术标签:

【中文标题】MVC 3,部分视图和jquery的复用,不冲突DOM【英文标题】:MVC 3, reuse of partial views and jquery, without conflicting the DOM 【发布时间】:2011-10-21 11:09:03 【问题描述】:

由于我还是 MVC 3 和 jquery 的新手,我想知道如何解决以下问题的最佳实践解决方案:

我有一个视图,我使用 jquery ajax 获取并显示包含产品 A 的一些产品详细信息的部分视图。加载的部分视图由一堆 html 和 jquery 代码组成,这些代码与定义的 id 相关联局部视图。

因此,我想重用相同的局部视图以在同一视图上显示其他产品的详细信息(例如,在弹出对话框中显示产品 B 的详细信息)。每当显示弹出窗口时,新获取的部分视图将与产品 A 的部分视图冲突,因为在 html 中使用了相同的 id。

有没有办法将 html 和 javascript 封装在局部视图中,并在多个页面中重复使用它而不用担心与 ID 和其他东西有任何冲突?

我希望我的问题是有道理的。谢谢,

/尼玛

更新

这是一些伪代码,概述了我的问题:

查看

<script type="text/javascript">
$(document).ready(function () 

    $('.productItems').click(function () 
        var input =  productId: $(this).attr('data-productID') ;
        var url = url = '<%: Url.Content("~/ProductDetails/ShowProductDetails") %>';


        // Show the modal box with product details
        $('#dialogBox').dialog(
            title: $(this).attr('data-productTitle')
        );


        // Fetch content in the background
        $.get(url, input, function (result, response) 
            $('#dialogBox').html(result);
            );
    );
);
</script>


<div id="detailsArea">
    <% Html.RenderPartial("ProductDetails", Model.Product); %>
</div>

<div id="productLinks">
  <span class="productItems" data-productID="123">Product B</a>
</div>

<div id="dialogBox" style="display: none;"></div>

控制器 -> 动作(ShowProductDetails)

public ActionResult ShowProductDetails(int productId)

  // Get product from db. and return the partial view

  return PartialView("ProductDetails", p);

局部视图(产品详情)

<script type="text/javascript">

   function SetProductTabContent(selectedTab) 
        $("#productDescriptionContent > div").css('display', 'none');

        switch (selectedTab) 

            case '#tab-1':
                $('#productDescriptionText').css('display', 'block');
                break;

            case '#tab-2':
                $('#productSpecificationText').css('display', 'block');
                break;   
        


$(document).ready(function () 
    // Get all the menu items
    var menuItems = $("#productMenu a");

    // Select the first tab as default
    menuItems.first().addClass("menuItemActive");

    // Handle the look of the tabs, when user selects one. 
    menuItems.click(function () 

        var item = $(this);

        // Get content for the selected tab
        SetProductTabContent(item.attr('href'));

        menuItems.removeClass("menuItemActive");
        item.addClass("menuItemActive");
        return false;
    );
);
</script>


<div id="productMenu" style="">
    <a href="#tab-1">
        <div class="menuItemHeader">Menu1</div>
    </a>
    <a href="#tab-2">
        <div class="menuItemHeader">Menu2 </div>
    </a>
</div>


<div id="productDescriptionContent">

        <div id="productDescriptionText" style="display: none;">
            <%: Model.Product.Description %>
        </div>
        <div id="productSpecificationText" style="display: none;">
            <%: Model.Product.Description2%>
        </div>
</div>

问题 当局部视图在 DOM 中加载两次时,div 会发生冲突。

【问题讨论】:

在 iframe 中显示对话框的内容怎么样? 【参考方案1】:

是的。正如您所指出的,不要在 JavaScript 中使用 id 和 id 选择器。而是使用类选择器:

例如,在您视图的标记中:

<div class="container">Partial View content</div>

JS:

var $div = $('div.container');
// do something

为了消除选择具有相同类名的其他标签的可能性,请为局部视图中的元素指定一个编程名称,该名称仅用作选择器句柄而不用作 CSS 类。

虽然基于 ID 的查找是最好的性能,但在这种情况下,通过基于 [tag+class] 的查找来避免 id 冲突更有意义。在性能方面,基于 [tag+class] 的查找非常接近 id 选择器。

此外,您还可以通过限制查找范围来获得进一步的改进:

<div class="container">Partial View content <span class="child">Child content </span></div>

var $span = $(span.child')  // scope of lookup here is entire document

但是,如果您知道 child 在容器 div 内,您可以通过以下方式限制范围:

var $div = $('div.container').children('span.child'); // or just '.child'

另一个技巧是进行一次查找并重复使用它:

// less performant
function doSomething() 

    // do something here
    $('div.container').css('color', 'red');

    // do other things
    $('div.container').find('.child');

   // do more things
    $('div.container').click(function() ...);



// better
function doSomething() 
    var $div = $('div.container');

    // do something here
    $div.css('color', 'red');

    // do other things
    $div.find('.child');

   // do more things
    $div.click(function() ...);

   // or chaining them when appropriate
   $('div.container').css('color', 'red').click(function()  ... );



更新:重构 OP 的帖子以演示该概念:

<script type="text/javascript">

       function SetProductTabContent(selectedTab, ctx) 
            var $container = $("div.pv_productDescriptionContent", ctx);

            // this will find only the immediate child (as you had shown with '>' selector)
            $container.children('div').css('display', 'none');  

            switch (selectedTab) 

                case '#tab-1':
                    $('div.pv_productDescriptionText', $container).css('display', 'block');
                    // or $container.children('div.pv_productDescriptionText').css('display', 'block');
                    break;

                case '#tab-2':
                    $('div.pv_productSpecificationText', $container).css('display', 'block');
                    // or $container.children('div.pv_productSpecificationText').css('display', 'block');
                    break;   
            


    function SetUpMenuItems(ctx) 
        // Get all the menu items within the passed in context (parent element)
        var menuItems = $("div.pv_productMenu a", ctx);

        // Select the first tab as default
        menuItems.first().addClass("menuItemActive");

        // Handle the look of the tabs, when user selects one. 
        menuItems.click(function () 

            var item = $(this);

            // Get content for the selected tab
            SetProductTabContent(item.attr('href'), ctx);

            menuItems.removeClass("menuItemActive");
            item.addClass("menuItemActive");
            return false;
        );
    
    </script>


<div style="" class="pv_productMenu">
    <a href="#tab-1">
        <div class="menuItemHeader">
            Menu1</div>
    </a><a href="#tab-2">
        <div class="menuItemHeader">
            Menu2
        </div>
    </a>
</div>
<div class="pv_productDescriptionContent">
    <div class="pv_productDescriptionText" style="display: none;">
        <%: Model.Product.Description %>
    </div>
    <div class="pv_productSpecificationText" style="display: none;">
        <%: Model.Product.Description2%>
    </div>
</div>

注意:我删除了 document.ready 包装器,因为当您加载局部视图时它不会触发。相反,我重构了您的 View 的 JS 以调用 setup 函数并传入范围(这将避免选择具有相同类的其他 div):

// Fetch content in the background
$.get(url, input, function (result, response) 
       $('#dialogBox').html(result);
       SetUpMenuItems($('#dialogBox'));   
);

显然,您可以在您认为适合您的应用的情况下进一步修改它,我所展示的只是一个想法,而不是最终解决方案。

如果您再次加载#dialog,它们将覆盖现有标记,因此不会重复。 如果您在其他容器中再次加载部分视图,您可以将其作为上下文传递,这将阻止您访问 #dialogchildren 我想出了这个任意前缀pv_ 用于编程类句柄。这样,您可以通过查看类名来判断它是用于 CSS 还是用于您的脚本。

【讨论】:

类选择器的问题是,如果我例如对产品 B 执行一些操作(在弹出窗口中),产品 A 也会发生同样的情况。这两者不应相互依赖。 尝试使用 data-* HTML5 样式并根据您的新属性选择那些样式。点赞 data-product="1" 这就是范围查找有帮助的地方。您可以在一定范围内查找。此外,您可以命名您的选择器,使它们是唯一的:class="partial_view_main_container"。这不必是用于样式设置的 CSS 类,而只是用于查找局部视图的内容。 我仍然看不到您的解决方案将如何工作。我添加了一些代码 sn-p,希望它能让我的问题更清楚。 @Miroprocessor 的答案很有意义,因为使用了 html 的 ids 部分。但是我想知道是否有更好的解决方案,因为您使用 id 作为 html 的一部分来编写问题。 我已经在一个大型项目中成功使用了它。如果您可以为您的局部视图和示例 JS 发布一些标记来展示您将如何访问元素,我可以向您展示。【参考方案2】:

最简单的方法是,将产品的 ID 作为 html 标签 ID 的一部分

类似的东西

<input type="text" id="txt_<%=Model.ID%>"> 

如果你使用Razor

<input type="text" id="txt_@Model.ID"> 

【讨论】:

从长远来看,这是一种混乱的方法。几个例子:1)你有责任每次生成唯一的ID(所以在请求之间,你需要以某种方式保留它的标签)。 2)您还需要以某种方式在您的javascript中注入它(除非您正在编写内联脚本)。 3)您的 javscript 不能进行单元测试,因为它依赖于动态 geberated ids。 嗯,在 html 中包含 id 实际上是有意义的。但我@Mrchief 提出的所有观点也是有道理的。但这真的是这样做的方法吗? @Nima 这只是在局部视图加载两次时防止 div 之间冲突的最简单方法,但还有许多其他方法可以做同样的事情 虽然问题中没有提到,但如果您使用 MVC 的内置数据绑定(例如 Html.TextboxFor()),手动设置 id 会破坏返回的数据绑定。【参考方案3】:

我很惊讶这种情况没有更频繁地出现。我猜大多数开发人员并没有使用部分创建自己的控件。这是我带来的东西,效果很好,而且在 MVC4 中实现起来并不难。

首先,我尝试将动态模型传递给局部模型,但这不起作用。 (悲伤的脸) 然后我去了键入的部分视图路线。我创建了一个称为步骤的对象集合(因为构建了一个向导控件)。

public class WizardStep

    public string Id  get; set; 
    public string Class  get; set; 


public class WizardSteps


    public WizardSteps()
    
        Steps = new List<WizardStep>();
        Steps.Add(new WizardStep() Id = "Step1");
        Steps.Add(new WizardStep()  Id = "Step2" );
        Steps.Add(new WizardStep()  Id = "Step3" );
        Steps.Add(new WizardStep()  Id = "Step4" );
        Steps.Add(new WizardStep()  Id = "Step5" );

    
    public List<WizardStep> Steps  get; set; 


Razor 代码如下所示:

@Html.Partial("_WizardButtonPanel", @Model.WizardSteps.Steps.First())

@Html.Partial("_WizardButtonPanel", @Model.WizardSteps.Steps.Skip(1).First() )

@Html.Partial("_WizardButtonPanel", @Model.WizardSteps.Steps.Skip(2).First())

还有更多

@Html.Partial("_WizardButtonPanel",@Model.WizardSteps.Steps.Skip(3).First())

局部视图看起来像这样:

@model SomeProject.Models.WizardStep
<div id="buttonpanel-@Model.Id" >
<a id="link-@Model.Id" >Somelinke</a>
</div>

编码愉快...

【讨论】:

使用类选择器查找 BTW 会导致向导类型控件出现问题。

以上是关于MVC 3,部分视图和jquery的复用,不冲突DOM的主要内容,如果未能解决你的问题,请参考以下文章

快速回顾,浅谈mvc思想

MVC 3 - 控制器和视图模型 - 哪个应该包含大部分业务逻辑?

MVC 3 防止部分视图缓存

关于ASP.NET MVC部分视图渲染问题。

消除部分视图 MVC 3 razor 中的重复 ID

在 MVC 3 中禁用局部视图上的缓存