Edit (I have solved this):
I have figured this out - I was returning a string (integer.ToString("C")) in the JSON data which I then placed into my textbox. Upon clicking the submit button, it was trying to parse the currency (now: "£2.99") into an integer (currentAmount is an integer in my bid object), which of course became 0, which resulted in my bid form not being able to serialise this data.
Currently I have an Ajax request through JQuery which performs a postback on my BID controller method. The method returns some JSON which lets me update my HTML with current values, such as Current Price, Bid Count, and the Next Recommended Bid Price.
My problem is, once I click the submit button, the ajax makes the post and correctly returns the data I expect. However, when I click the Submit button a second time, the bid passed pass through to my controller from my View doesn't have my form data (amount is now 0, when I can see it isn't on the page)
Why is this? I am using entity framework to save my auction and bid.
Here is my code:
Controller BID Post
[HttpPost]
public ActionResult Bid(Bid bid)
{
var db = new AuctionsDataContext();
var auction = db.Auctions.Find(bid.AuctionId);
bid.Username = User.Identity.Name;
auction.Bids.Add(bid);
auction.CurrentPrice = bid.Amount;
db.SaveChanges();
return Json(new
{
CurrentPrice = bid.Amount.ToString("C"),
BidCount = auction.BidCount,
Number = (bid.Amount + 1).ToString("C")
});
}
My BID form on Auction.cshtml
<p>
#using (Html.BeginForm("Bid", "Auctions"))
{
var lowestBidAmount = auction.CurrentPrice.GetValueOrDefault(auction.StartPrice) + 1;
<span>
Bid: $#Html.TextBox("amount", lowestBidAmount)
#Html.ValidationMessage("amount")
</span>
#Html.Hidden("auctionId", auction.Id)
<input class="post-bid" type="submit" value="Bid" />
}
</p>
My Ajax script
#section Scripts{
<script type="text/javascript">
$(function () {
$('.post-bid').on("click", function () {
var form = $(this).parent("form");
$.ajax({
type: "POST",
url: form.attr('action'),
data: form.serialize()
})
.success(function (data) {
var template = $('#current-price-template').clone().html();
var html = template
.replace('{{CurrentPrice}}', data.CurrentPrice)
.replace('{{BidCount}}', data.BidCount);
$('.current-price').replaceWith(html);
$('#amount').val(data.Number);
})
.error(function () {
alert("Your big has been rejected");
});
return false;
});
});
</script>
I can't see the content of the template but my guess would be that you are replacing the content to which to click event is attached and so when you replace the dom element you lose the click event binding. This type of binding is called directly bound.
To achieve the type of functionality you want you need to use the delegated style of binding where the click event handler is bound to a parent element or higher and a selector parameter in the binding call identifies which elements to process the click event for. This type of event binding is called delegated. When the content is replaced the handler remains and is available to handle the events from the child elements identified by the selector.
In your case something like.
form.on('click', '.post-bid',eventHandler);
For a more detailed description please the documentation for the jquery .on statement
http://api.jquery.com/on/
I have figured this out - I was returning a string (integer.ToString("C")) in the JSON data which I then placed into my textbox. Upon clicking the submit button, it was trying to parse the currency (now: "£2.99") into an integer (currentAmount is an integer in my bid object), which of course became 0, which resulted in my bid form not being able to serialise this data.
Related
AJAX sent more than one request each time the function is called (ASP.NET MVC, Ajax and JQuery)
I have a webpage that has a table (using JQuery Datatable) and each row in the table has a delete button that holds the id of each row. The Id is used to send a Delete request to a Web API(in the same project) and, if the Id is correct, it will delete the row.
It works just fine if I use just once the button. However, if I click the button for one row (so it gets deleted), then, click to delete another row, I realized that the request is sent duplicated to the Web API, with both the Id of the previous call and the current call.
For that reason, the server will throw a NotFound error for the Id that had been deleted and, for the current id, it will delete just fine.
If I repeat with another button, it will send, then, three delete request.
I'm not an expert with Javascript (or ajax or jquery), so I couldn't figure out what I can do to solve it.
(I saw similar posts, but I couldn't find something that works in my case)
HTML to create each row:
// Note that it uses a data-customer-id to data attribute to hold the value
foreach (var customer in Model) {
<tr>
<td>
// ( unrelated code)
// DELETE BUTTON
<button type="button" class="btn btn-link js-delete"
data-customer-id="#customer.Id" data-toggle="tooltip"
data-placement="right" title="Using JQuery & AJAX">Delete
</button>
</td>
</tr>
}
Javascript:
<script>
$(document).ready(function() {
$("#customers").on("click", ".js-delete",function() {
//getting a reference for the button to be used when it's clicked
var button = $(this);
// call the modal (display the dialog box on the screen)
$("#deleteModal").modal();
// if the deleteConfirm button is clicked
$("#deleteConfirm").on("click", function () {
var urlLog = button.attr("data-customer-id");
$.ajax({
url: "/api/customers/" + button.attr("data-customer-id"),
method: "DELETE",
success: function () {
// case it's success, remove the row from the table
button.parents("tr").remove();
}
});
// hide de modal (dialog box)
$("#deleteModal").modal("hide");
});
});
});
</script>
Ouput in the Network tab in the browser
I was expecting that each click to a delete button would send only one request to the WebApi, not multiple requests.
That happens because you are attaching an event listener to your #deleteConfirm element every time you click on .js-delete.
Refactor and move your $("#deleteConfirm").on("click", function () ... function outside of the $("#customers").on("click", ".js-delete",function() ... block.
I modified my code following the suggestion above and separated both click functions.
Ps. If I kept both functions as they were in the beginning, one inside the other, I could use this code for the second click function it also works
$("#deleteConfirm").off().bind("click", function ()
In the end, the final script was:
$(document).ready(function() {
$("#customers").on("click", ".js-delete",function() {
//getting a reference for the button to be used when it's clicked
var button = $(this);
// STORE THE REFERENCE TO THE BUTTON
$("#deleteConfirm").data('ref', button);
// call the modal (display the dialog box on the screen)
$("#deleteModal").modal();
});
$("#deleteConfirm").on("click", function () {
//var button = $("#customers .js-delete");
// get the reference to the button
var button = $(this).data('ref');
$.ajax({
//url: "/api/customers/" + button.attr("data-customer-id"),
url: "/api/customers/" + button.attr("data-customer-id"),
method: "DELETE",
success: function () {
// case it's success, remove the row from the table
button.parents("tr").remove();
}
});
// hide de modal (dialog box)
$("#deleteModal").modal("hide");
});
});
We have a form with a dropdownlist and a mix of Telerik Kendo UI controls on it (as well as a Telerik Grid).
When the user makes a selection from the dropdown, an ajax call is made to an MVC controller action which sends back more data that will partially fill out the form. One of these fields is represented with a Kendo UI NumericTextBox.
The requirement is to set input focus on this NumericTextbox when the data returns.
However, this doesn't appear to be working in any scenario I try.
Here is how the numeric textbox is defined on the page:
#Html.Kendo().NumericTextBoxFor(model => model.ApplyFromPOA).Name("ApplyFromPOA").Step(0.01m).Min(0.00m).HtmlAttributes(new { #style = "width: 100%", #id = "ApplyFromPOA", #class = "defaultfocus" })
Here is the definition of the dropdownlist:
#Html.Kendo().DropDownList().Name("AddPaymentCustomer").BindTo(#Model.CustomerList).DataTextField("Name").DataValueField("ID").HtmlAttributes(new { style = "width: 100%; max-width: 300px;" }).Events(e => { e.Change("changeCustomerInAddPaymentWindow"); })
The changeCustomerInAddPaymentWindow function looks like this:
function changeCustomerInAddPaymentWindow (e) {
var dataItem = getSelectedDataItemFromDropdown(e);
var datagrid = $('#MyCustomerInvoiceResults').data('kendoGrid');
var dataSource = datagrid.dataSource;
if (null != dataItem) {
if (dataItem.ID == 0) {
// Clear out the form
clearOutForm();
}
else {
$.ajax({
url: "/Home/GetCustomerAndInvoices",
type: 'POST',
data: {
customerId: dataItem.ID
},
success: function (updatedModel) {
$("#ApplyFromPOA").val(updatedModel.ApplyFromPOA);
$("#poaAvailable").val(updatedModel.POAStringNoCommas);
$("#POAString").html(updatedModel.POAString);
$("#amount-left").html(updatedModel.POAString);
$.each(updatedModel.Invoices, function (index, item) {
dataSource.add(item);
});
dataSource.sync();
setTimeout(function () {
$("#ApplyFromPOA").select();
$("#ApplyFromPOA").focus();
$("#ApplyFromPOA").find("input").focus(0, function () { });
}, 200);
},
error: function () {
}
});
}
}
}
The relevant part is the attempt to set focus on the "ApplyFromPOA" control after the ajax call returns. This does not work. The dropdownlist retains focus.
I've also tried to use the 'sync' event of the grid to call a special function that will set the input focus on the "ApplyFromPOA" NumericTextBox. No love there either.
In every case, the DropdownList stubbornly retains input focus.
The problem is that the NumericTextbox will NOT update itself to the value that is set after the Ajax call until someone actually clicks into the field. When the AJAX call returns, we do this:
$("#ApplyFromPOA").val(updatedModel.ApplyFromPOA);
That sets the value correctly internally, but until someone sets the cursor on the control, it continues to display the previous value.
Ideally, we need to have the cursor input on that numeric text box anyway.
Thanks for your help.
Chad Lehman
20th Century Fox
Senior Dev/Architect
Enterprise IT team
The Kendo NumericTextBox actually does a really obnoxious thing and takes your existing <input> and sets it to display:none;, then makes a second <input> over top of it.
Behind the scenes in JS it copies the value back and forth between the inputs.
What you want to do is work with the widget instance instead of the input elements.
Inside your success callback, instead of using jQuery functions like .val() and .focus() replace it with:
success: function (updatedModel) {
// get Kendo widget instance
var applyFromPoaWidget = $("#ApplyFromPOA").data("kendoNumericTextBox");
// set new value
applyFromPoaWidget.value(updatedModel.ApplyFromPOA);
// set focus
applyFromPoaWidget.focus();
}
I'm pretty much a jquery newb...I've almost got this working I think, let me know if i can clarify anything.
I have a screen displaying a list..let's call them affiliates. To create a new affiliate, a modal style pop up dialogue is used.
When the dialogue "Create" button is clicked, the form data must be saved (creating a new affiliate), the dialogue disappears, and the affiliate list updates without reloading the page
The jquery file at the bottom shows how I'm trying to do it now: trying to detect a click on the "confirm" button, get the form to submit using the data target property, and using the form's target property to know what container to update.
What is happening is: nothing. The cancel button works, create button does absolutely nothing.
Also note that the "Create" button, which will act as the form's submit, is not located within the <form> tags.
I'm pretty sure I'm doing modals wrong but let's ignore that for the moment and focus on the async post and list update. I've included my relevant code below to support my post.
--AffiliateListPartial
#model IPagedList<Acme.Business.DomainModel.Affiliates.Affiliate>
<div class="items-list" id="affiliate-list-view">
#foreach (var item in Model)
{
<a href="#Url.Action("AffiliateDetails", "Vendor", new { id = item.AffiliateId })">
//basic spans and razor display list in here..nothing notable
</a>
}
</div>
The above partial view is contained within a full view, lets call it AffiliateList. Nothing particularly relevant in there except that it is controlled by the VendorController/Affiliatelist method.
The VendorController.AffiliateList looks like:
public ActionResult AffiliateList(string searchTerm = null, int page = 1)
{
var userId = WebSecurity.GetUserId(User.Identity.Name);
var model = (from a in db.Affiliates.ToList()
where a.VendorId == userId
select a).ToPagedList(page, 15);
if(Request.IsAjaxRequest()) return PartialView("_AffiliateListPartial", model);
return View(model);
}
The modal style dialoque for creating a new affiliate (I'll just include the lines that I think are relevant):
_Modal.AffiliateCreate.Partial
<form id="affiliate-create-form" class="form" method="post" action="#Url.Action("AffiliateCreate")" data-acme-ajax="true" data-acme-target="#affiliate-list-view">
// multiple input elements
</form>
<div class="modal-footer">
<button name="close_modal"><span>Cancel</span></button>
<button name="confirm" data-acme-target="#affiliate-create-form"><span>Create</span></button>
</div>
And the VendorController.AffiliateCreate method:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult AffiliateCreate(Affiliate affiliate)
{
if (!ModelState.IsValid) return View(affiliate);
db.Affiliates.Add(affiliate);
db.SaveChanges();
return RedirectToAction("AffiliateList");
}
And the .js file's relevant parts:
$("button[name='confirm']").on("click", function() {
var $form = $(this).attr("data-acme-target");
var options = {
url: $form.attr("action"),
type: $form.attr("type"),
data: $form.serialize()
};
$.ajax(options).done(function (data) {
var $target = $($form.attr("data-acme-target"));
var $newHtml = $(data);
$target.replaceWith(data);
$newHtml.effect("highlight");
});
$(".modal_overlay").css("opacity", "0");
$(".modal_container").css("display", "none");
return false;
});
$("button[name='close_modal']").on("click", function(event) {
$(".modal_overlay").css("opacity", "0");
$(".modal_container").css("display", "none");
return false;
});
var $form = $(this).attr("data-acme-target"); is getting the attribute named 'data-acme-target' of the button, rather than the form it's associated with. So then when you're using $form.attr('action'), you aren't getting anything back.
Since data-acme-target is an ID to another control that is the form you want to submit, use $($(this).attr("data-acme-target")); to get it.
I have a WebGrid bound to a Plain Class Object. Nothing Fancy. The Grid features some paging and is used to display a list of users. The Last column of the Grid is an Ajax Action Link which fires a Pop Up DIV with some Edit Controls. On completion of this Edit the OnSuccess Event fires and closes the DIV. This is where I would like to now refresh the grid without losing the current page/search/filter applied. One way that comes to mind is to somehow grab the WebGrids Url and then refreshing it with JQuery, however.. I cant find a way to retrieve the current url and parameters? Anyone tried this before?
UPDATE: Thanks to the answers this is what I have resorted to now:)
The ActionResult called by the ajax edit
[HttpPost]
public ActionResult EditUserDetails(User model)
{
if (ModelState.IsValid)
{
// for the sake of brevity.
// do your edit logic here and then on success you return:
return Json(new { state = "success", id = "1", name = "John", surname = "Doe", etc = "blah" });
}
else
{
return Json(new { state = "failed", message="some error text here maybe?" });
}
}
Then on the edit click I set the class of the row to be .editRowSelected. (i will be changing this to an id rather)
Then onSuccess of the Ajax Update Call:
function onSuccess(ajaxContext) {
var result = eval(ajaxContext);
if (result.state == "Success") {
var row = $('.busyEditRow');
row.html(content);
$('.busyEditRow').removeClass('busyEditRow');
}
else {
// Display some error stuffs here with the message - result.message
}
}
This updates the grid row with the new data:)
Having just edited one record, rather than refreshing the entire grid and having to then post back the filters applied, current page, etc, I would have a function in my controller that returns a partial view containing data for a single user.
You could call this via AJAX after the DIV closes, and use jQuery to replace the row in the grid with the HTML returned from the function.
As long as each row in the grid is identifiable by id, it should be simple enough to find the relevant row and replace it.
I am using a jQuery rating plugin inside a asp:listview with in a asp:update panel. Below is the function which is being called on click of rating stars.
function DisplayRatings() {
$('DIV.ratingBar').each(function() {
var id = $(this).attr('id');
var count = $(this).attr('rel');
$('#' + id).raty({
showCancel: false,
readOnly: false,
showHalf: true,
start: count,
onClick: function(score) {
// I have to pass the score and ID to server side
}
});
});
}
Now I have to pass the 'score' and 'ID' to server side and call a method which rebinds the listview and updates the rating on the screen without screen refresh.
Please suggest how to go about this.( I can't use ajax toolkit rating as it doesn't support half star rating)
To pass data to the server, just store it in hidden form fields (in the UpdatePanel):
<asp:HiddenField id="score" runat="server" name="Score" ClientIDMode="Static" />
<asp:HiddenField id="id" runat="server" name="Score" ClientIDMode="Static" />
If you had a lot of data to pass back and forth, probably it would make sense just to use one hidden field and use serialization/deserialization to store it all there. Then in your onClick function, set the values of those fields, and initiate an async postback:
$('#score').val(score);
$('#id').val(id);
__doPostBack('UpdatePanelID', '')
This will cause an async update of your UpdatePanel, same as if a bound submit control was clicked by the user.
Note that if the jQuery control itself is in the UpdatePanel, you will have to reconfigure it after the async postback, since the effect is just as if the page had been reloaded as far as it is concerned. However, any javascript code which you may have run in $(document).ready() will not run after an async postback, since the whole page wasn't actually reloaded.
Ideally, keep the rating control outside the update panel, since you probably don't want it to change as a result of an event it initiated. If this isn't possible for some reason, or you just need a dynamic way to configure it the first time it is visible, then add a hook from the end of a page refresh:
// Page startup script - this adds a hook after an update panel is refreshed
Sys.WebForms.PageRequestManager.getInstance().add_pageLoaded(onRefresh);
function onRefresh(sender, args) {
// You can try to check which panel was updated
var panelsUpdated = args.get_panelsUpdated();
var panelsCreated = args.get_panelsCreated();
// but usually it's easier to just track the state of your jquery stuff
if (my_jquery_thing_is_visible &&
my_indicator_that_it_has_already_been_configured===false) {
configureJqueryThing();
}
}
Solved it by using jQuery with AJAX.
aspx
function SaveRating(id, score) {
var params = '{linkID:"' + id + '",ratingVal:"' + score + '"}';
$.ajax({
type: "POST",
url: "page.aspx/UpdateRating",
data: params,
contentType: "application/json; charset=utf-8",
dataType: "json"
});
}
aspx.cs
[System.Web.Services.WebMethod]
public static void UpdateRating(string linkID, int ratingVal)
{
//code to update to db
}