Get a reference to the anchor element of an Ajax.ActionLink at the OnSuccess handler - ajax

Basically my question is similar or even a duplicate of this one, except that I'm using MVC Razor. And I'm certain that the answers there are outdated since the client library currently used is jQuery / unobtrusive ajax.
So to sum up the question, I'm trying to access the anchor element that triggered the Ajax request in the handler specified at the OnSuccess property of the provided AjaxOptions.
Here is the ActionLink:
#Ajax.ActionLink("Add opening times entry", "AddOpeningTimes",
new { htmlPrefix = Html.HtmlPrefixFor(m => Model.OpeningTimes) },
new AjaxOptions { UpdateTargetId = "openingTimes",
InsertionMode = nsertionMode.InsertAfter,
OnSuccess = "updateHtmlPrefix" },
new { title = "Add opening times entry" })
JS:
function updateHtmlPrefix() {
this.href = this.href.replace(/\d(?=]$)/, function (i) { return ++i; });
}

here is a link to an answer that shows several solutions and a good mark explanation of the issue.
https://stackoverflow.com/a/1068946/1563373
you could always just write
OnBegin="function() { clickedLink = $(this); }"
You can then access the clickedLink variable in the success handler (remember to declare it with page scope).
EDIT:
After some playing around with the call stack, you could try something like this:
<script type="text/javascript">
function start(xhr) {
var stack = start.caller;
// walk the stack
do {
stack = stack.caller;
} while (stack.arguments != undefined && stack.arguments.length > 0 && (stack.arguments[0].tagName == undefined || stack.arguments[0].tagName != "A"))
//stack now points to the entry point into unobtrusive.ajax
if (stack.arguments != undefined)
xhr.originalElement = $(stack.arguments[0]);
//blech
}
function UpdateHrefText(result, status, xhr) {
debugger;
if(xhr.originalElement != undefined)
xhr.originalElement.text(result.Message);
}
</script>
#Ajax.ActionLink("Test", "Message", "Home", new AjaxOptions{ OnBegin = "start", OnSuccess = "UpdateHrefText"})
Not sure I would trust this in production though. I'd do something more like:
<script type="text/javascript">
var theLink;
function start(xhr) {
xhr.originalElement = theLink;
}
function UpdateHrefText(result, status, xhr) {
debugger;
if(xhr.originalElement != undefined)
xhr.originalElement.text(result.Message);
}
</script>
#Ajax.ActionLink("Test", "Message", "Home", null, new AjaxOptions{ OnBegin = "start", OnSuccess = "UpdateHrefText"}, new { onclick="theLink = $(this);"})

Related

how to load a partial view inside an anchor tag which has been generated via Ajax

I have a form with a dropdownlist. When selecting an option, I make an ajax call to dynamically add a list of links in the view. When I click on one of the links, I want to update the existing page with a partial view returned by the PostListSubCategory() method.
Currently, clicking on one of the links does a redirect and shows the partial view in a new page. How can I update the the existing page?
<script language="javascript" type="text/javascript">
function GetSubCategory(_categoryId) {
var procemessage = "<a='0'> Please wait...</a>";
$("#SubCategoryID").html(procemessage).show();
var url = "/Posts/GetSubCategoryById/";
$.ajax({
url: url,
data: { categoryid: _categoryId },
cache: false,
type: "POST",
success: function (data) {
var markup = "";
for (var x = 0; x < data.length; x++) {
var num = data[x].Text;
markup += "<a href='/posts/postlistsubcategory?subcategoryid=" + data[x].Text + "'>" + data[x].Text + "</a><br />";
// markup += "<a href=" + Url.Action("postlistsubcategory", new { subcategoryid = num });
}
$("#SubCategoryID").html(markup).show();
},
error: function (reponse) {
alert("error : " + reponse);
}
});
$.ajax({
url: "/Posts/PostListCategory",
data: { categoryid: _categoryId },
cache: false,
type: "POST",
success: function (data) {
$("#postList").html(data).show();
},
error: function (reponse) {
alert("error : " + reponse);
}
});
}
</script>
#using (Html.BeginForm())
{
#Html.ListBoxFor(m => m.CategoryModel, new SelectList(Model.CategoryModel, "CategoryId", "Name"), new { #id = "ddlcategory", #style = "width:200px;", #onchange = "javascript:GetSubCategory(this.value);" })
<br />
<br />
<div id="SubCategoryID" name="SubCategoryID" style="width: 200px"></div>
<br /><br />
}
In the controller
public PartialViewResult PostListSubCategory(string subcategoryid)
{
if (subcategoryid == null)
{
return PartialView(db.Posts.ToList());
}
return PartialView("PostList", db.Posts.Include(i => i.SubCategory).Where(p => p.SubCategory.Name == subcategoryid));
}
You currently dyamically generating links with an href attribute so clicking on them will do a redirect. You need to handle the click event of those links using event delegation and then use ajax to update the existing DOM. There a some other bad practices in your code and I suggest you use the following
#using (Html.BeginForm())
{
// no need to override the id attribute and use Unobtrusive Javascript (don't pollute markup with behavior)
#Html.ListBoxFor(m => m.CategoryModel, new SelectList(Model.CategoryModel,"CategoryId", "Name"))
}
<div id="SubCategoryID"></div> // no point adding a name attribute
<div id="postList"></div>
var subcategories = $('#SubCategoryID');
$('#CategoryModel').change(function() {
var url = '#Url.Action("GetSubCategoryById", "Posts")'; // don't hard code url's
var category = $(this).val();
subcategories.empty(); // clear any existing links
$.post(url, { categoryid: category }, function(data) { // this could be a GET?
$.each(data, function(index, item) {
subcategories.append($('<a></a>').text(item).attr('href','#').addClass('subcategory')); // see note below
});
});
});
Note: Since your ajax only needs one property to generate the links (the value to display in the link), then your GetSubCategoryById() should be returning IEnumerable<string> not a collection of complex objects (you current code suggest your returning other data which you never use). If you do need to return a collection of objects, then change the above to use .text(item.Text). The above code will generate
.....
for each item you return. Then add an additional script to handle the .click() event of the links (since the links are dynamically added, you need event delegation using the .on() method)
var posts = $('#postList');
$('#SubCategoryID').on('click', '.subcategory', function() {
var url = '#Url.Action("postlistsubcategory", "posts")';
var subcategory = $(this).text();
posts.load(url, { subcategoryid: subcategory });
});

