I am looping through an object passed to the View (I'm using ASP.NET MVC 3) and creating a Nav Tab using Bootstrap.
At the moment, the actual TABS work and render fine. The tab-content however doesn't get displayed - Or one of them will if I make it's class "active" which basically shows the current tab-content (It doesn't change when I click on a different tab).
What I want to happen: Click on a tab -> It's content appears.
What currently happens: Click on a tab -> Nothing shows unless I change a tab-content's class to active (like i mentioned earlier).
Below is my code:
<div>
<ul class="nav nav-tabs">
#for (int i = 0; i < 1; )
{
foreach (var cat in Model.MyObject)
{
if (i == 0)
{
<li class="active">#cat.Name</li>
i++;
}
else
{
<li>#cat.Name</li>
}
}
}
</ul>
<div class="tab-content" style="overflow: visible;">
#for (int i = 0; i < 1; )
{
foreach (var cat in Model.Model.MyObject)
{
if (i == 0)
{
<div class="tab-pane active" id="##cat.Name">#cat.Name</div>
i++;
}
else
{
<div class="tab-pane" id="##cat.Name">#cat.Name</div>
}
}
}
</div>
I put a Breakpoint over each foreach loop - there IS content being passed so that's not the problem.
How do I modify the code so the content changes when I select a different tab?
EDIT: I have made an IF function that only makes the first one active. As of now, the tabs can switch, BUT, the contents DON'T switch. Only the first content is shown and it won't change even if I click on another tab.
You have the markup, but not the logic for active class. Bootstrap provides that logic through Javascript.
$('#myTab a').click(function (e) {
e.preventDefault();
$(this).tab('show');
})
Source: http://twitter.github.com/bootstrap/javascript.html#tabs
I have done a similar thing in cakePHP. You need to specify one of them as active so that when the user loads the page they see the active tab. Then when they click on a different tab the js kicks in and moves the active class to that tab. You can put an if statement in to have it only add the active class to the first tab (or whichever tab you want active by default).
Related
Basically I replaced the div in the content as soon as I click "Delete" on the Setting menu above. Well it does changes from collapsible set into checkbox with footer below containing two buttons. The buttons are delete and cancel but I can't perform anything with those buttons. For example, If Cancel clicked it should pop the alert popup (For the sake of testing). Please help..
<div data-role="content">
Tambah Katagori
<div id="Outcome" data-role="collapsibleset" data-theme="a" data-content-theme="a" data-collapsed-icon="arrow-r" data-expanded-icon="arrow-d"></div>
</div>
$("#Delete").click(function(){
$("#Outcome").replaceWith('<form><fieldset data-role="controlgroup" id="OutcometoDelete">');
db.transaction (function (transaction)
{
var sql = "SELECT * FROM KatagoriPengeluaran";
transaction.executeSql (sql,[],
function (transaction, result)
{
if (result.rows.length)
{
for(var i = 0; i< result.rows.length; i++ ) {
All2 =result.rows.item(i);
element = $(' <input type="checkbox" name="'+All2.Katagori+'" id="'+All2.Katagori+'"><label for="'+All2.Katagori+'">'+All2.Katagori+'</label>').appendTo($('#OutcometoDelete'));
}
All2 =result.rows.item((result.rows.length-1));
$('#'+All2.Katagori).append('</form></fieldset>');
$("#OutcometoDelete").append('<div id="someFooter" data-role="footer" data-position="fixed"><div id ="navbarFooter" data-role="navbar"><ul><li><a data-role="button" href="#popUpDelete">Delete</a></li><li><a data-role="button" href="index.html#Pengeluaran" data-ajax="false" id="cancelDelete" >Cancel</a></li></ul></div></div>');
$("#OutcometoDelete").trigger('create');
$("#someFooter").trigger('pagecreate');
$('#navbarFooter').trigger('pagecreate');
}
else
{
alert ("Retrieval Error");
}
}, error);
});
});
$("#cancelDelete").click(function(){
alert("oi");
});
This is because your way of event binding will not work for future elements. You can't just bind click event and expect it will work in the future, event(s) must be bind to something inside the DOM.
So this code will not work:
$("#cancelDelete").click(function(){
alert("oi");
});
On the other hand this code will work:
$(document).on('click',"#cancelDelete",function(){
alert("oi");
});
Second code will work because event is bind to document and it will propagate as soon as needed element is part of the DOM and appropriate event is trigger on it. This is also called delegated event binding.
jsFiddle at http://jsfiddle.net/nAgfQ/2/ (See top of HTML section for explanation and workaround.)
Scenario
I'm using jQuery Mobile (1.4.2) and KnockoutJS (3.1.0) to build a very straightforward single-page tab-based web app for displaying data to business users.
Code
Here's the JS:
$(function () {
var Tab = function (Title, TabID) {
var self = this;
self.Title = ko.observable(Title);
self.TabID = ko.observable(TabID);
self.TabHref = ko.computed(function () {
return '#' + self.TabID();
});
};
function DashboardViewModel() {
var self = this;
self.Title = ko.observable();
self.DashboardID = ko.observable();
self.tabs = ko.observableArray([
new Tab("Tab 1", "tabs-1", []),
new Tab("Tab 2", "tabs-2", [])]);
self.refreshTabs = function () {
$('#tabs').tabs("refresh").tabs("option", "active", 0);
//Added to callback to convert navbar div into jQuery Mobile Navbar
$('#dashboard_navbar').navbar();
};
}
dvm = new DashboardViewModel();
ko.applyBindings(dvm);
});
Here's the body content of the page:
<body>
<div data-role="page" id="page-1">
<div data-role="header">
<h1>jQuery Mobile Tabs Test</h1>
</div>
<div data-role="content">
<div data-role="tabs" id="tabs">
<div data-role="navbar" id="dashboard_navbar">
<ul data-bind="template { foreach : tabs }">
<li><a data-bind="attr : { href: TabHref } , text: Title" data-ajax="false"></a>
</li>
</ul>
</div>
<div data-bind=" template { foreach :tabs, afterRender: refreshTabs}">
<div data-bind="attr : { id: TabID }" class="ui-body-d ui-content">
<h4 data-bind="text: Title" />
</div>
</div>
</div>
</div>
</div></body>
Issue
When you have a Tab widget in jQuery Mobile, you are encouraged to declare an element to have a data-role attribute set to "navbar."
When jQuery renders the page, it looks for the first ul child element of the selected element, and reads the number of li elements underneath that ul.
It then uses this to add a class with the naming schema ul-grid-N, where N is the letter of the alphabet corresponding to the number of elements found minus 1 (i.e. ul-grid-a for 2 elements, ul-grid-b for 3, etc.) If there is only one element, it uses a special class ul-grid-solo.
However, when you use KnockoutJS to load a set of bound tabs, you just supply a single li element as a template underneath a foreach binding. jQuery Mobile only sees the 1 element and so adds the ul-grid-solo class and then the navbar li elements end up being rendered as stacked on top of one another instead of horizontally aligned.
Workaround
The solution I have so far is to remove the "navbar" data-role and instead use KnockoutJS's afterRender callback to convert the element into a navbar once all the bound tabs have been inserted. (See the *refreshTab*s function in the DashboardViewModel object.)
This works, but is less than ideal since it forces the ViewModel to know something about the View which is an MVVM no-no.
Questions
Can I tell jQuery Mobile to hold off applying the grid class to the navbar until after the bindings have been applied? I poked around its API but didn't see anything particularly useful.
Is there something I can do with Knockout's custom bindings? Again, trying not to inject any DOM manipulation into the ViewModel.
In general, any other workarounds, comments on the code, etc. would be appreciated.
Working with knockout and jQuery Mobile for a while, I can confirm that they simply do not play nice together. Our team has a list of re-usable knockout custom bindings just for working with jQuery mobile, because they're such a pain.
You could essentially wrap up the below workaround, or your own, into a custom binding that you'd use in place of foreach. Or subscribe to changes to the array of navbar items and update there.
Workaround based on your jsFiddle, trying to recreate the navbar, you have to also rip out the dynamic markup that jQuery mobile puts into the elements. Try adding this (source):
navbar.find("*").andSelf().each(function(){
$(this).removeClass(function(i, cn){
var matches = cn.match (/ui-[\w\-]+/g) || [];
return (matches.join (' '));
});
if ($(this).attr("class") == "") {
$(this).removeAttr("class");
}
});
JSFiddle
I am using jqueryuihelpers (http://jqueryuihelpers.apphb.com/Docmo/Dialog) for MVC 4. The following code is from the documentation page.
<p>#Html.JQueryUI().Button("Click me!", new { id = "triggerButton" })</p>
#using (Html.JQueryUI().Begin(new Dialog().AutoOpen(false)
.TriggerClick("#triggerButton")))
{
<p>This dialog is opened with a button.</p>
<p>Please click the X at the top right corner to close it.</p>
}
In the above example, the selector of TriggerClick is for just one button and it works fine.
If I have more than 1 buttons, I would like to use the class selector ".button".
#using (Html.JQueryUI().Begin(new Dialog().AutoOpen(false)
.TriggerClick(".button")))
{
<p>This dialog is opened with a button.</p>
<p>Please click the X at the top right corner to close it.</p>
}
In this case, how I can identify which button triggers the click event which in turn opens the dialog box?
There is a workaround demonstrated in the shopping cart example on the same page.
<div id="cart">
#{Html.RenderPartial("ShoppingCart");}
</div>
#using (Html.JQueryUI().Begin(new Dialog().Title("Remove").AutoOpen(false)
.ConfirmAjax(".removeLink", "Yes", "No",
new AjaxSettings() { Method = HttpVerbs.Post, Success = "removeSuccess", Error = "removeError" })))
{
<p>Remove the item from your shopping cart?</p>
}
It used the class selector and post back to the server and update the partial page content.
It is not entirely what I want. But it works as alternative.
I'm trying to implement a thought i had to allow user defined sections to be dynamically generated for my MVC 3 Razor site.
A template would look something like this
<div class="sidebar">
#RenderSection("Sidebar", false)
</div>
<div class="content">
#RenderSection("MainContent", false)
#RenderBody()
</div>
Adding a view with the following code gives me the result I would expect
DefineSection("MainContent", () =>
{
this.Write("Main Content");
});
DefineSection("Sidebar", () =>
{
this.Write("Test Content");
});
Output:
<div class="sidebar">Test Content </div>
<div class="content">Main Content <p>Rendered body from view</p></div>
Looking at this it seemed easy enough to create a model
Dictionary<SectionName, Dictionary<ControlName, Model>>
var sectionControls = new Dictionary<string, Dictionary<string, dynamic>>();
sectionControls.Add("MainContent", new Dictionary<string, dynamic>()
{
{"_shoppingCart", cart}
});
sectionControls.Add("Sidebar", new Dictionary<string, dynamic>()
{
{ "_headingImage", pageModel.HeadingImage },
{ "_sideNav", null }
});
pageModel.SectionControls = sectionControls;
So the above code declares two template sections ("MainContent" with a cart and a "Sidebar" with an image and a nav.
So now my view contains code to render the output like so
foreach(KeyValuePair<string,Dictionary<string,dynamic>> section in Model.SectionControls)
{
DefineSection(section.Key, () =>
{
foreach (KeyValuePair<string, dynamic> control in section.Value)
{
RenderPartialExtensions.RenderPartial(Html, control.Key, control.Value);
}
});
}
Now when I run this code, both sections contain the same content! Stepping through the code shows the load path is as follows
Action Returns, Code above runs in View, LayoutTemlpate begins to load. when RenderSection is called for these two sections in the layout template, the view Runs again! What seems even stranger to me is that the end result is that the "HeadingImage" and "SideNav" end up in both the Sidebar and MainContent sections. The MainContent section does not contain the cart, it contains a duplicate of the sidebar section.
<div class="sidebar">
<h2><img alt=" " src="..."></h2>
..nav..
</div>
<div class="content">
<h2><img alt=" " src="..."></h2>
..nav..
<p>Rendered body from view</p>
</div>
Commenting out one of the two section definitions in the Controller causes the other one to be the only item (but it is still duplicated!)
Has anyone had this issue before or know what limitation could be causing this behavior?
Edit: Excellent. Thanks for the linkage as well! I'm hurting for the new version of resharper with razor support.
Your lambda expressions are sharing the same section variable.
When either lambda is called, the current value of the variable is the last section.
You need to declare a separate variable inside the loop.
foreach(KeyValuePair<string,Dictionary<string,dynamic>> dontUse in Model.SectionControls)
{
var section = dontUse;
DefineSection(section.Key, () =>
{
foreach (KeyValuePair<string, dynamic> control in section.Value)
{
RenderPartialExtensions.RenderPartial(Html, control.Key, control.Value);
}
});
}
I have an ASP.NET MVC application that has a view called Products.
This Products View has a Left Menu Navigation that is implemented using NavMenuProducts.ascx Partial View.
This Menu is implemented using JQuery Treeview so that it has the list of ProductNames as the Parent Node and it is expandable(For Example: 10 Products).
Each of these Products have a ChildNode as DocTypeName and it is a hyperlink(For Example: 3 DocTypeNames).
When the user clicks ChildNode Hyperlink all the matching Documents are displayed and is implemented using Ajaxy call. So that the user has better UI experience.
But the problem with this is the url always is static(Example: http://DocShare )
But based on the Node that is clicked, I want the url like http://DocShare/Products/Product1/Letter
I am wondering how to make this dynamic url using Ajaxy call.
NOTE: If I am using HTML.ActionLINK, then I am getting dynamic URL. But this ActionLink, while the Page is loading, we are getting random treeview screen. TO avoid that flickering tree effect, I am making Ajax call for better UI experience.
Any solution would be appreciated for getting dynamic url with Ajaxy call.
Here is the Code:
NavigationProducts.ascx Page:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<MedInfoDS.Controllers.ProductViewModel>" %>
<script type="text/javascript">
$(document).ready(function () {
$(".docId").click(function () {
alert("DocTypeName: " + this.id);
$("#docDetails").load('<%= Url.Action("DocumentDetails") %>', { ProductName: "darbepoetin alfa", DocTypeName: this.id }, function (responseText, status) {
});
return false;
});
});
<div id="treecontrol">
<a title="Collapse the entire tree below" href="#">Collapse All</a> | <a title="Expand the entire tree below"
href="#">Expand All</a> | <a title="Toggle the tree below, opening closed branches, closing open branches"
href="#">Toggle All</a>
</div>
<div id="divByProduct">
<ul id="red" class="treeview-red">
<% foreach (var item in Model.Products)
{ %>
<li><span>
<%=item.Name%></span>
<ul>
<%foreach (var item1 in Model.DocTypes) { %>
<li><span>
<%= Html.ActionLink(item1.DocTypeName, "Products", new { ProductName = item.Name, DocTypeName = item1.DocTypeName })%>
<br />
<a class="docId" href="#" id="<%=item1.DocTypeName%>"><%= item1.DocTypeName%></a>
<%= Html.Hidden("ProductName", item.Name)%>
</span></li>
<% } %>
</ul>
</li>
<% } %>
</ul>
</div>
Controller Method:
// Response to AJAXy call to populate details for given ProductName and DocType
[HttpPost]
public virtual ActionResult DocumentDetails(string ProductName, string DocTypeName)
{
var entities = new MIDSContainer();
if (ProductName == null) return View();
int ProductId = (entities.Products.FirstOrDefault(p => p.Name == ProductName)).ProductId;
int DocTypeId = (entities.DocTypes.FirstOrDefault(d => d.DocTypeName == DocTypeName)).DocTypeId;
var documents = (from d in entities.Documents.Where(p => p.ProductId == ProductId && p.DocTypeId == DocTypeId && p.AvailableForUse == true && p.Status == "Approved") orderby (d.Description) select d).ToList();
return View(documents);
}
There's a pretty comprehensive solution here: http://ajaxpatterns.org/Unique_URLs
I assume you want to "change the url" so that the Back button works in the browser to navigate between the different products. You can't actually change the URL without a postback, but you can change the url's Hash value. By changing the Hash value you will be able to support the browsers Back button just like the URL itself was changed but without any postbacks.
In the following URL:
http://site/page#SomeValue
The Hash value is "#SomeValue".
You can set the Hash using "document.Hash" in JavaScript.
To make it much easier to work with the "document.Hash" value and setup a function to get notified when it changes, I create the jHash project.
You'll probably want to look at jHash to help you do what you're looking for.
http://jhash.codeplex.com/