jQuery Delete Link With Downlevel Support

Earlier this morning, I posted on making a simple jQuery delete link which makes it easy to create a delete link that does a form post to a delete action. Commenters pointed out that my solution won’t work for down-level browsers such as some mobile phones, and they were right. I wasn’t really concerned about down-level browsers.

One solution for down-level browsers is to render a proper form with a submit button, and then hide the form with JavaScript. Of course this takes a bit more work. Here’s what I did. I made sure I had the following script in my master template.

<script type="text/javascript">
 $("form.delete-link").css("display", "none");
 $("a.delete-link").show();
 $("a.delete-link").live('click', function(ev) {
    ev.preventDefault(); 
    $("form.delete-link").submit(); 
 });
</script>

When the following HTML is rendered in the page…

<form method="post" action="/go/delete/1" 
  class="delete-link">
  <input type="submit" value="delete" />
  <input name="__RequestVerificationToken" type="hidden" 
  value="Jrcn83M7T...8Z6RkdIfMZIJ5mVb" />
</form>
<a class="delete-link" href="/go/delete/1" 
  style="display:none;">delete</a>

… the jQuery code shown above will hide the form, but display the link (notice the link is hidden by default). When the link is clicked, it posts the form. However, in cases where there is no JavaScript, the form will be displayed, but the link will not be because the JavaScript is the thing that hides the form.

To make this easier to use, I wrote the following helper:

public static string DeleteLink(this HtmlHelper html
  , string linkText
  , string routeName
  , object routeValues) {
  var urlHelper = new UrlHelper(html.ViewContext.RequestContext);
  string url = urlHelper.RouteUrl(routeName, routeValues);

  string format = @"<form method=""post"" action=""{0}"" 
  class=""delete-link"">
<input type=""submit"" value=""{1}"" />
{2}
</form>";

  string form = string.Format(format, html.AttributeEncode(url)
    , html.AttributeEncode(linkText)
    , html.AntiForgeryToken());
  return form + html.RouteLink(linkText, routeName, routeValues
  , new { @class = "delete-link", style = "display:none;" });
}

Notice that we’re using the AntiForgery helpers included with ASP.NET MVC. What this means is that I need to make one small change to my delete method on my controller. I need to add the ValidateAntiForgeryToken attribute to the method.

[ValidateAntiForgeryToken]
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Delete(int id) {
  //Delete it
}

I’ve left out a bit. For example, I didn’t specify a callback to the jQuery code. So what should happen when this action method returns? I leave that as an exercise to the reader. I may address it in a future follow-up to this blog post. In my code, I’m just being cheesy and doing a full redirect, which works fine.

Technorati Tags: ,

What others have said

Requesting Gravatar... Ricky Jan 30, 2009 4:14 PM
# re: jQuery Delete Link With Downlevel Support
Does this really work? From what I can tell $.post(this.href); is just going to do an AJAX Post and the user will think nothing has happened (the page will not reload). What I would do here is make the form submit on click of the link. So something like this.

HTML:


<form id="delete-form" method="post" action="/go/delete/1" class="delete-link">
<input type="submit" value="delete" />
</form>
<a href="/go/delete/1">delete


JavaScript:


$("a.delete-link").live('click', function(ev) {
ev.preventDefault();
$('form#' + $(this).attr('rel')).submit();
});



Make sense? Sorry to keep bugging you :)
Requesting Gravatar... Ricky Jan 30, 2009 4:17 PM
# re: jQuery Delete Link With Downlevel Support
crap, the link rendered... meant this:


&lt;form id="delete-form" method="post" action="/go/delete/1" class="delete-link"&gt;
&lt;input type="submit" value="delete" /&gt;
&lt;/form&gt;
&lt;a rel="delete-form" class="delete-link" href="/go/delete/1"
style="display:none;"&gt;delete&lt;/a&gt;
Requesting Gravatar... Kazi Manzur Rashid Jan 30, 2009 4:24 PM
# re: jQuery Delete Link With Downlevel Support
This is cool, seems you are using jq 1.3?
Requesting Gravatar... haacked Jan 30, 2009 4:34 PM
# re: jQuery Delete Link With Downlevel Support
@Ricky I updated the post since you last commented. Note that I'm now submitting the original form (yes, I tested this). That allows me to use the anti-forgery helpers, like you suggested. Thanks for the feedback!
Requesting Gravatar... Rick Jan 30, 2009 5:13 PM
# re: jQuery Delete Link With Downlevel Support
How about this for down-level (including non-css!)

JavaScript:

$(function() {
$("form.post-link").each(replaceButtonsInForm);
});

function replaceButtonsInForm() {
var form = $(this);
$(":submit", form).each(function() {
var button = $(this);
var text = button.val();
var link = $("<a href=\"#\">").text(text).click(function(ev) { ev.preventDefault(); form.submit(); });
button.replaceWith(link);
return link;
});
}


HTML:

<form method="post" action="delete.ashx" class="post-link"><input type="submit" value="Delete" /></form>
Requesting Gravatar... Rick Jan 30, 2009 5:18 PM
# re: jQuery Delete Link With Downlevel Support
Ummm, except for the bad habit of using $(this) inside the replaceButtonsInForm method.
Requesting Gravatar... Elijah Manor Jan 30, 2009 7:38 PM
# re: jQuery Delete Link With Downlevel Support
Very nice! I usually don't have to work about down-level browsers at work because we defined a white & black list of browsers to support & not support... also required JavaScript :)

Thanks for the pointers. Will definitely come in handy when I need to support those scenarios!

In either case, I do need to make sure my deletes and modifies are post only operations!

