Pass hidden model data to the controller using ajax in mvc - javascript

Once again stumped on a partial view issue using ajax to swap out partial views.
I have a partial view that is loaded onto a page when the page is loaded. This partial view has a button on it that when clicked, will replace that partial view with another partial view. Previously I was accomplishing this with #Ajax.ActionLink and passing in the page's Model as a parameter to be passed to the controller. This was working correctly.
However, the next step was to fill in a form on the new partial view and submit it which would return another partial view. I asked how to do that on here and got it to work using jquery event delegation.
Now I'm trying to replace the #Ajax.ActionLink with a $.ajax function and am running into an issue with my js script where the model data already in the view or partial view is not being passed to the controller by the js.
Here's what I mean:
I have a page called ReviewPage that on load, will call an action that returns a partial view. This partial view is determined based on a value from the page's model that is passed to the controller. More often than not, the view returned is _NoNotes:
#model GuestPointerAppV4.Models.ViewModels.NotesOnCompanyViewModel
<div id="no_company_notes">
<h4>Notes on Company</h4>
<div class="row col-sm-6 col-sm-offset-6">
<div class="text-center">
#Html.HiddenFor(item => Model.RequestId)
<div class="primary-action-bttn-border">
<div class="primary-action-bttn-bkg">
<button data-gp-ajax="true" data-gp-target="#no_company_notes" value="Save" action="#Url.Action("_CompanyNotesEditGet", "NewSignUpRequestReview")" method="get" class="btn btn-default primary-action-bttn">
Add Note
</button>
#*#Ajax.ActionLink("Add Note", "_CompanyNotesEditGet", Model, new AjaxOptions { UpdateTargetId = "company_notes", InsertionMode = InsertionMode.Replace, HttpMethod = "GET" }, new { #class = "btn btn-default primary-action-bttn" })*#
</div>
</div>
</div>
</div>
<div class="row text-center padding-25-top">
<p>There are no notes for this company. Do you want to add some?</p>
</div>
</div>
You'll notice the commented out ajax action link in the code above. This was how I was previously able to get the model passed to the controller.
When the button is clicked on this partial view, the following js responds:
$(document).on('click', 'button[data-gp-ajax="true"]', function () {
var $form = $(this);
var options = {
url: $form.attr("action"),
type: $form.attr("method"),
data: $form.serialize()
};
$.ajax(options).done (function (data) {
var $target = $($form.attr("data-gp-target"));
$target.replaceWith(data);
});
return false;
});
I want to reuse this js as much as possible so I utilize custom data- attributes to help call the right function (data-gp-ajax="true") and tell the script where to return the results (data-gp-target="#targetid").
The problem is that in debugging this, I found that while Model.RequestId is not 0 on the page, this value is not getting picked up by the js and passed to the controller. So, when my controller looks for the data it doesn't find it and returns a null or 0 depending on the type of data I'm trying to pass.
I did a bit of research and tried to encode the Model in the partial view itself using the following at the top of the partial view's code just after the model typing:
#{
var val = Json.Encode(Model);
}
<script type="text/javascript">
var model1 = #Html.Raw(val)
</script>
Then, in my js function, I tried passing model1 to the controller by using it like so:
$(document).on('click', 'button[data-gp-ajax="true"]', function () {
var $form = $(this);
var $model = $(model1);
var options = {
url: $form.attr("action"),
type: $form.attr("method"),
data: $model.serialize()
};
$.ajax(options).done (function (data) {
var $target = $($form.attr("data-gp-target"));
$target.replaceWith(data);
});
return false;
});
I also tried data: $model and data: $model.first() but no such luck. I managed to debug and see the data I was expecting to see in the js on the $model variable so I know it's getting in there but it isn't getting passed to my controller when it's called.
Really what I'm looking for is a clean and reliable way way to pass the Model for the page into the JavaScript and have it pass it along to the Controller to execute some action on before a partial view is returned to replace the original partial view.
Thoughts?
Update
Here is an example of the controller:
[HttpGet]
public ActionResult _CompanyNotesEditGet(NotesOnCompanyViewModel notesOnCompanyViewModel)
{
//Some actions are taken on the model passed in and then repopulated to notesOnCompanyViewModel
return PartialView("~/Views/NotesOnCompany/_CompanyNotesEdit.cshtml", notesOnCompanyViewModel);
}
However, when I debug and the action is called by $.ajax, the notesOnCompanyViewModel is blank. Even when I can see there is data that should be getting passed by the js function.

