JavaScript and URL helpers need some love regarding best practices

Folks,

my original intent for this post was to promote a patch that I submitted to Lighthouse because Pratik marked it as invalid and told me to raise the issue here if I found it to be important. Please find the ticket here: http://rails.lighthouseapp.com/projects/8994/tickets/517-link_to_remote-should-behave-like-remote_form_for.

However, I've pondered the issue over the last few days and I think the root of the problem is actually way deeper than I originally thought. I think, Rails' JavaScript helpers and URL helpers need a certain amount of refactoring for one really simple reason: Pretty much everywhere, Rails follows best practice approaches and advocates a really clean and professional way of "doing things right". Two exceptions to that rule are parts of the JavaScript helpers and URL helpers.

Let me be a little bit more specific about issues that I think should at least be considered: - Like I said in the ticket, link_to_remote should populate the href by default to provide a sensible fallback. - submit_to_remote should be renamed or at least aliased to button_to_remote just for the sake of being consistent with link_to/ link_to_remote. - link_to/link_to_remote helpers should at least raise some kind of notice when used with :method => :post/:put/:delete. Links shouldn't be used to post/put/delete on a server. Yes, I know that it actually wraps the whole thing inside a form, but still it's not a good idea. The fact that potentially "destructive" actions should be handled with a button should definitely be advocated. - Not strictly related to the helpers: While I think that mostly beginners use the scaffold feature, I feel that it still should reflect best practices. Therefore, the delete link should be made into a button.

Now while most if this stuff isn't an issue for me personally (and it probably neither is for you guys), I feel that there's definitely room for improvement. If I was to decide, I'd totally remove all *_to_remote stuff from the core and force people to write their JavaScript by hand and/or use lowpro. But I think, the above things would be quite easy to implement without being a harsh change.

I kept this post short by intention in order for you to really read it, but of course I've got some more ideas regarding these helpers. I'd be more than happy to provide patches if you agree with me that this is an issue worth being addressed.

Please let me know what you think.

Best, - Clemens

Hi Clemens,

Isn't this the kind of thing that the UJS for Rails plugin (http://ujs4rails.com/) already does?

Cheers,

Nik

Nik,

unfortunately, IMO it hardly is. First and foremost, UJS4Rails has been unmaintained for more than 1 1/2 years for a simple reason: Dan and Luke think that JavaScript should be written separately from the app anyways. I'd have to say that I totally agree.

As I mentioned in my first post, IMO it would probably be best to just totally remove this stuff from Rails because it kinda hides bad practice behind some framework code. In most areas, Rails evangelizes best practices and opinions by making it more work for the programmer to break the rules (think of set_table_name, for example). But in the case of these helpers it's actually less work to break the rules (or, rather, guides - speaking of unobtrusive javascript) than to follow them.

Especially those folks new to Rails (and probably not all that experienced in web development in general) may be led astray where we could easily guide them down the right or at least better way than the current one. I'm not saying that Rails should be more newbie-friendly - IMO the barriers of entry have already become too low if you take a look at the questions asked at RailsForum or WWR. What I'm saying is that Rails, to me, is a whole lot of good practices and rules and it should be consistent in that way without any exceptions - not even something as "humble" as the helpers.

Anyways, I'd love to hear more opinions on the topic.

Best, - Clemens

As I mentioned in my first post, IMO it would probably be best to just totally remove this stuff from Rails because it kinda hides bad practice behind some framework code. In most areas, Rails evangelizes best practices and opinions by making it more work for the programmer to break the rules (think of set_table_name, for example). But in the case of these helpers it's actually less work to break the rules (or, rather, guides - speaking of unobtrusive javascript) than to follow them.

I'll probably get in trouble for this, but I don't think that unobtrusive javascript has yet to obtain the status of undeniable best practise that you seem to be ascribing to it. The current helpers are a pragmatic solution to what is otherwise an incredibly frustrating job, and the code they generate may be 'obtrusive' but it works just fine.

The specific case you mentioned in your patch sounds interesting but I'm not sure that the assumption of being able to make non-AJAX to the ajax url is a valid one. <a href="/people/1"> isn't the same thing as a link which submits a DELETE request to the same URL.

