问题
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.

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.
回答1:
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.
回答2:
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="txt_@Model.ID">
回答3:
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="buttonpanel-@Model.Id" >
<a id="link-@Model.Id" >Somelinke</a>
</div>
Happy coding...
来源:https://stackoverflow.com/questions/6966850/mvc-3-reuse-of-partial-views-and-jquery-without-conflicting-the-dom