All you need is to send the unique Id. for that, you can simply override the button click event and make an ajax call with the Id in the request url. If you would like to take advantage of the Url helper method to generate the correct relative path to the action method, you can do that and set the result of that to html5 data attribute on the button.
<button id="addNote"
data-url="#Url.Action("_CompanyNotesEditGet", "NewSignUpRequestReview",
new { id=Model.RequestId})" >
Add Note
</button>
Assuming your _CompanyNotesEditGet action method accepts this Id and return the value as needed
public ActionResult _CompanyNotesEditGet(int id)
{
var vm= new NotesOnCompanyViewModel { RequestId=id };
//load other properties of vm as needed.
return PartialView(""~/Views/NotesOnCompany/_CompanyNotesEdit.cshtml",vm);
}
And the script to ajaxify the click event. You can use the load method to make the ajax call and update the DOM element.
$(function(){
$(document).on("click","#addNote",function(e){
e.preventDefault();
var url=$(this).data("url");
$("#no_company_notes").load(url);
});
});
If you are editing a note, Inside your action method, you can read your note entity from the db using the Id passed in.
This should work assuming you have no script errors in the page.

Related

Conditionally Pass Data from Enclosing View to Partial View

My Requirement
I need to render a partial view on selection of a dropdownlist
On page load (from Controller) I have all the required data(viewmodel) for the Partial View
All the partial views need to get the same data from the viewmodel of the View(cshtml)
On selection of the dropdown list value, the required Partial View(given below 1- 4) needs to be rendered.
Rendering Partial View 1
#{
Html.RenderPartial("_Dashboard", new Dashboard.Web.Models.SignalDataViewModel { dData = Model.dData });
}
Rendering Partial View 2
#{
Html.RenderPartial("_Dashboard_Graph", new Dashboard.Web.Models.SignalDataViewModel { dData = Model.dData });
}
Rendering Partial View 3
#{
Html.RenderPartial("_Dashboard_Arrow", new Dashboard.Web.Models.SignalDataViewModel { dData = Model.dData });
}
Rendering Partial View 4
#{
Html.RenderPartial("_Dashboard_Custom", new Dashboard.Web.Models.SignalDataViewModel { dData = Model.dData });
}
My Query
Please suggest how should I conditionally render partial view(from among a list of Partial Views with same ViewModel) as per the selection of dropdown list while having the data for the partial views that's already availble within the View's(cshtml-parent) ViewModel
Inside of doing all of this via html. I would suggest using javascript.
Have a single div that has an ID such as
<div id="InterchangableView"></div>
Add an onChange event to the dropdown and pass a javascript function the value of the dropdown
In the function determine what value it is and call the necessary controller method such as below
if(id == 1)
{
var json = '{controllerParam: ' + id + '}';
$.ajax({
url:'#Url.Action("Action", "Controller")',
type:'POST',
data: json,
contentType:'Application/json',
success:fucntion(result){
$("#InterchangableView").html(result);
}
});
}
In the controller action, depending on the result passed into it, do a check and return the correct partialView this way.
Every time the dropdown changes, the javascript should get called, which should return a partialView which should overwrite whatever is in the div that that Id is located against.
I finally did some work around to implement this.
In the JavaScript on selection of drop-down, via ajax call I passed the model data to a controller returning Partial View.
In the Controller, the right partial view is decided and it returned back that partial view as html data to my ajax call that I use to be replace within the required(partial view) div.

