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.
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.
#dialog
again, they will overwrite existing markup, hence there won't be duplicate.children
of #dialog
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.