I'm trying to implement a Widget control that exists on every page in the system, which will allow the user to have basic Search & Directory functionality available on each page. This is a tab control defined below, where in the <ul> the currently selected tab is determined by the the value in Model.CurrentTab and the corresponding content that I want to display (basically, make visible) is also determined by that value.
<div class="WidgetControl">
<ul class="WidgetTab tabs">
<li <%= (Model.CurrentTab == "Search") ? "class='active'" : "" %>>
<span href='<%= Url.Action("SearchBox", "Search") %>'>Search</span>
</li>
<li <%= (Model.CurrentTab == "Directory") ? "class='active'" : "" %>>
<span href='<%= Url.Action("DirectoryList", "Group") %>'>Directory</span>
</li>
</ul>
<div id="Search" class="tab_container">
<% Html.RenderAction("SearchBox", "Search"
, (Model.CurrentTab == "Search") ? Model.Search : ""); %>
</div>
<div id="Directory" class="tab_container">
<% Html.RenderAction("DirectoryList", "Group"
, (Model.CurrentTab == "Directory") ? Model.Search : ""); %>
</div>
</div>
The reason I want to load both Search and Directory is so the page doesn't have to request the content depending on which tab is clicked. I want it all to be available immediately.
The problem I'm having is that if CurrentTab contains the value "Directory" this means (I assumed) that Html.RenderAction("SearchBox"... should pass in an empty string. But when it gets to the action method, the View Model passed into SearchBox contains a value and not ""
I don't understand why this is happening. Even when I pass an empty string into SearchBox, the View Model still contains a value. Can somebody please explain what's going on? Should I be doing this differently?
update:
public PartialViewResult DirectoryList(DirectoryViewModel vm)
{
return PartialView(vm.Search); // this is expecting a string
}
public PartialViewResult SearchBox(SearchViewModel vm)
{
return PartialView(vm); // the among other things, the Search string is used
}
Both DirectoryViewModel and SearchViewModel contain a property called Search
The ModelBinder will new() up any object in a ActionMethod's parameters. I don't think this behavior can be turned off without implementing your own modelbidner. You need to create an overload that has no parameters and route it accordingly.
Should you be doing something like this
<% Html.RenderAction("SearchBox", "Search",
new { vm = ((Model.CurrentTab == "Search") ? Model.Search : "") }); %>
Since the third parameter of the Html.RenderAction is object routeValues which is a dictionary with a parameter of the Action you are calling as a Key. If you don't specify the parameter that you are passing in your routeValues parameter of your Html.RenderAction it will always pass an object value to the vm parameter of your Action.
Related
I have this simple list of users in my model.
If we click one user, I would like to set that user as the chosen one and refresh the partial view.
My code looks like:
<div id="myPartialView">
#if (#Model.ChosenUser != null)
{
#Model.ChosenUser.UserName
}
<ul>
#foreach (var u in Model.Users)
{
<li>
<a href='#Url.Action("ChooseUser", "Controller", new { userId = u.UserId })'>#u.UserName</a>
</li>
}
</ul>
The controller method returns an Ok();
My current code redirects me to an empty page and I have to go back and refresh the page in order to see the model changes.
My question is, how can I refresh only this partial view after the razor action?
You will need to use Ajax.ActionLink here :
#foreach (var u in Model.Users)
{
<li>
#Ajax.ActionLink(u.UserName, // <-- Text to display
"Choose User", // <-- Action Name
"Controller", // <-- Controller Name
new AjaxOptions
{
UpdateTargetId="myPartialView", // <-- DOM element ID to update
InsertionMode = InsertionMode.Replace, // <-- Replace the content of DOM element
HttpMethod = "GET" // <-- HTTP method
})
</li>
}
The helper method would render the html needed to create the anchor tag element with the specified values, and you need to make sure that you have scripts added in the master layout for unobtrusive ajax.
For that you can look here, what scripts are needed to be pre-requisite:
How to use Ajax.ActionLink?
and your action method should be returning the model of the same type that your partial view expects with data populated in the model.
Please refer to the following post to learn in detail about Ajax Action Link:
http://www.c-sharpcorner.com/UploadFile/abhikumarvatsa/ajax-actionlink-and-html-actionlink-in-mvc/
I have added a button in my view. When this button is clicked partial view is added. In my form I can add as much partial view as I can. When Submitting this form data I am unable to send all the partial view data to controller.
I have made a different model having all the attributes and I have made a list of that model to my main model. Can anyone please give me some trick so that I can send all the partial view content to my controller?
In My View
<div id="CSQGroup">
</div>
<div>
<input type="button" value="Add Field" id="addField" onclick="addFieldss()" />
</div>
function addFieldss()
{
$.ajax({
url: '#Url.Content("~/AdminProduct/GetColorSizeQty")',
type: 'GET',
success:function(result) {
var newDiv = $(document.createElement("div")).attr("id", 'CSQ' + myCounter);
newDiv.html(result);
newDiv.appendTo("#CSQGroup");
myCounter++;
},
error: function(result) {
alert("Failure");
}
});
}
In My controller
public ActionResult GetColorSizeQty()
{
var data = new AdminProductDetailModel();
data.colorList = commonCore.getallTypeofList("color");
data.sizeList = commonCore.getallTypeofList("size");
return PartialView(data);
}
[HttpPost]
public ActionResult AddDetail(AdminProductDetailModel model)
{
....
}
In my Partial View
#model IKLE.Model.ProductModel.AdminProductDetailModel
<div class="editor-field">
#Html.LabelFor(model => model.fkConfigChoiceCategorySizeId)
#Html.DropDownListFor(model => model.fkConfigChoiceCategorySizeId, Model.sizeList, "--Select Size--")
#Html.ValidationMessageFor(model => model.fkConfigChoiceCategorySizeId)
</div>
<div class="editor-field">
#Html.LabelFor(model => model.fkConfigChoiceCategoryColorId)
#Html.DropDownListFor(model => model.fkConfigChoiceCategoryColorId, Model.colorList, "--Select Color--")
#Html.ValidationMessageFor(model => model.fkConfigChoiceCategoryColorId)
</div>
<div class="editor-field">
#Html.LabelFor(model => model.productTotalQuantity)
#Html.TextBoxFor(model => model.productTotalQuantity)
#Html.ValidationMessageFor(model => model.productTotalQuantity)
</div>
Your problem is that the partial renders html based on a single AdminProductDetailModel object, yet you are trying to post back a collection. When you dynamically add a new object you continue to add duplicate controls that look like <input name="productTotalQuantity" ..> (this is also creating invalid html because of the duplicate id attributes) where as they need to be <input name="[0].productTotalQuantity" ..>, <input name="[1].productTotalQuantity" ..> etc. in order to bind to a collection on post back.
The DefaultModelBinder required that the indexer for collection items start at zero and be consecutive, or that the form values include a Index=someValue where the indexer is someValue (for example <input name="[ABC].productTotalQuantity" ..><input name="Index" value="ABC">. This is explained in detail in Phil Haack's article Model Binding To A List. Using the Index approach is generally better because it also allows you to delete items from the list (otherwise it would be necessary to rename all existing controls so the indexer is consecutive).
Two possible approaches to your issue.
Option 1
Use the BeginItemCollection helper for your partial view. This helper will render a hidden input for the Index value based on a GUID. You need this in both the partial view and the loop where you render existing items. Your partial would look something like
#model IKLE.Model.ProductModel.AdminProductDetailModel
#using(Html.BeginCollectionItem())
{
<div class="editor-field">
#Html.LabelFor(model => model.fkConfigChoiceCategorySizeId)
#Html.DropDownListFor(model => model.fkConfigChoiceCategorySizeId, Model.sizeList, "--Select Size--")
#Html.ValidationMessageFor(model => model.fkConfigChoiceCategorySizeId)
</div>
....
}
Option 2
Manually create the html elements representing a new object with a 'fake' indexer, place them in a hidden container, then in the Add button event, clone the html, update the indexers and Index value and append the cloned elements to the DOM. To make sure the html is correct, create one default object in a for loop and inspect the html it generates. An example of this approach is shown in this answer
<div id="newItem" style="display:none">
<div class="editor-field">
<label for="_#__productTotalQuantity">Quantity</label>
<input type="text" id="_#__productTotalQuantity" name="[#].productTotalQuantity" value />
....
</div>
// more properties of your model
</div>
Note the use of a 'fake' indexer to prevent this one being bound on post back ('#' and '%' wont match up so they are ignored by the DefaultModelBinder)
$('#addField').click(function() {
var index = (new Date()).getTime();
var clone = $('#NewItem').clone();
// Update the indexer and Index value of the clone
clone.html($(clone).html().replace(/\[#\]/g, '[' + index + ']'));
clone.html($(clone).html().replace(/"%"/g, '"' + index + '"'));
$('#yourContainer').append(clone.html());
}
The advantage of option 1 is that you are strongly typing the view to your model, but it means making a call to the server each time you add a new item. The advantage of option 2 is that its all done client side, but if you make any changes to you model (e.g. add a validation attribute to a property) then you also need to manually update the html, making maintenance a bit harder.
Finally, if you are using client side validation (jquery-validate-unobtrusive.js), then you need re-parse the validator each time you add new elements to the DOM as explained in this answer.
$('form').data('validator', null);
$.validator.unobtrusive.parse($('form'));
And of course you need to change you POST method to accept a collection
[HttpPost]
public ActionResult AddDetail(IEnumerable<AdminProductDetailModel> model)
{
....
}
I am trying to do a search with GET Method but I couldnt do the way I need. My html-css code is like below
<div class="top_search_form">
<a class="top_search_btn" href="javascript:void(0);" ><i class="fa fa-search"></i></a>
<form method="GET" action="/Banyo/Urunler">
<input type="text" name="search" value="Search" onFocus="if (this.value == 'Search') this.value = '';" onBlur="if (this.value == '') this.value = 'Search';" />
</form>
</div>
It looks like below
The user enters some text and hit the enter to do a search. But when I do a search, the URL is like below, which is not the way I need.
/Banyo/Urunler?search=asd
when the user hits the enter. I want to see the link below
/Banyo/Urunler/asd
how can I have this URL "/Banyo/Urunler/asd" ? what should I do to have this?
My MapRoute is like below
routes.MapRoute(
"Urunler", // name it!
"Banyo/Urunler/{Filtre}", // Route name
new { controller = "Banyo", action = "Urunler", Filtre = UrlParameter.Optional } // Parameter defaults
);
and I updated my html
<form method="GET" action="/Banyo/Urunler">
if the parameter name in your action (search in this case) is not defined in your route config, it'll be appended as a query string parameter when doing GET requests.
the default route defines an id parameter so you could use this instead.
Your search field would be renamed to id and your action would have an id parameter as well.
public ActionResult Urunler(string id)
{
}
Another option would be to use attribue routing (if you are using MVC4 and above).
Route("Banyo/Urunler/{search}")]
public ActionResult Urunler(string search)
{
}
Attribute routing in MVC4 would require you to install the nuget packages yourself ... i think they are added by default in MVC5
In the form, add JavaScript to the onsubmit attribute. For example...
<form onsubmit="location.assign(this.action+'/'+this.search.value);return false" action="/Banyo/Urunler">
This browses the <input> tag's value appended to the URL provided by the form. this.search provides access to the <input> element with the name of "search".
In View, the user will check 1 or more names from what's displayed ...
<form name=chkAttend method=post onsubmit='return validate(this)'>
<div>
#if (Model.evModel.Participants != null)
{
foreach (var fi in Model.evModel.Participants)
{
<div>
#if (#fi.AttendedFlag != true)
{
<input type="checkbox" id="c_#fi.EnrollmentId" name="MyCheckboxes" value="#fi.EnrollmentId" />
<label for="c_#fi.EnrollmentId" aria-multiselectable="True"></label>
<span></span> #fi.EnrollmentId #fi.ParticipantName
}
</div>
}
}
<input type=submit value="Confirm Attendance">
</div>
</form>
After selecting names, call the function to identify which names checked. The checked names only need to be passed to the controller. The problem - I'm getting the error message: Error 49 The name 'id' does not exist in the current context
function validate(form) {
var id = ""
for (var i = 0; i < document.chkAttend.MyCheckboxes.length; i++)
{
if (document.chkAttend.MyCheckboxes[i].checked)
id += document.chkAttend.MyCheckboxes[i].value + ","
}
if (id == "")
{
alert("Place a check mark next to event's Participant")
}
else
{
#Html.Action("ConfirmAttendance", "Admin", new { eventid = #Model.evModel.Id, enrollid =id })
}
return false;
}
How do I pass ONLY the checked items as parameter for the function in my controller?
You cannot inject a server-side partial view into your code like that. As it stands (if you got around the id variable reference problem) you would literally inject a partial view inline into your Javascript which will create invalid Javascript!
Instead treat the JS as client-side only and feed information into the page that the Javascript will need in a way that is easy to access. You can inject global variables via injected JS code, but I strongly advise against that practice.
Instead your MVC page could inject the controller action URL and the event id as data- attributes like this:
<form name="chkAttend" method="post"
data-action="#Url.Content("~/Admin/ConfirmAttendance")"
data-eventid="#Model.evModel.Id">
Using Url.Content will ensure the URL is always site relative, even when hosted as a virtual application.
Your client-side code can then pick up the values from the form, add the selected id and call the server action using Ajax:
e.g.
function validate(form) {
var action = $(form).data('action');
var eventId = $(form).data('eventid');
The fun begins now because you need to call the server from the client-side, e.g. via Ajax, with your selected option and do something with the result to change the display.
$.ajax({
// Use constructed URL (action + event id + id)
url: action + "?eventid=" + eventId + "&enrollid=" + id,
type: "PUT"
success: function (data){
// Do something with the server result
}
});
You do not show your controller's ConfirmAttendance action so I cannot even guess what you are returning. Make sure it is suitable (could be as simple as a Boolean result or as complex as a partial view to insert into the page).
In an ASP.NET MVC application, I'm making logic for Admin to accept or reject new members. I'm showing a list of members and two buttons Accept and Reject, like this:
<% foreach (var mm in (ViewData["pendingmembers"] as List<MyMember>)) %>
<% { %>
<tr><td>Username:<%=mm.UserName %></td><td>
<tr><td>Firstname:<%=mm.FirstName %></td><td>
...etc...
<tr>
<td>
<% using (Html.BeginForm("AcceptPendingUser", "Admin"))
{ %>
<input type="submit" value="Accept" />
<% } %>
</td>
<td>
<% using (Html.BeginForm("RejectPendingUser", "Admin"))
{ %>
<input type="submit" value="Reject" />
<% } %>
</td>
</tr>
<% } %>
So, the list of pending member data is in a list of MyMember-objects. Each MyMember object will be printed out member and two buttons are setup for the admin to either accept or reject a pending member.
Then, in the controller I'm separating the handling of those two input fields/forms, like this:
public ActionResult AcceptPendingUser()
{
// TODO: Add code to save user into DB and send welcome email.
return RedirectToAction("Index");
}
public ActionResult RejectPendingUser()
{
// TODO: Add code to remove user from PendingUsers list and send rejection email.
return RedirectToAction("Index");
}
I would like to directly get the object next to the button the user pressed.
How can I send the MyMember object from the View to the controller?
Or how do I send perhaps a numeric index with button press? Maybe with a hidden field?
The simplest option would probably be a hidden input:
<input type="hidden" value="<%=mm.Key%>" name="key" id="key" />
(name accordingly; in each form)
The two controller would then take an argument called "key" (rename to suit). If you want to parse the object from multiple inputs, you'll need a ModelBinder. Of course, rather than 2*n forms, you might consider either query-string based urls, or use something like jQuery (or some other script helper) to submit the data without needing the forms (if script is available).
Instead of using an HTML button consider using an ActionLink and construct it to include the id of the member being approved. Another alternative would be to have a checkbox (whose value is the id of the member being approved) that the admin can select for each member to be approved and a similar one for reject and one each approve/reject buttons for the entire form.
Answering to myself and other mvc newbies:
I got it finally working with this code:
VIEW:
<%=Html.ActionLink(
"Jump",
"Jump",
new { name=(ViewData["Person"] as Person).Name,
person=ViewData["Person"]},
null) %>
CONTROLLER:
public ActionResult Index()
{
ViewData["Title"] = "Home Page";
ViewData["Message"] = "Welcome to ASP.NET MVC!";
Person p = new Person();
p.Name = "Barrack";
p.Age = 35;
ViewData["Person"] = p;
return View();
}
public ActionResult Jump(string name, Person person)
{
return View();
}
Debugging the app in the Jump method gives me nice "Barrack"-string for the name parameter, but Person parameter in null.
I also understand what the kind commenters tried to explain: it's easy to send simple data types like strings and ints to controller, but complex types such as my Person object needs something else.
Basically passing an int is enough for me. The hardest part here was figuring out the right way to set up ActionLink.
Cheers,
Pom