Especially those folks new to Rails (and probably not all that experienced in web development in general) may be led astray where we could easily guide them down the right or at least better way than the current one.

What are the current practical problems which the existing helpers cause?

I'm not saying that Rails should be more newbie-friendly - IMO the barriers of entry have already become too low if you take a look at the questions asked at RailsForum or WWR.

I strongly disagree with this, while it may be satisfying to feel superior about people finding it hard to get started, that's not the way successful communities behave. The barriers to entry should be *lower* not higher.

I think *_to_remote belong in a plugin. I can't remember the last time I used one.

@Sandofsky:

views dir of project number 1 (irb) :

Dir["**/*.rhtml", "**/*.html.erb"].inject(0) { |total, file| total += `grep -c "link_to_remote" #{file}`.to_i; }

=> 29

views dir of project number 2 (irb):

Dir["**/*.rhtml", "**/*.html.erb"].inject(0) { |total, file| total += `grep -c "link_to_remote" #{file}`.to_i; }

=> 36

Would seem not everybody feels the same :slight_smile:

(ps my unix-fu is not strong, so I might have messed those up...)

Koz,

thanks for the statements.

I'll probably get in trouble for this, but I don't think that unobtrusive javascript has yet to obtain the status of undeniable best practise that you seem to be ascribing to it. The current helpers are a pragmatic solution to what is otherwise an incredibly frustrating job, and the code they generate may be 'obtrusive' but it works just fine.

You're probably right about getting in trouble, I already feel the accessibility folks will come kicking and screaming if they hear about this! :wink: But seriously: A little proactivity wouldn't hurt here - after all, Rails also created the whole Convention Over Configuration buzz, wasn't it? It may well be that it can also give birth to a _real_ unobtrusive javascript movement that really influences the way people think about accessibility.

The specific case you mentioned in your patch sounds interesting but I'm not sure that the assumption of being able to make non-AJAX to the ajax url is a valid one. <a href="/people/1"> isn't the same thing as a link which submits a DELETE request to the same URL.

Absolutely right. As I said, I wrote the patch and the short post that goes with it a couple days ago and reflected on it before starting this discussion. I mentioned in my first post here that the programmer should probably get an error message or at least a warning when trying to use a "harmful" method such as delete in a link_to_remote because it really should be a button (i.e. a form). If you can safely assume that POST/PUT/DELETE are always handled by forms, pages generally degrade more gracefully for people with JavaScript disabled. With the current way, it doesn't - if you have a link with POST/PUT/DELETE and a standard-REST action (e.g. /users or /users/1) it won't work with JavaScript disabled. Instead of POST /users, people will get the index action, and instead of PUT/DELETE /users/1, people will be given the show action.

What are the current practical problems which the existing helpers cause?

I think it's not so much a practical problem. IMO it rather is one of those "little big problems" on a higher level.

Little story from one of my projects here: I'd tell the client that I'll prepare a little prototype for them to see where we are going. They'll then try the prototype and ask "Why doesn't delete work? It always shows me the product page!". Well, guess what - they had JavaScript disabled. When I told them to enable it because the prototype needs JavaScript enabled, we got into a mini-fight about this because one of the requirements for the projects was that the page should degrade gracefully for people with JavaScript disabled.

As I said earlier, it's not so much about the current functionality being a problem but more about the (IMO) better solution standing right in front of us and (so far) being ignored.

I strongly disagree with this, while it may be satisfying to feel superior about people finding it hard to get started, that's not the way successful communities behave. The barriers to entry should be *lower* not higher.

I knew while I was typing that very sentence that I might get in trouble for saying it or at least kick off a discussion because what I said can easily be misunderstood. I had already written an answer to that one but I've deleted it a few seconds ago to not kick of the argument about that because I think the matter at hand is way more important than the question of how high or low the barriers of entry should be! :wink:

Cheers, - Clemens

You're probably right about getting in trouble, I already feel the accessibility folks will come kicking and screaming if they hear about this! :wink: But seriously: A little proactivity wouldn't hurt here - after all, Rails also created the whole Convention Over Configuration buzz, wasn't it? It may well be that it can also give birth to a _real_ unobtrusive javascript movement that really influences the way people think about accessibility.

