MVC 3,重用部分视图和jquery,不与DOM冲突

发布于 2024-11-28 00:30:33 字数 3448 浏览 1 评论 0原文

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

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

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

案例概念概述

有没有一种方法可以将 html 和 javascript 封装在局部视图中,并在多个页面中重复使用它,而无需担心ID之类的东西有冲突吗?

我希望我的问题有意义。谢谢,

/Nima

更新

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

VIEW

<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>

Controller ->操作(显示产品详细信息)

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 会发生冲突。

As i am still new to MVC 3 and jquery, i would like to know a best practice solution to how the following can be solved:

I have a view, where I use jquery ajax to fetch and display a partial view with some product details for product A. The loaded partial view consist of a bunch of html and jquery code, which is tied to the defined id's within the partial view.

Thus, i would like to reuse the same partial view to show details from other products on the same View (e.g. show product B details in a pop-up dialog). Whenever the pop-up is shown, the newly fetched partial view will conflict with the partial view for product A, as the same id's are used in the html.

Conceptual overview of the case

Is there a way to encapsulate the html and javascript in the partial view, and reuse it several pages without worry about any conflicts with ID's and stuff?

I hope my question makes sense. Thanks,

/Nima

UPDATED

Here is some pseudo code, outlining my issue:

VIEW

<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>

Controller -> Action (ShowProductDetails)

public ActionResult ShowProductDetails(int productId)
{
  // Get product from db. and return the partial view

  return PartialView("ProductDetails", p);
}

Partial View (ProductDetails)

<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>

ISSUE
When the partial view gets loaded twice in the DOM, the divs conflicts.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

初见 2024-12-05 00:30:33

是的。正如您所指出的,不要在 JavaScript 中使用 ids 和 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 还是用于脚本中。

Yes. As you pointed out, do not use ids and id selectors in your JavaScript. Instead use class selectors:

E.g., in your view's markup:

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

JS:

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

To eliminate possibility of selecting other tags with same class name, assign a programmatic name the elements in partial view which is used only as a selector handle and not as a CSS class.

While ID based lookup is the best performance wise, in this case, it makes more sense to go by the [tag+class] based lookup to avoid id conflicts. [tag+class] based lookup comes pretty close to id selectors in terms of performance.

Also, you can gain further improvement by limiting the lookup scope:

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

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

However, if you know that child is inside container div, you can limit the scope by saying:

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

Another tip is to do the lookup once and reuse it:

// 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() { ... });


}

Update: Refactoring OP's post to demo the concept:

<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>

Note: I removed document.ready wrapper since that will not fire when you load the partial view. Instead, I refactored your View's JS to call the setup function and also pass in the scope (which will avoid selecting other divs with same class):

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

Obviously, you can modify this further as you deem fit in your app, what I've shown is an idea and not the final solution.

  • If you load #dialog again, they will overwrite existing markup, hence there won't be duplicate.
  • If you load the partial view again in some other container, you can pass that as the context and that will prevent you accessing the children of #dialog
  • I came up with this arbitrary prefix pv_ for programmatic class handles. That way, you can tell looking at the class name if it is for CSS or for use in your script.
樱花坊 2024-12-05 00:30:33

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

类似于id

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

如果您使用 Razor,则

<input type="text" id="[email protected]"> 

Simplest way to do that is, make ids of products as part of html tags ids

something like that

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

if you use Razor

<input type="text" id="[email protected]"> 
旧人九事 2024-12-05 00:30:33

我很惊讶这种情况并没有更频繁地出现。我想大多数开发人员都不会使用部分内容创建自己的控件。这是我带来的东西,它运行得很好,并且在 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())

or

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

or

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

and more

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

部分视图如下所示:

@model SomeProject.Models.WizardStep
<div id="[email protected]" >
<a id="[email protected]" >Somelinke</a>
</div>

快乐编码...

I'm surprised this hasn't come up more often. I guess most developers aren't creating their own controls with partials. Here is something that I came with that is works pretty well and not that hard to implement in MVC4.

First I tried passing a dynamic model to the partial and that doesn't work. (sad face)
Then I went the typed partial view route. I created a collection of objects called steps (because build a wizard control).

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 code looks like this:

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

or

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

or

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

and more

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

The partial view looks something like this:

@model SomeProject.Models.WizardStep
<div id="[email protected]" >
<a id="[email protected]" >Somelinke</a>
</div>

Happy coding...

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文