Strongly typed partial view navigation - ajax

Basically, I started working on this app knowing nothing about web development which is severely inhibiting my search-fu. Probably should read a book, but that's not likely. Using ASP.NET MVC3 RC2, I'm trying to make a strongly typed partial view which can navigate to related items, as well as maintaining a bread crumb trail (which I thought would be a List in the ViewBag?). A good analogy would be a dictionary page with a thesaurus sub-view.
class Entry
{
string Name;
string Definition;
IEnumerable<Entry> Synonyms;
}
Primarily the page shows the word and its definition, etc. But there's a div with synonyms that you can click to see the selected word's synonyms, replaced w/ ajax.
I was initially thinking I needed to do an html helper, but then I saw stuff about returning PartialView from my controller which seems a lot better.
I'm having trouble piecing together all the pieces of the puzzle. A sample or outline would be great.
Thanks!

Man, I knew I should have worked on it a little longer before asking.
So, here's the recipe:
Make a function in the controller, Synonym(id) { return PartialView(GetEntry(id)); }
Make a partial view, Synonym.cshtml, strongly typed to Entry.
Display the partial view in your main display with <div id="Synonyms">#{Html.RenderAction("Synonym", #Model.Id);}</div>
For the ajax navigation link inside Synonym.cshtml, use something like this
#foreach(Entry syn in #Model.Synonyms)
#Ajax.ActionLink((string)#syn.Name, "Synonym",
new { id = #syn.Id }, new AjaxOptions { UpdateTargetId = "Synonyms" })
One thing that briefly tripped me up was the new { id = #syn.Id } because the parameter name in my controller was actually number, I had to use new { number = ... }
Oh, and I asked about navigation. I think the browser's back button will be sufficient for most navigation, but still wanted a "Back to Current Word" link. For that I did the following:
Modified Controller.Synonym to take another parameter, int orig and set ViewBag.OrigId = orig; before returning.
Modified the ActionLinks to say new { id = #syn.Id, orig = #ViewBag.OrigId }
Created the back link inside Synonym.cshtml - #Ajax.ActionLink("Back To Current Word", "Synonym", new { id = #ViewBag.OrigId, orig = #ViewBag.OrigId }, new AjaxOptions { UpdateTargetId = "Synonyms" })
Modified the call to #Html.RenderAction to pass an object like in the ActionLinks new { id = #Model.Id, orig = #Model.Id }
If someone else comes up with a better answer that makes me want to rewrite my stuff, I'll accept theirs instead. And I'd love feedback on whether I'm missing anything, like how I could avoid the second parameter to Controller.Synonym or other simplifications or improvements.

Related

MVC 3 Ajax returning partial view of area

I am trying to return a partial view via ajax. My code likes below and it works fine when partial view is not in an area.
[HttpPost]
public ActionResult SearchLogsAjax(string searchParams)
{
// Do some searching
return PartialView("LogResults", searchResults);
}
Partial View is rendered when I first open the page. The problem occurs when I press the search button which makes an ajax call to the ActionMethod. If it is in Admin area I get the classic view not found error.
The partial view 'LogResults' was not found or no view engine supports the searched locations. The following locations were searched:
~/Views/Log/LogResults.aspx
~/Views/Log/LogResults.ascx
~/Views/Shared/LogResults.aspx
~/Views/Shared/LogResults.ascx
~/Views/Log/LogResults.cshtml
~/Views/Log/LogResults.vbhtml
~/Views/Shared/LogResults.cshtml
~/Views/Shared/LogResults.vbhtml
Why it is not looking under Admin area which is the current area?
Is there any way for me to specify the area name I wanted, by something like new { area = "Admin" }
On the other hand when I move mouse over the partial view name in the code, ReSharper shows the view which i expected.
You can specify the area name you want, by using the RouteValuesDictionary parameter and syntax same as you suggested
new { area = "Admin" }
But the trickier part to me is getting the proper method signature (meaning the signature that I intend to be using) because there are more than a couple of choices and it is EASY to get the wrong signature and still be syntactically correct.
In this case I think you want the signature:
public static MvcHtmlString ActionLink(
this AjaxHelper ajaxHelper,
string linkText,
string actionName,
RouteValueDictionary routeValues,
AjaxOptions ajaxOptions
)
So modify the Ajax.ActionLink you currently have (which you did not post) to something like this:
#Ajax.ActionLink( "Link Text", "YourAction", "YourController", new { area = "YourArea" }, new AjaxOptions() { *the ajaxoptions you've been using* } )
Hope that helps.

Alternative for ViewData and QueryString

I have a page(a.aspx) to which im navigating from two different pages(ie there is a link in these two pages where it navigates to a.aspx)
to differentiate from which page i was navigated to a.aspx i used the below code.
passed Querystrings(B1,B2) to the target page as below
Html.ActionLink("test" "Testing", new { Controller = "Stats",prev="B1"},new { #class = "link",target="_self" })
Html.ActionLink("test" "Testing", new { Controller = "Stats",prev="B2"},new { #class = "link",target="_self" })
and in the action of the target page controller i used the below code
ViewData["prev"] = Request.QueryString["prev"].ToString();
and im using this ViewData in the target page ie a.aspx.This is working fine..
Im abit reluctant to use Query.string and ViewData for the above requirement.Please suggest any other alternative approach for the same.
You can use this,
Get the name of the controller
#ViewContext.Controller.ValueProvider.GetValue("controller").RawValue
Get the name of the action
#ViewContext.Controller.ValueProvider.GetValue("action").RawValue
I found that here.
Why not navigate to two separate actions that return the same view? Then, return a model to the view that will indicate the "from" page. You'll know which page you came from by virtue of which action gets hit. It's cleaner, far less complicated, and easier to maintain than trying to pass around this sort of thing in a querystring, viewdata, viewbag, etc. That being said, if you are having to do a lot of this sort of thing, creating separate actions each time around is not reali

How to change WebGrid action for getting data (.NET MVC3)

I have a Partial View that renders WebGrid. My controller looks like
public ActionResult Index()
{
return View();
}
public ActionResult GetUserList(int? page, string sort, string sortdir)
{
var model = UserModel.getList(page,sort,sortdir);
return PartialView("_UserList",model);
}
Index.cshtml :
....
#Html.Action("GetUserList")
The problem is that every time I click on grid navigation or sort links it calls Index method. How can I make Webgrid to execute a different action (GetUserList in this case)? I'm sure I can prepend GetUserList to all links in grid using jquery, but I believe it should be a better way.
It's also possible that what I'm doing is completely wrong, so thanks for your suggestions.
After lot of monkeying around and digging (and even fiddling with Reflector with WebGrid's source code), I came to the conclusion that with WebGrid, you cannot control/change the Header link action.
To create the header link URL, the path is taken from HttpContext.Request.Path, so there is no way to customize it to point to a different route.
One very ugly hack would be to tap into to jQuery Ajax's events (since the header link uses jQuery.load to sort) and overwrite the URL:
Album Id
Better solution would be to use:
Telerik Grid which lets you specify custom routes and also offers much more flexibility in rendering your layout
or MvcContrib Grid (not sure if this lets you modify header links but definitely offers more flexibility than WebGrid)
#MrChief had the idea above about the ugly hack...I put that together. Here is the main code that I used to do this. It does, indeed, hijack the ajax call before it is put on the wire. The key is to modify the URL that is getting sent because the grid will grab that URL from HttpContext.Request.Path. and plug it into the onclick for the anchor element.
I put this into my main common.js and will simply attach a function to capture the ajaxSend event which happens just before the data is sent.
// Used to hijack the sending of all AJAX calls. Before it sends the call to the server, it checks to see if the
// active element (the element that prompted the call) is marked with a given class. If so, then it will perform
// the given operation.
$(document).ajaxSend(function (event, jqXHR, ajaxOptions) {
var activeElement = document.activeElement;
if ($(activeElement).attr('redosorturl') != null) {
// If this is a sort anchor link from a grid that needs to have the sort link redone, do it here.
// the code is in the eipGrip.js file.
if ($(activeElement).attr('redosorturl').toString() == 'redoSortURL') {
var newURL = RedoGridSortURL(activeElement, ajaxOptions.url.toString());
ajaxOptions.url = newURL.toString();
}
}
return false;
});
When rendering the page, I have marked the tag in column header that contains the incorrect URL with a class named "redosorturl', so I know when I hijack the ajax call, the operation has to be done on this element. I then call a custom function that gives me the correct URL, then the ajaxOptions.url is then rewritten with that new URL.
I have to pass the activeElement to that rewrite function so I can traverse up the DOM to get the grid information, where I have put data like the controller and action method that is used along with and IDs and other info that I use for the URL. Likewise, I pass in the current url string because the grid will inject a token at the end of the url that I parse off and put on the new url.
Your conclusion isn't right. You just need to wrap your webgrid in a Get form:
using (Html.BeginForm("GetUserList", "ThingaMaBob", System.Web.Mvc.FormMethod.Get))
{
var grid = new WebGrid(
...
));
Html.Hidden(grid.SortFieldName, grid.SortColumn);
Html.Hidden(grid.SortDirectionFieldName, grid.SortDirection == SortDirection.Ascending ? "ASC" : "DESC");
}
The hiddens are so that the sort dir and sort field end up in parseable form in the querystring. You end up with urls like localhost/ThingaMaBob/GetUserList?someotherfields=whatever=&sort=city&sortdir=ASC
If you remove [HttpPost] attribute and let the route come to the same function. you'll find the Request["page"] value in your method. this will allow you to put a check on Request["Page"] value.

Linking Directly to a Tab using Prototype

I'm using Magento to build a storefront - I'm not used to Prototype, but that's what they use by default, so I'm trying to play nice. I have used the tabs setup provided in the Modern theme (built by the Magento team), and I've integrated it into my theme and it works great.
Where I need help is in directly linking to a specific tab - I've created a tab to house the product reviews, and that works fine, but there are links higher up on the page that link to reviews - however, they are linking to another page, which I would rather not use. I'm not familiar with the prototype being used, and I don't know what the link would look like to link to the tab - I'd like the experience to be something similar to:
1) Click on the link
2) The reviews tab opens and the page moves you down to it - like a run-of-the-mill anchor.
The href value of the tab is simply:
javascript:void(0);
The javascript that runs the operation is this:
Varien.Tabs = Class.create();
Varien.Tabs.prototype = {
initialize: function(selector) {
var self=this;
$$(selector+' a').each(this.initTab.bind(this));
},
initTab: function(el) {
el.href = 'javascript:void(0)';
if ($(el.parentNode).hasClassName('active')) {
this.showContent(el);
}
el.observe('click', this.showContent.bind(this, el));
},
showContent: function(a) {
var li = $(a.parentNode), ul = $(li.parentNode);
ul.select('li', 'ol').each(function(el){
var contents = $(el.id+'_contents');
if (el==li) {
el.addClassName('active');
contents.show();
} else {
el.removeClassName('active');
contents.hide();
}
});
}
}
new Varien.Tabs('.product-tabs');
My guess is that I need to invoke the showContent function and just force it to use the reviews tab, but I'm not quite sure how to do this. If anyone could shed some light on it, I'd appreciate it.
Thanks.
Not entirely the right answer, ie it cheats a bit, but we solved this by using jQuery's 'click()' function to simulate the tab click.
ie
Gave the reviews tab title anchor an id of 'tab-reviews-tab' and in our link at the top of the page added the following JS:
jQuery('#tab-reviews-tab').click();
Obviously it would be silly to include jQuery just for this, but if you're using it for something else already, sticking to what you know can work!
If someone is still interested in a solution, here is a hint to the same question:
souce link http://www.magentocommerce.com/boards/viewthread/59930/#t262411
Hope this can help u.
The answer to your question is on gist and ipaste.
Here's this summary:
You need to save the Varien.Tabs object in a var so replace the new Varien.Tabs('.product-tabs'); with var csTabs = new Varien.Tabs('.product-tabs');
If you are only doing this one time just create link as such:
<a href="#" onclick="javascript:csTabs.showContent($$('#product_tabs_email_or_other_id a')[0]);" >The Link</a>
If you expect to do this often just add a new method to the Varien.Tabs (at line 75)
openTab: function(b) {
var controlledLink = $$("#"+b+" a")[0];
this.showContent(controlledLink);
}
Now your links work like this:
Email
Further Info

How do you handle displaying navigation and sub navigation in an MVC app?

I'm having trouble determining where to place navigation for an MVC app. For example, say you have the following structure:
Conferences
South Eastern Conference
Florida Gators
Georgia Bulldogs
Arkansas Razorbacks
Pac-10
USC
Hawaii
Big East etc...
How would you best create a structure for implementing a 'main' navigation and subsequent 'sub' navigation? Using the hypothetical example, You'd have specific sub navigation for each conference, showing its respective colleges (and only that conferences colleges).
Is this something you'd handle in the main view and just hide the non-selected conference?
Or would you create a menu helper (or yet another partial) and call that from each individual college's view?
Best way is to use multiple, nested master pages. e.g. Site.master would contain your top-level nav (list of conferences?) then you'd have a different master page for each conference that would 'extend' site.master. You can, in theory, have as many nested master pages as you want. Finally, Florida Gators etc would be 'real' views (i.e. non-master pages).
The tricky part is telling any parent master page which navigation item is currently selected. Because you can't bind master pages to the ViewModel you'll have to use the View Dictionary e.g. View["SelectedMainNavItem"].
Why not use some global layout template that always displays the main navigation, and relies on some helper to render the subnav? (The helper may be superfluous -- you might just output the subnavigation inline in the layout template)
Your controller passes current category/sub-category, and some data structure describing the current subnavigation options, to the view.
After contemplating this issue for a while along with the suggestions, I came up with this solution. Since my subnavigation will always be below the main navigation, I decided to go with the Convention over Configuration method.
In my Site.Master, I have the following two render partials. One displays the main navigation and the other makes a call to BuildSubNavigation to display get the name of a partial to render:
<% Html.RenderPartial("_MainNavigation"); %>
<% var submenu = ViewContext.BuildSubNavigation();
if (submenu != null) {
Html.RenderPartial(submenu);
}%>
Granted, this could be thrown into a Helper, and I intend to do that, this is more explicit and aids in the understanding of the issue.
What this does is call the BuildSubNavigation method. It goes with the convention that if a controller is to have a specific sub navigation, there will be a partial in the form of "_Navigation" So in the spirit of the example, one partial would be "_SouthEasternConferenceNavigation" What I do is then check to see if the current view actually exists. If it does, I return the name, where it's then used to render the partial.
public static string BuildSubNavigation(this ViewContext vc) {
var controller = vc.RouteData.Values["controller"] ?? "";
var viewName = "_" + controller + "Navigation";
if (ViewExists(vc.Controller.ControllerContext, viewName, null)) {
return viewName;
} else {
return null;
}
}
And this is the method that checks whether the View actually exists against the current View Engine:
public static bool ViewExists(ControllerContext cc, string viewName, string masterName) {
if (ViewEngines.Engines.FindView(cc, viewName, masterName).View != null) {
return true;
} else { return false; }
}
I'm unsure if this is the best way to do this, but it's working rather well for a small project I'm currently working on.
Thanks for the answers!

Resources