I just don't think it's something we should be proactive about. Making the ajax case that much harder to theoretically get accessibility benefits doesn't seem like something we should be doing. If there's a way for us to make extend the helpers to make it easier to add fallback cases, then I'd be happy to hear about those. But if it's just a question of either:

* Hand Code and be accessible * use helpers and don't be

Then I think we can just leave the helpers as is and people who feel strongly about it can start a community talking about how to avoid the helpers, get some best practises brewing etc.

As I said earlier, it's not so much about the current functionality being a problem but more about the (IMO) better solution standing right in front of us and (so far) being ignored.

Given how widely used the current fuctionality is, then we'd need to see much better adoption of the better solution before we could do something like this.

If something easier, and more accesible emerges then we can go for it, but personally I definitely prefer to use link_to_remote over link_to with some class then inject the ajax functionality onload.

IMHO make the whole JavaScript package, as it currently stands, a plugin. This would be a wonderful step towards having a Prototype, jQuery, MooTools (etc.) package for Rails. The situation with Merb and JavaScript is quite wonderful: pick your own and run with it. There's no helpers, no RJS equivalent or anything. I like this approach personally.

James H.

IMHO make the whole JavaScript package, as it currently stands, a plugin.

There is absoltely no reason/explaination to do so. And I don't see it happening. Many of us use those helpers in most of the projects, and it works for us like a charm.

This would be a wonderful step towards having a Prototype, jQuery, MooTools (etc.) package for Rails. The situation with Merb and JavaScript is quite wonderful: pick your own and run with it. There's no helpers, no RJS equivalent or anything. I like this approach personally.

Rails is not forcing you to use those helpers or RJS.

Lack of feature is not a feature. If Rails, in any way, is making it difficult to implement those helpers/rsj in plugins for other js libraries, solution is for Rails to provide hooks and make it simpler to write such plugins. Patches welcome :slight_smile:

Given how widely used the current fuctionality is, then we'd need to see much better adoption of the better solution before we could do something like this.

If something easier, and more accesible emerges then we can go for it, but personally I definitely prefer to use link_to_remote over link_to with some class then inject the ajax functionality onload.

I see where you're coming from. But I think we somehow lost a little track from what I posted originally. I said that if I was to decide I would extract the whole thing from the core but I also said that that's unlikely to happen. Instead, I gave 4 quick suggestions for improvements so that the current functionality (mostly) stays in place and that the generated code degrades more gracefully. I understand you're hesitant to completely remove a widely used feature - but what do you think of my 4 concrete suggestions?

@James: I totally love that idea but we'd definitely need to have an extra abstraction layer like the AbstractAdapter for databases to provide the possibility of framework-agnostic RJS for at least the most important bits and functions. And personally I doubt that it can be easily achieved because of the way the RJS generator uses method_missing. But we could maybe take a closer look at it.

I understand you're hesitant to completely remove a widely used feature - but what do you think of my 4 concrete suggestions?

Heh, good point

- Like I said in the ticket, link_to_remote should populate the href by default to provide a sensible fallback.

I'm not sure that this *is* a sensible fallback though, given that it won't work on resource routes it seems like most new apps wouldn't benefit even a little from this. The link would go somewhere but not where it said it would. This sounds more frustrating than just hitting the #. The documented example seems pretty good in this case?

- submit_to_remote should be renamed or at least aliased to button_to_remote just for the sake of being consistent with link_to/ link_to_remote.

An alias sounds good.

- link_to/link_to_remote helpers should at least raise some kind of notice when used with :method => :post/:put/:delete. Links shouldn't be used to post/put/delete on a server. Yes, I know that it actually wraps the whole thing inside a form, but still it's not a good idea. The fact that potentially "destructive" actions should be handled with a button should definitely be advocated.

- Not strictly related to the helpers: While I think that mostly beginners use the scaffold feature, I feel that it still should reflect best practices. Therefore, the delete link should be made into a button.

I'm not convinced here, while I follow the reasoning that links should be safe to click, and things which look like links should also be safe, the pattern is so widely used in our industry I don't think that putting our collective fingers in the dike will really gain us much.

Lack of feature is not a feature.

I think it is. Otherwise we'd have ActionWebService, pagination, and acts_as_list in core.

Wasn't pagination widely used when it was pulled out? How about
auto_complete? in_place_editing?