http://twitter.com/elijahmanor
http://zi.ma/webdev
Requesting Gravatar... Steve Jan 30, 2009 7:52 PM
# re: jQuery Delete Link With Downlevel Support
It's a bit trickier than this, assuming you're using ".live()" because of its AJAX-tolerant benefits. (If not you could just use .click() like you did in previous example.) The script will run once and hide the form, but later AJAX updates might insert a form that doesn't get hidden.

Instead, you should use CSS to hide the form. A one-time call to add a CSS class "js-enabled" to the body will let you put in styles like:
a.delete-link { display: none; }
.js-enabled a.delete-link { display:inline; }
and the benefit is that it's automatically applied to any DOM manipulations that happen later.

I'd also suggest putting this inside a $(document).ready() rather than just directly embedding as script (shortcut: $(function() { ... }); will do the same thing.) But maybe that's what you intended and it's not a big deal.
Requesting Gravatar... Steve Jan 30, 2009 7:57 PM
# re: jQuery Delete Link With Downlevel Support
(forgot one style:
.js-enabled form.delete-link { display: none; }
)
Requesting Gravatar... haacked Jan 30, 2009 8:02 PM
# re: jQuery Delete Link With Downlevel Support
@Steve thanks for the tips. I need to spend some time getting this production worthy and revisit. Maybe later. ;)
Requesting Gravatar... Brian J. Cardiff Jan 30, 2009 8:39 PM
# re: jQuery Delete Link With Downlevel Support
One problem this solution is regarding nested FORM tags. Not all browser and not all jquery plugins (e.g.: validation) play nice with nested FORMs. One alternative would be to delay the render of those helpers forms until a safe zone in the markup is reached.

I should double check all the [delete] links in my web apps ;-).
Requesting Gravatar... Patrik Akselsson Jan 31, 2009 2:12 AM
# re: jQuery Delete Link With Downlevel Support
Why do you want a link to perform a delete operation in the first place? Doesn't that go against the semantics of html? An alternative solution would be to make GET /go/delete/1 return a page with an "Are you sure"-form that POSTs to /go/delete/1.

Much simpler imho, and you won't have to try to add fudge to accomodate non-javascript browsers
Requesting Gravatar... Duncan Smart Jan 31, 2009 1:24 PM
# re: jQuery Delete Link With Downlevel Support
"...down-level browsers" no no no, it's not about that. Progressive enhancement is about satisfying (typically) governmental organisations that require WCAG compliance (for the blind and other disabilities).
Requesting Gravatar... Mark Feb 02, 2009 5:23 PM
# re: jQuery Delete Link With Downlevel Support
Still think css is the way to go...


<form method="post" action="/go/delete/1"
class="delete-link">
<input type="submit" class="delete" value="delete" />
<input name="__RequestVerificationToken" type="hidden"
value="Jrcn83M7T...8Z6RkdIfMZIJ5mVb" />
</form>
<style >
input.delete { border: none; background: none; color: Blue; padding: 0; margin: 0; cursor: pointer; }
</style>

Requesting Gravatar... SelArom Feb 05, 2009 8:28 AM
# re: jQuery Delete Link With Downlevel Support
couldn't you make it a post for javascript-enabled browsers (using the technique in your previous entry) then also make a controller action for GET (that intercepts the standard link click non-javascript browsers) that displays a confirmation page with a standard form submit button, POSTing to the same page (this time invoking the POST method) to delete that object?

It will require an extra step sure, but only for browsers that don't use javascript. Seems like a clean, safe solution...

I don't know enought about mvc to know if this is a viable solution (can you have the same method name, overloaded for POST and GET?) but it seems logical to me!
Requesting Gravatar... Erik van Brakel Feb 10, 2009 3:50 PM
# re: jQuery Delete Link With Downlevel Support
@SelArom: No, you can't have the same method signature twice, regardless of the attributes. Normal C# rules still apply in MVC, there's no magic going on in the code as far as that's concerned.
I actually implemented what you suggested on my pet project this week, seeing how you mention it, I think I'll blog about it (might attract some people :P)
Requesting Gravatar... Erik van Brakel Feb 11, 2009 2:03 PM
# re: jQuery Delete Link With Downlevel Support
@SelArom: see tedkees.blogspot.com/.../...nk-with-downlevel.html
Requesting Gravatar... sagar Dec 17, 2009 10:36 PM
# Create. Share. Sell.
Online art gallery for contemporary artists, painters, sculptors, and those devoted to art photography, traditional art, digital art, video art, animation, poetry, prose, music.
Requesting Gravatar... John Smith Jan 11, 2010 1:00 AM
# Most Professional Personal Injury Attorneys of Los Angeles
Visit BerenjiLaw to learn about the finest and most professional providers of personal injury lawyer services in the entire Los Angeles. We provide the best legal assistance in the whole California state!
Requesting Gravatar... Oliver May 08, 2010 5:04 AM
# re: jQuery Delete Link With Downlevel Support
Hi Phil,

I am trying to create a form with java and also include the AntiForgeryToken within the from. The blow code is what I am trying to generate. I liked your helper class that injects the AntiForgeryToken into the from... can something similar be done to produce the output below?

Thank you,
Oliver

onclick="var f = document.createElement('form'); f.style.display = 'none'; this.parentNode.appendChild(f); f.method = 'POST'; f.action = this.href;var s = document.createElement('input'); s.setAttribute('type', 'hidden'); s.setAttribute('name', '__RequestVerificationToken'); s.setAttribute('value', 'NTcwdh77hWox5dGULzvutUDMqp+3ma+NmsHRGliq6XL4j7jyKb6oV8wu+AgqQsBqo5tF4y6fEgHXDDEiboFy7Q=='); f.appendChild(s);f.submit();return false;"

What do you have to say?

(will show your gravatar)
Please add 7 and 3 and type the answer here: