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?

http://ennerchi.com/projects/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