Sujal

Lack of feature is not a feature.

I think it is. Otherwise we'd have ActionWebService, pagination, and acts_as_list in core.

ActionWebService and pagination were unmaintained with better alternatives out there (REST and will_paginate). acts_as_list was a close call but each of the other acts was unmaintained and essentially broken, leaving one in there seemed unreasonable.

auto_complete was a tiny tiny wrapper around scriptaculous functionality and in_place_editing was too restrictive and tightly coupled.

Either way, there's no reason to remove those features except for some ascetic sense of satisfaction which could be drawn. If you don't like them you can just stop using them. There's no impact with having them there for applications which don't use them.

I don't have a strong opinion here, but look at what you just said:

Pros for keeping helpers: Lots of projects use them

Pros for removing actionwebservice, pagination, etc.: unmaintained,
better alternatives exist (e.g. will_paginate) or can be made easily
(e.g. auto_complete)

The reasons for the proposed patch are "the functionality isn't great"
plus that it promotes accessibility, not just for screen readers but
for things like search engines. That's actually my most important
concern...

It sounds like the opposition is mostly that there isn't an
alternative/better implementation for the js helpers. So, let me ask
a different question: would everyone be more receptive to removing the
javascript helpers if an alternative existed? The UJS plugin was a
decent idea, and the patches attached to the ticket here are not bad
either. Or, is it that removing them is off the table, period? (I can
imagine arguments in favor of having a default JS plugin, but I'm not
convinced by any of them)

Sujal

I like the idea of extracting the javascript helpers and the
javascript files to a plugin. That way we can have a PrototypeJS
plugin, a jQuery plugin, a YUI plugin, which all provide the same
default helpers (Kind of the way ActiveRecord provides connectors to
MySQL/Postgres/...)

It would be great to be able to use the JS library that I fancy, but
with the helpers we all love.

regards, Jan De Poorter

I like the idea of extracting the javascript helpers and the javascript files to a plugin. That way we can have a PrototypeJS plugin, a jQuery plugin, a YUI plugin, which all provide the same default helpers (Kind of the way ActiveRecord provides connectors to MySQL/Postgres/...)

It would be great to be able to use the JS library that I fancy, but with the helpers we all love.

Don't we already have this with things like jrails?

If there are hooks or assumptions in our stuff that would make this kind of plugin easier, then I think we could take patches for that.

Just to chime in with my 5 cents (yeah, that's right, I just paid an extra three! ;)).

1) I like button_to_remote as an alias for submit_to_remote.

2) I don't think the JS helpers should be a plugin at all. Rails has always been about including a default answer to most common questions at the infrastructure layer. Following the same argument, you could debundle Active Record to allow for ORM alternatives, debundle Active Resource for API alternatives, and so on. Soon you'd end up with something that wasn't Rails. But as Pratik and Koz said, we should totally make it trivial for people who want to do stuff like jrails to make it so. Just like it's easy to add in your own template engine if ERb/Builder doesn't float your boat.

3) Requiring all post/delete actions to be buttons is a personal choice that you're more than free to make for your project. It wouldn't fly with my designers at 37signals. Rails shouldn't be dictating how the UI of an application should look or feel.

4) I agree with koz's analysis of the fallback of link_to_remote. We have an even better way currently where you can set :url in the html options and thus get a different fallback.

5) It's already possible to write unobtrusive JS with Rails. No one is being forced to use link_to_remote and friends. If you like to write your JS by hand, by all means do. But until there's an unobtrusive answer with as much comfort and ease of use as the current obtrusive approach, I don't see them going any where. I personally don't really care about un/obtrusive. Or actually, that's not fair. I do care on the "that'd be nice" level, but I'm not willing to trade much if any developer comfort to get that.

This has been an interesting discussion. I don’t have a strong opinion either way as I don’t use the helpers or prototype/scriptaculous.

However, if you did want to make the current helpers unobtrusive, with backwards compatibility to current behaviour, couldn’t you have the helper output a form in the HTML with javascript that executes removing the form and replacing it with the link and javascript as currently outputted by the helper.

So if a user goes to the page with javascript turn on they see exactly the same as they currently would. If they go there with javascript turned off they see a button.

Regards,

Anthony Richardson