Delay ajax OnBegin until fadeOut of DOM - MVC5

I am trying to fade out a div containing html from a partial view when a user clicks on a link and fade in the newly fetched partial view. The problem I am having is that sometimes my view is fetched before the fadeout of the original partial view is finished so I end up seeing the partial views switch out, fade out, then fade back in again. Is there a way to delay the ajax request until the fade out is complete?
Here is the order of operations I am trying to achieve.
User clicks link > partial view A fades out > new partial view is fetched via ajax > partial view B fades in.
This is what is happening at times
User clicks link > partial view A begins to fade out but partial view B comes in before it finishes fading > partial view B fades out > partial view B fades back in again.
#Ajax.ActionLink("Me", "ManageUserAccount", null, new AjaxOptions
{
HttpMethod = "GET",
UpdateTargetId = "ajax-update",
InsertionMode = InsertionMode.Replace,
OnBegin = "ajaxBegin",
OnSuccess = "ajaxSuccess"
}, new { #class = "active" })
#Ajax.ActionLink("Alerts", "ManageAlerts", null, new AjaxOptions
{
HttpMethod = "GET",
UpdateTargetId = "ajax-update",
InsertionMode = InsertionMode.Replace,
OnBegin = "ajaxBegin",
OnSuccess = "ajaxSuccess"
}, new { #class = "active" })
<div id="ajax-update">
#Html.Action("ManageUserAccount")
</div>
<script type="text/javascript">
function ajaxSuccess() {
$('#ajax-update').fadeIn();
//sometimes the new partial view is returned before this even finishes its job
}
function ajaxBegin() {
$('#ajax-update').fadeOut();
}
i make a very similar function... but i use get, done and effect. and works! I use to make a complex website without postback, change partial is in the menu for simulate the change page
$(".menu-button").click(function (e) {
var id = e.target.id;
var urlID = document.URL.split('#')[1];
if (id != urlID) {
location.hash = id;
changepartial(id);
}
});
function changepartial(id)
{
var path = "../home/"+id;
getOut();
$.get(path).done(function (result) {
//done is for wait get result after make the next step.
$("#Contenant").html(result);
getIn();
});
}
function getOut() {
$("#Contenant").effect('drop', { direction: 'left' }, 100);
}
function getIn() {
$("#Contenant").effect('slide', { direction: 'right' }, 1000);
}

How can I get the data from my controller into my View via Ajax while returning a View?

I have this action in my controller which is returning a View...
public ActionResult SaveTimeShift(...)
{
try
{
if (Request.IsAjaxRequest())
return PartialView(....);
return View(userRecord);
}
catch (Exception e)
{
return PartialView(...);
}
}
Then this the html code in my viewpage...
using (Ajax.BeginForm("SaveTimeShift", new { }, new AjaxOptions { HttpMethod = "Get", UpdateTargetId = "recordList", InsertionMode = InsertionMode.Replace, Confirm = "Do you want to save the new time shift?", OnSuccess = "partialRequestSuccess(data)", OnFailure = "partialRequestFailure" }, new { #class = "form-inline" }))
{
Now on my partialRequestSuccess(data) function on my OnSuccess parameter of AjaxOptions...
function partialRequestSuccess(data) {
if (data == 1)
alert("New Time Shift has been saved.");
}
Now my problem here is .... Im trying to set a value of my "data" variable that will be set in my controller... I did some research about returning a Json object unfortunately I'm returning a View in my controller... For now my "data" variable has a garbage value...Is there a way of knowing from my client side if my saving of data in the database was a success or not... Thanks! :)
You could store the data in your model or ViewBag in the action method:
ViewBag.MyVariable = "myValue";
then use in in the JavaScript
var myVariable = #Html.Raw(Json.Encode(ViewBag.MyVariable))

How to include the #Html.AntiForgeryToken() when deleting an object using a Delete link

i have the following ajax.actionlink which calls a Delete action method for deleting an object:-
#if (!item.IsAlreadyAssigned(item.LabTestID))
{
string i = "Are You sure You want to delete (" + #item.Description.ToString() + ") ?";
#Ajax.ActionLink("Delete",
"Delete", "LabTest",
new { id = item.LabTestID },
new AjaxOptions
{ Confirm = i,
HttpMethod = "Post",
OnSuccess = "deletionconfirmation",
OnFailure = "deletionerror"
})
}
but is there a way to include #Html.AntiForgeryToken() with the Ajax.actionlink deletion call to make sure that no attacker can send a false deletion request?
BR
You need to use the Html.AntiForgeryToken helper which sets a cookie and emits a hidden field with the same value. When sending the AJAX request you need to add this value to the POST data as well.
So I would use a normal link instead of an Ajax link:
#Html.ActionLink(
"Delete",
"Delete",
"LabTest",
new {
id = item.LabTestID
},
new {
#class = "delete",
data_confirm = "Are You sure You want to delete (" + item.Description.ToString() + ") ?"
}
)
and then put the hidden field somewhere in the DOM (for example before the closing body tag):
#Html.AntiForgeryToken()
and finally unobtrusively AJAXify the delete anchor:
$(function () {
$('.delete').click(function () {
if (!confirm($(this).data('confirm'))) {
return false;
}
var token = $(':input:hidden[name*="RequestVerificationToken"]');
var data = { };
data[token.attr('name')] = token.val();
$.ajax({
url: this.href,
type: 'POST',
data: data,
success: function (result) {
},
error: function () {
}
});
return false;
});
});
Now you could decorate your Delete action with the ValidateAntiForgeryToken attribute:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Delete(int id)
{
...
}
Modifying the answer by Bronx:
$.ajaxPrefilter(function (options, localOptions, jqXHR) {
var token, tokenQuery;
if (options.type.toLowerCase() !== 'get') {
token = GetAntiForgeryToken();
if (options.data.indexOf(token.name)===-1) {
tokenQuery = token.name + '=' + token.value;
options.data = options.data ? (options.data + '&' + tokenQuery)
: tokenQuery;
}
}
});
combined with this answer by Jon White
function GetAntiForgeryToken() {
var tokenField = $("input[type='hidden'][name$='RequestVerificationToken']");
if (tokenField.length == 0) { return null;
} else {
return {
name: tokenField[0].name,
value: tokenField[0].value
};
}
Edit
sorry - realised I am re-inventing the wheel here SO asp-net-mvc-antiforgerytoken-over-ajax/16495855#16495855

ASP.Net MVC 3.0 Ajax.ActionLink Onbegin Function true the execute the action?

I have a Ajax Action link, which will call a action Method,
In my Ajax Option i have called a Validate function,
If this function returns true,
then only i would want this Action Execute, not sure how i can get this done?
My Ajax ActionLink
Ajax.ActionLink("Renew", "Edit", "Controller", new { id = "<#= ID #>" },
new AjaxOptions
{
OnBegin = "isValidDate",
OnSuccess = "DestroyRecreateAccordion",
UpdateTargetId = "accordion",
InsertionMode = InsertionMode.InsertAfter,
}, new { #class = "standard button" })
How can I do this only if isValidDate returns true?
AjaxOptions on Action Link
OnBegin="isValidDate"
JavaScript
function isValidDate() {
var date = $('#dateid').val()'
//...check date....
if(date is valid) return true;
else return false;
}
this worked
You need to return false on your OnBegin Method
OnBegin = "function(){ return isValidDate(); }",
function isValidDate() {
var date = $('#dateid').val()'
...check date....
if(date is valid) return true;
else return false;
}

Resources