I am working on an ASP.NET Core 2.1 MVC app using razor. I have searchQuery.cshtml and a (individually working perfectly) viewQuery.cshtml pages. In my searchQuery page, I let user enter queryId and on clicking "Search" button I want to run the action of ViewQuery that displays the results in viewQuery.cshtml and show the viewQuery below the search button area.
I am not good working with Ajax or so. On Search btn click, I call the viewQuery Get action thru ajax. In the button click, I pass the entered queryId of type int. But, when I load searchQuery page, it throws null exception for passing the queryId. I searched few hous, but didn't get any solution.
searchQuery.cshtml UPDATED
<div>
<div class="col-md-6">
<dl class="dl-horizontal">
<dt>
#Html.DisplayNameFor(model => model.QueryId)
</dt>
<dd>
<input asp-for="QueryId" class="form-control" />
</dd>
</dl>
</div>
<input type="submit" value="Show" />
<!-- CHANGE IN CALL -->
Search
</div>
<div class="modal fade" id="myModal">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
×
<h3 class="modal-title">Query Answer</h3>
</div>
<div class="modal-body" id="myModalBodyDiv">
</div>
<div class="modal-footer">
Ok
</div>
</div>
</div>
</div>
<script>
function ShowResult() {
// Retrieve queryId
var queryId = $("#QueryId").val();
// DisplayS PROPERLY
alert("Entered ID " + queryId);
// TRIED '/query/viewQuery' ALSO
$.ajax({
type: 'GET',
url: '../query/viewQuery',
data: { queryId: queryId },
success: function (response) {
alert(response); // **DISPLAYS [Object: object]**
$("#myModalBodyDiv").html(response);
$('#myModal').modal("show");
}, error: function (response) {
alert("Error: " + response);
}
});
}
</script>
My ViewQuery action in controller UPDATED
[Route("[controller]/viewQuery/{queryId:int}")]
public async Task<IActionResult> ViewQuery(int queryId)
{
// Retrieve Data from api using HttpClient
....
return PartialView("ViewQuery", qVM); // PartialView(qVM); //View(qVM);
}
Search Query Action UPDATED
[Route("searchQuery")] // customer/searchQuery
public IActionResult SearchQuery()
{
return View();
}
Can anyone please help me how do I achieve my goal. Simple - a text box were user enters queryId. A button on click, want to pass the entered queryId, call a GET action on controller and get the response. Finally show the response below the search button. I was just trying with the above modal dialog, I prefer text and not dialog.
Try & isolate the issue.
Instead of using model.QueryId in the searchQuery.cshtml, simply hardcode any reference to "modelid" - that way at least you are eliminating the possibility that Model is null on that page. Then instead of onclick="ShowResult(#Model.QueryId)"> , hard code some known id instead of #Model.QueryId. Then debug to see if your ViewQuery action method id hit. If the method is hit, then you can take it from there.
Also, I noticed that your jquery calls may need to be modified:
Instead of: $('myModalBodyDiv').html(response); it should probably be $('#myModalBodyDiv').html(response); (the "#" is missing ..) - same for $('myModal').
You can use Partial Pages(ViewQuery page) , in your searchQuery page , you could use Ajax to call server side action with parameter ID . On server side , you can query the database with ID and return PartialView with models :
[HttpPost]
public IActionResult Students (StudentFilter filters)
{
List students = Student.GetStudents(filters);
return PartialView("_Students", students);
}
Then in success callback function of Ajax , you can load the html of partial view to related area in page using jQuery :
success: function (result) {
$("#searchResultsGrid").html(result);
}
You can click here and here for code sample if using MVC template . And here is code sample if using Razor Pages .
Related
<form
class="" id="form" hx-post="/add/" hx-swap="afterbegin" hx-target="#big_list" hx-trigger="submit">
<input type="text" name="langue1" >
<input type="text" name="langue2">
<div id="errors"></div>
<button type="submit">GO</button>
</form>
<div id="big_list">
.....
</div>
I have a big list in #big_list, and I want my #form appends only one row when submitted.
How with htmx, can I handle errors and show message in #errors ?
I created this solution so you can use hx-target-error = to define which HTML will be displayed after a failed request
document.body.addEventListener('htmx:afterRequest', function (evt) {
const targetError = evt.target.attributes.getNamedItem('hx-target-error')
if (evt.detail.failed && targetError) {
document.getElementById(targetError.value).style.display = "inline";
}
});
document.body.addEventListener('htmx:beforeRequest', function (evt) {
const targetError = evt.target.attributes.getNamedItem('hx-target-error')
if (targetError) {
document.getElementById(targetError.value).style.display = "none";
}
});
If your code raises the errors (validation?), you can change target and swap behavior with response headers.
Response.Headers.Add("HX-Retarget", "#errors");
Response.Headers.Add("HX-Reswap", "innerHTML");
If you want to return a status other than 200, you have to tell htmx to accept it.
4xx would normally not do a swap in htmx. In case of validation errors you could use 422.
document.body.addEventListener('htmx:beforeOnLoad', function (evt) {
if (evt.detail.xhr.status === 422) {
evt.detail.shouldSwap = true;
evt.detail.isError = false;
}
});
It works in htmx 1.8.
If you want to remove the error message on then next sucessfull request, you could use hx-swap-oob. Out of band elements must be in the top level of the response.
So the response could look like this:
<div>
your new row data...
</div>
<div id="errors" hx-swap-oob="true"></div>
Update
You can now use the new powerful extension multi-swap to swap multiple elements arbitrarily placed and nested in the DOM tree.
See https://htmx.org/extensions/multi-swap/
Although it doesn't follow REST principles, you might consider using an swap-oob to report your error back to your user. For example, your request might return a (slightly misleading) status 200, but include content like this:
<div id="errors" hx-swap-oob="true">
There was an error processing your request...
</div>
If it's important to follow REST more precisely, then you'll want to listen to the htmx:responseError event, as mentioned by #guettli in his previous answer.
I'm calling a controller method using AJAX request.
This function used to return a partial view so I will load it in an HTML element.
the function:
public PartialViewResult LoadLockTimerEnd()
{
Session["Info"] = new Request();
RequestReply reqRep = new RequestReply("/Home/Index", "ID missing. Reseting");
return PartialView("FailurePartialView", reqRep);
}
When passing a simple string as model to this PartialView it works fine, but when passing a RequestReply object as model it is not working and the partialView is not loaded at all.
The PatialView:
#model EPS_WEB_SITE.Models.RequestReply;
#{
Layout = "~/Views/Shared/_FailureLayout.cshtml";
}
<strong>#Html.Raw(#Model.Message.ToString())</strong>
<div class="buttons-container button-container-small">
<div data-request-url="#Model.RedirectURL.ToString()">
<button type="button" id="dismiss-failure-btn" class="btn btn-danger dismiss">Dismiss</button>
</div>
</div>
The AJAX call:
$.get('/Home/LoadLockTimerEnd', function (data) {
$("#resultDiv").html(data);
});
Why does the PartialView works with string as model and not class as model?
$.ajax({
dataType: "HTML",
url: '/Home/LoadLockTimerEnd',
success: function (data) {
$("#resultDiv").html(data);
}
});
Try to call your Action using this way
OK so I found the problem:
It was a compilation error.
I needed to remove ; in the model binding in the view
#model EPS_WEB_SITE.Models.RequestReply;
I was able to find that in the network tab on Chrome browser.
Double click on the problematic request and it shown the server error.
Hope it will help someone
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'm currently building a website where I have to update two separate targets from a single Ajax.BeginForm. I got it working by using an additional container to container the two separate targets. As in:
Original Form
#model Mod1
#using (Ajax.BeginForm("LoadData", new AjaxOptions{UpdateTargetID = "Div1"}))
{
<select id="sel1" name="sel1" onchange="$(this.form).submit">
// ...
</select>
}
#using (Ajax.BeginForm("ProcessData", new AjaxOptions{UpdateTargetID = "Div2"}))
{
<div id="Div1"></div>
// ...
<input type="submit" value="GO!" />
}
Code File
public ActionResult LoadData(int sel1)
{
// loading data from database
return PartialView(mod1);
}
Partial View
#model Mod2
<select id="sel2" name="sel2">
#foreach (var item in Model.SelectItems)
{
<option value="#item.Value">#item.Text</option>
}
</select>
#foreach (var item in Model.CheckBoxItems)
{
<label>#item.Text<input type="checkbox" id="chk1" name="chk1" value="#item.Value"></label>
}
For the processing method, I have tried:
public ProcessData(Mod1 mod1, string[] chk1, int sel2)
However I am unable to retrieve the values for either chk1 or sel2 upon form submission. examination of chk1 and sel2 in Debug mode, chk1 is null while sel2 is 0 (no such value in the select options). Can anyone please offer some insight into the reason for this and also how I can go about solving it. Thank you in advance.
If I understand you correctly you can do what you want y having two submit buttons on the same form, each calling a separate action method. That way each submit button will have access to all the fields in the form. For a detailed explanation of how you can do that see my answer here:
How to use ajax link instead of submit button for form?
Edit
In response to comment: the action method LoadData should return a partial view that contains the other two controls and have the whole begin form included in it:
#using (Ajax.BeginForm("LoadData", new AjaxOptions{
UpdateTargetID = "Div1",
InsertionMode = InsertionMode.Replace
}))
{
<select id="sel1" name="sel1" onchange="$(this.form).submit">
// ...
</select>
}
<div id="Div1">
</div>
<div id="Div2">
</div>
Move this to another partial view:
#using (Ajax.BeginForm("ProcessData", new AjaxOptions{UpdateTargetID = "Div2"}))
{
// ...
<input type="submit" value="GO!" />
}
In a Create view which properly starts with a...
#using (Html.BeginForm("Create", "Song", FormMethod.Post, new { enctype = "multipart/form-data" }))
and among a few other fields has a...
<div class="editor-field">
<input type='file' name="SongFileUpload" id="SongFileUpload" onchange="readURL(this);" />
#Html.ValidationMessageFor(model => model.SongData)
</div>
ending with a
<p>
<input type="submit" value="Create" />
</p>
if I browse for and select a filename which contains a beginning and ending parentheses within the name... when I click on the "Create" button to submit it... when just running of the localhost server VS2010 provides... it just bops right over to the IE (v10) error of....
>This page can't be displayed
>
>•Make sure the web address http://localhost:63129 is correct.
>•Look for the page with your search engine.
>•Refresh the page in a few minutes.
At first I thought it might be because the filename began with an "#" symbol. but that's not it as I can select another filename that is similarly as long but does not contain the parentheses and it jumps into the
[HttpPost]
public ActionResult Create( )
Like it should and everything works as it should.
What the heck as going on here?
Below is the JavaScript for the "readURL( )" function.
function readURL(input) {
if (input.files && input.files[0]) {
var reader = new FileReader();
reader.onload = function (e) {
$('#SongMimeType').attr('value', input.files[0].type);
$('#SongData').InnerHtml = (e.target.result);
}
reader.readAsDataURL(input.files[0]);
}
}
My guess is the JavaScript function is what might be blowing-up??
How would I fix it to handle this filenames with parentheses??