C# MVC not calling correct action method when adding form action dynamically through JavaScript

First of all, please forgive me if this looks a silly question but I am not being able to solve this.
I have a MVC page with the following structure (not using HTML.BeginForm):
<form id="frmMiniSearch" method="post">
... search controls are here ...
<%: Html.DropDownListFor(model => model.VehicleCondition, Model.Condition, new {#class = "form-control",#required="true"}) %>
<input type="submit" value="Search" />
</form>
<script type="text/javascript">
jQuery(document).ready(function(){
$("#VehicleCondition").bind("change", function(){
$("#frmMiniSearch").attr("action", "/quick-search/used/honda/civic/1968-2018")
});
});
</script>
When the above JavaScript executes, I can see the form (frmMiniSearch) action changes to /quick-search/used/honda/civic/1968-2018. But when I click on the Submit button, rather than calling the right action method, it is calling some other.
The above URL pattern is set in my RouteConfig.cs file:
routes.MapRoute(
name: "VehicleMiniSearchResult",
url: "quick-search/{cond}/{makeSlug}/{model}/{yearRange}",
defaults: new { controller = "SearchResult", action = "VehicleQuickSearch", id = UrlParameter.Optional }
);
I also tried this:
$(document).ready(function(){
$("submit").click(function( e ) {
e.preventDefault();
$("#frmMiniSearch").submit();
});
});
Result is the same. Still calling wrong method.
What I am doing wrong? Any suggestion would save my life.
NB: If I want to use Html.BeginForm to call the right method, how do I call it using the route pattern (i.e. quick-search/used/honda/civic/1968-2018)? Something like:
<% using(Html.BeginForm, "Action", "Controller", <something here?>) {} %>
I tried Html.BeginRouteForm but that did not seem to work either.
Thank you for your suggestion.
EDIT:
Here are the routes in the order of they appear in RouteConfig.cs
routes.MapRoute(
name: "VehicleSearchResult",
url: "vehicles/search/",
defaults: new { controller = "SearchResult", action = "Index", id = UrlParameter.Optional }
);
routes.MapRoute(
name: "VehicleMiniSearchResult",
url: "quick-search/{cond}/{makeSlug}/{model}/{yearRange}",
defaults: new { controller = "SearchResult", action = "VehicleQuickSearch", id = UrlParameter.Optional }
);
As far as I know route order is something we need to keep in mind in Orchard CMS, but does this true for pure MVC applications as well?
By the way, when I type in the URL in browser directly the route (quick-search/used/honda/civic/1968-2018) works and I get the desired result. Then it is not calling another method. It is only happening when I am trying to submit the form via JS.
This is not the accurate solution, its just a suggestion.
Why can't you use static action and pass the parameter in query string or in body?
in that way you can avoid dynamic uris.
or call the uri from the javascript function as
$(document).ready(function(){
$("submit").click(function( e ) {
e.preventDefault();
//$("#frmMiniSearch").submit();
//Call your uri here like window.location="";
});
});

MVC controller method not being triggered by jquery in the view

I have the following method in a controller called "ProductController":
public ActionResult LoadProducts(int prodID)
{
return View();
}
I'm trying to trigger it from a view cshtml page this way:
#section Scripts {
<script type="text/jscript">
$('#MyProducts').change(function () {
var selectedID = $(this).val();
$.get('/Product/LoadProducts/' + selectedID, function (data) {
window.alert(selectedID);
});
});
</script>
}
<div>
#using (Html.BeginForm("Update", "Product", FormMethod.Post, new
{ enctype = "multipart/form-data" }))
{
#Html.DropDownList("MyProducts",
(IEnumerable<SelectListItem>)ViewBag.MyProducts as
IEnumerable<SelectListItem>, "Select")
}
</div>
The call to the jquery works when I change the value in the drop down, as I tested it via the popup box, but getting it to trigger the action method in the controller is not working.
Thanks.
In your controller, you can use HttpGetArribute to define the route. Without specifying the route, the parameter is considered as optional and it should be called as Product/LoadProducts?prodId=1. Example of HttpGetAttribute:
[HttpGet("[controller]\[action]\{prodId}")]
public ActionResult LoadProducts(int prodID)
{
return View();
}
set Url by Url.Action
var Url='#(Url.Action("ActionName","ConttrolerName"))';
and Put the variable name of the sent with the same variable received
SelectedID to ProdID
<script type="text/jscript">
$('#MyProducts').on("change",function () {
var Url='#(Url.Action("LoadProducts","Product"))';
var SelectedProdID = $(this).find("option:selected").val();
$.get( Url,{prodID:SelectedProdID}, function (data) {
window.alert(selectedID);
});
});
</script>
Suggestion: use script type="text/javascript". I am not an expert on which browsers support "jscript" but after 20 years of development I can assure you all browsers support javascript.
Also, I would discourage you from using the code which Farhad Bagherlo posted. If at all possible you should avoid using razor code inside your script tag because you may want to move this code into separate JS files or later on refactor to use TypeScript. Also, why invoke a method on the server to get an endpoint/url if you already know the path which is needed. seems wasteful. However, you could use the method he outlined to ensure that you are actually giving the correct URL. If his code works then what is the value of "Url"? (also, the client side standard for naming variables is camelCase, so url should be lower.)
If you are debugging your code and set a breakpoint in your controller. then you should be able to get it to break on that line by simply navigating to that route.
If you go to http://localhost:post/Product/LoadProducts/1 does it actually break on that line in Visual Studio?
Edit: #Transcendent is correct and would get my vote, need to understand how routing is defined vs arguments/parameters passed to the action method. Nice call Transcendent!
I agree with Collin on that using razor in your javascript marries them and that can be a pain if you try to split the js into its own file. I recently had to go through it.
Edit: This is just to show how to use Url.Action and still be able to separate js into a separate file by the use of data property in the div
What I suggest is doing something like:
#section Scripts {
<script type="text/javascript">
$('#MyProducts').change(function () {
var url = $('#mydiv').data('url') + '?prodId=' + selectedID;
var selectedID = $(this).val();
$.get(url, function (data) {
window.alert(selectedID);
});
});
</script>
}
<div id="mydiv" data-url="#(Url.Action("ActionName","ConttrolerName"))">
#using (Html.BeginForm("Update", "Product", FormMethod.Post, new
{ enctype = "multipart/form-data" }))
{
#Html.DropDownList("MyProducts",
(IEnumerable<SelectListItem>)ViewBag.MyProducts as
IEnumerable<SelectListItem>, "Select")
}
</div>

Getting parameters from query string

I am creating a web page using ASP.Net WebAPi, MVC and Knockout.
I have a normal MVC controller that loads the pages when I need them:
[Authorize]
public class AdminController : Controller
{
public ActionResult Clients()
{
return View();
}
public ActionResult ClientEdit(int? Id)
{
return View();
}
}
And once the page is loaded, my Knockout model takes care of the loading of the data. So, the 'Clients' controller simply loads a list of all clients. When on that screen, a user can click 'Edit' next to a client, and the page is navigated to the 'ClientEdit' controller, which takes an id.
So, my knockout click event looks like this in my knockout view model:
self.EditClick = function () {
if (this.ClientId && typeof this.ClientId !== 'undefined') {
window.location.href = "/Admin/ClientEdit/" + this.ClientId;
}
else
window.location.href = "/Admin/ClientEdit/";
}
(It handles the 'Create New' button and the edit button, hence the 'if')
Once I redirect, the MVC controller loads the page, and the URL is:
http://localhost:49389/Admin/ClientEdit/1
I then load the knockout model, and would like to make an API call to get the data...
After my page loads, I want to bind the view model to the page. Here's my view model at the moment:
function AdminClientEditor() {
var self = this;
self.Name = ko.observable("");
self.ContactName = ko.observable("");
ko.applyBindings(new AdminClientEditor(), $("#clienteditor")[0]);
So, I will create a $.get method that calls a webAPI method that will return me data based on the id. I just need to get the ID somehow.
But, how do I get the Id (In this case, '1', from the URL?
And, is this the right way to achieve what I am trying to do?
You can pass the id value to view via viewbag.
public ActionResult ClientEdit(int? Id)
{
ViewBag.ClientId=id;
return View();
}
and in the view's script section
var clientId="#ViewBag.ClientId";
alert(clientId);
// use this
If your javascript code which accesses this id value is inside a separate external js file, you may set this value to a js variable in your view and access it in your js file. Make sure to use namespacing to avoid global variable overwriting value issues.
So in your view
<script>
var myApp = myApp || {};
myApp.ClientId= "#ViewBag.ClientId";
</script>
<script src="~/Scripts/PageSpecificExternalJsFile.js"></script>
And in the PageSpecificExternalJsFile.js file,
var clientId=myApp.ClientId;
//use this as needed
I'm not sure if this is the best way, but you can get the ID from the URL by using JS:
var id = GetID();
function GetID() {
var href = location.href;
var results = href.split("/");
return results[results.length - 1];
}
I've come up with this solution which works, but I am unsure if it's the best way. It seems pretty good.
I created a MVC ViewModel class in my application code, called 'GenericParameteModel', which at the moment, has a single parameter, "Id".
I then modified my page loading MVC method:
public ActionResult ClientEdit(int? Id)
{
var mv = new GenericParameteModel { Id = Id };
return View(mv);
}
On my View page, I added the model 'GenericParameteModel' to the View.
I created a hidden field, called 'ClientId' on the view.
<input type="hidden" id="clientId" value="#model.Id">
Then, within my knockout view model, I check if $("#clientId").val() has a value. If so, I do the $.get call, using that value, and populate my view model.
In doing so, all my initial page loads from MVC will have the ability to you the GenericParameteModel, and it will be a pattern for other pages. As it's a model, I can add new fields as my application requires.
This seems to work well. I'm unsure if this is an acceptable way as I am new to this (MVC to load views and the Knockout/WebApi to get the data after loading). But it seems neat and manageable.

Return View from controller after jquery post

Hi I am new to MVC and I have the below senario
VIEW:
step3.cshtml is my view
This has data binded to step3.js(using knockout)
$.post("/Step/Step4", { "some":some_data }, function (data) {
}, 'JSON');
CONTROLLER:
[HttpPost]
public ActionResult Step4(model foo)
{
//save this data to the database and return a view
using (DBContext dbContext = new DBContext())
{
dbContext.Table.Add(foo);
dbContext.SaveChanges();
}
return View(foo);
}
public ActionResult Step4()
{
//get this view with the model
}
I am able to see that the view Step4.cshtml is able get the property values from model but.
I see that my View Step4.cshtml is a response header from the post how do I render this in the browser.
Is this the right approach??
So is it that an HTML form post can return a view and replace the whole content and AJAX cannot do that except for the partial view update?
When you call $.post, you are making a jQuery ajax request with a POST method. That data is given back to you in the success method. If you are returning the view, then you will have to manually replace the html in your document with that response.
Normally, when you use ajax like this, you would return a PartialView to update a section of your page, not your whole page. You might want to see if you can return a partial and update a div.
You can also do that by using an Ajax.ActionLink, and in the AjaxOptions, specify the id of the element you want to update when the response is returned, and the InsertionMode, which should be set to Replace. And you won't have to worry about replacing the content yourself.
Please see this stackoverflow question, which may help you: How To Use Ajax.ActionLink

Categories

Resources