I have been writing web applications for a while now using different
languages and frameworks (including hand-written ones just following
best practices). I like it how Rails combines a lot of best practices
and how its conventions guide the user to writing good applications
(which I am convinced is possible on any kind of platform, but you
need to know what you're doing, whilst Rails often takes you by the
hand and stops you from getting off track).
One thing I am having serious doubts about, though, is the usage of
JavaScript in Rails and I would appreciate your comments on my
thoughts. I hope this hasn't been discussed before, I did my best to
try and search for comments on it, the only thing I did see mentioned
was an article by Dan Webb about Ruby being the devil on the client-
sides shoulder, but it seems to have been taken off the web.
There is two things I would like to comment on:
1. How Rails creates a mess on the client-side violating the MVC
2. Why I don't see the use of RJS
1. How Rails creates a mess on the client-side violating MVC
Whilst this has often been obscured by messy coding practices, you
can easily think of the client side of a web application as
implementing the MVC-paradigm.
M: HTML is the model. Good HTML does not include any presentational
information. (e.g. that is why nowadays there is a wide consensus
that HTML-tables are not meant to be used for layout)
V: CSS is the view. It defines how HTML data is presented on the
screen. I admit that a certain part of the presentation is pre-
defined by the ordering within the HTML, but stylesheets still allow
you to fundamentally shape a page
C: JS is the controller. It defines the beaviour of elements on the
page and in more advanced applications (especially AJAX) it
manipulates the HTML data.
However, HTML allows you to break the paradigm. You can include CSS
in your HTML (using style-tags) and you can include JS in your HTML
(using onclick, onmouseover etc. event-handler-tags). The fact that
MVC is so pervasive on the server side these days seems to indicate
that it has some value, though. So you can mess it up on the client
side but, as with any best practice, you usually realise after a
while that you're better off when you don't.
It's become very common practice to keep your CSS out of your HTML.
For JS however, there is still a lot of mixing going on as it seems.
However, it is easily possible to keep your Javascript entirely out
of your HTML. The approach is often termed Unobtrusive JavaScript
(UJS). Sounds like a new feature, but essentially it's just doing
what should always have been done. There is very good libraries
supporting it such as prototype and lowpro (especially its behaviour
feature).
Now, coming to Rails: On the server side it provides a very clean
implementation of MVC, strictling separating the three components. On
the client side, however, Rails creates a considerable mess on the
client side by heavily mixing Javascript into HTML (especially
promoted by RJS), thus mixing V and C. From the server's point of
view HTML, JS and CSS are all part of the view. Fair enough. But from
the client's point of view they're not.
An attempt has been made by Luke Redpath and Dan Webb by providing
the UJS plugin (http://www.ujs4rails.com/). However, Dan lines out
himself that it is a somewhat half-hearted attempt (http://
www.danwebb.net/2007/6/16/the-state-and-future-of-the-ujs-plugin) The
plugin may create cleaner code in the end and may save you band-width
by keeping your JS out of your HTML (thus having to download it once
only). But to my mind, UJS is above all about programming practice,
about clean and maintainable code and the plugin doesn't help that.
2. Why I don't see the use of RJS
I have spent a fair amount of time pondering about it, I have read a
lot of enthusiastic tutorial, but still I fail to see the use of RJS.
Essentially, RJS provides Ruby functions that output some bits of JS
code. Such as other functions (for example the form helpers, I hope I
am using the right terminology here) output HTML.
In terms of HTML that makes sense. HTML is a markup language, not a
programming language. There is no way to write functions, makros or
whatever in HTML. So if you have a certain complex HTML-construct
(e.g. a complex table or a custom control made up of various tags),
you would have to write it by hand again and again. So it's useful to
have a Ruby function that does the job and reduces the work to a
single line of Ruby instead of say 20 lines of HTML.
However, JS is a programming language. If there is something complex
that you expect to be doing in many places, you can write a
Javascript function. So why do I need a Ruby function that renders
three lines of Javascript where I can just have a Javascript function
that reduces the three lines to one function call. Even more so, why
do I need a Ruby function like page.hide that translates to a single
JS call Element.hide.
That doesn't make your code DRYer, it doesn't make life easier. Yes,
I know, it saves you from writing JS. But JS is not difficult and
there are brilliant libraries and it's well worth the effort of
learning. Because what price do you pay using RJS the way I see it
used most of the time?
- It creates an extra layer of complexity. It wraps Javascript with
Ruby (in a way you can think of RJS as compiling Ruby to Javascript).
- it seems to me that with any serious JS-based application you will
quickly run into the limitations of RJS and you will need real JS,
anyway (Dan Webb seems to think along similar lines: http://
www.danwebb.net/2006/11/17/rjs-minus-r)
- when it comes to debugging you will still have to do it on the
client-side. You won't recognize your own code, because you wrote it
in Ruby, but what you're looking at, now, is JS. Firebug is a very
powerful and useful tool for debugging. But you will have to work in
Javascript. And you're life will be much easier if your code is well-
structured within one JS-file and not spread out all over the place
inside HTML, in JS-files and in RJS-files that are downloaded using
AJAX on demand. That is a nightmare to debug.
So for the moment, for my first Rails project, my approach will be:
I will use Rails for the server side and I won't let it mess with the
client-side. Rails belongs to the server and that's where it's
brilliant. For the client, there is HTML, JS and CSS already and used
properly they can be brilliant in their own way. I will treat JS just
as CSS: I will put it in the public folder and write only clean UJS
using prototype and lowpro which make it dead easy. I will debug
using Firebug on the client side.
I am thinking about writing my own version of
"javascript_include_tag :defaults". My version would not only include
the default scripts but would also check if there is a js-file by the
same name as the controller in /public/javascripts. So /app/
controllers/user_controller.rb would have a corresponding /public/
javascripts/user.js that represents the client-side controller and is
included whenever a resource within the user controller is requested.
I am very keen on hearing your comments! Thanks
Stefan
> 1. How Rails creates a mess on the client-side violating MVC
> Whilst this has often been obscured by messy coding practices, you
> can easily think of the client side of a web application as
> implementing the MVC-paradigm.
> M: HTML is the model. Good HTML does not include any presentational
> information. (e.g. that is why nowadays there is a wide consensus
> that HTML-tables are not meant to be used for layout)
> V: CSS is the view. It defines how HTML data is presented on the
> screen. I admit that a certain part of the presentation is pre-
> defined by the ordering within the HTML, but stylesheets still allow
> you to fundamentally shape a page
> C: JS is the controller. It defines the beaviour of elements on the
> page and in more advanced applications (especially AJAX) it
> manipulates the HTML data.
You're hijacking the MVC pattern. Your argument is essentially this
- MVC is good
- I can map M, V, and C onto separate languages used on the client
- Therefore, this separation on the client is (or would be) good
I'm not saying that a separation of structure, presentation, and
behavior is not good. It can be very good indeed. But this is not
because it can be mapped contortedly onto MVC.
MVC is good for reason, namely that is resolves several forces in
favorable ways. For your argument to hold, you'd have to demonstrate
that these same forces apply to the targets of your MVC and that they
are resolved equally favorable.
> However, HTML allows you to break the paradigm. You can include CSS
> in your HTML (using style-tags) and you can include JS in your HTML
> (using onclick, onmouseover etc. event-handler-tags). The fact that
> MVC is so pervasive on the server side these days seems to indicate
> that it has some value, though. So you can mess it up on the client
> side but, as with any best practice, you usually realise after a
> while that you're better off when you don't.
You don't have an argument here. It boilds down to praise by
association. Because MVC is good "there", doesn't imply that it is good
elsewhere.
> Now, coming to Rails: On the server side it provides a very clean
> implementation of MVC, strictling separating the three components. On
> the client side, however, Rails creates a considerable mess on the
> client side by heavily mixing Javascript into HTML (especially
> promoted by RJS), thus mixing V and C.
You're missing one essential point. You fail to distinguish between
between structure/presentation/behavior as they exist in views and
HTML/CSS/JS as they are or not mixed-up in generated web pages.
This distinction is crucial. And it is, of course, a Rails best
practice, to keep behavior out of views and put it in helpers or
controllers. At its root, this is just separation of concerns.
Your complaint about Rails apparently boils down to the fact that
the "compiled" output mixes concerns. There are independent arguments
why this may not be good, but you don't touch them.
> 2. Why I don't see the use of RJS
> I have spent a fair amount of time pondering about it, I have read a
> lot of enthusiastic tutorial, but still I fail to see the use of RJS.
I think you're discarding the enthusiasm of others too easily. You seem
to imply that they are misguided in seeing a use in RJS when you don't.
> Essentially, RJS provides Ruby functions that output some bits of JS
> code.
[...]
> Javascript function. So why do I need a Ruby function that renders
> three lines of Javascript where I can just have a Javascript function
> that reduces the three lines to one function call. Even more so, why
> do I need a Ruby function like page.hide that translates to a single
> JS call Element.hide.
I think the answer is obvious: Because it is a Ruby function, not a JS
function.
> That doesn't make your code DRYer, it doesn't make life easier.
Well, it makes life immensely easier for those Rails developers who
don't know JavaScript.
> Yes,
> I know, it saves you from writing JS. But JS is not difficult and
> there are brilliant libraries and it's well worth the effort of
> learning. Because what price do you pay using RJS the way I see it
> used most of the time?
So, all Rails developers have to learn JS because it is easy and simply
relying on their framework to do it's part is somehow making them pay a
price that they don't perceive.
> - It creates an extra layer of complexity. It wraps Javascript with
> Ruby (in a way you can think of RJS as compiling Ruby to Javascript).
For those who don't know JS, it is a layer of simplicity.
> - it seems to me that with any serious JS-based application you will
> quickly run into the limitations of RJS and you will need real JS,
> anyway (Dan Webb seems to think along similar lines: http://
> www.danwebb.net/2006/11/17/rjs-minus-r)
Don't do a "serious JS-based application" with RJS. That's no-brainer.
Don't do anything that seriously involves X unless you know X well
enough.
> - when it comes to debugging you will still have to do it on the
> client-side.
The beauty is that for most common uses of RJS you rarerly ever have to
debug. If someone runs into this regularly, they are using RJS well out
of its scope. The others may go ahead.
> I am very keen on hearing your comments! Thanks
I'm quite sure that what I wrote is not what you'd hope to hear. I'm
adversarial on purpose because I agree with some of your conclusions,
but I'm convinced that your arguments don't pull their weight there.
First and foremost, your arguments suffer from absolutism. You imply
that because something is not good for your purpose it can't be good
for any purpose. In the case of RJS this is patently not true.
Regarding unobtrusive JavaScript, you'd have to argue what tangible
advantages it brings for developers or users. Prima facie, it makes
thinks more contorted for developers, for they can't just attach
behavior where it applies. Maybe this can be offset by improved
accessibility and graceful degradation, but these don't come free even
with unobtrusive JS.
For the record, I know JS fairly well and like to use it. But I
recognize this as a minority position among software developers (aka
backend programmers).
Michael
--
Michael Schuerig
mailto:mic...@schuerig.de
http://www.schuerig.de/michael/
> I am thinking about writing my own version of
> "javascript_include_tag :defaults". My version would not only include
> the default scripts but would also check if there is a js-file by the
> same name as the controller in /public/javascripts. So /app/
> controllers/user_controller.rb would have a corresponding /public/
> javascripts/user.js that represents the client-side controller and is
> included whenever a resource within the user controller is requested.
>
I did not replace javascript_include_tag, but I do use a global array
called @js_includes, and in my layout, I do something like
<% if @js_includes %>
<% @js_includes.each do |js| %>
<%= javascript_include_tag js %>
<% end %>
<% end %>
Then in my controller, I will do one of two things:
1) for js that applies to certain actions, include the js in the
action with
def my_action
@js_includes = ['my_js']
...
end
2) if the js applies to the whole controller, write a before_filter
before_filter :set_includes
private
def set_includes
@js_includes = ['my_js']
end
[Hm, now that I think about it, I should probably use the
before_filter all the time and tack on :only or :except. That'd be a
bit more DRY.]
I have not needed the flexibility yet, but I could also adjust my
assignment lines
@js_includes = ['my_js']
to be
@js_includes << 'my_js' unless @js_includes.include?('my_js')
I have done the same thing for CSS, but as I learn more about CSS,
I'm finding that this approach it is not as necessary.
Peace,
Phillip
> I am not trying to say MVC is good, because it's MVC. It's the same
> reasoning as above: I see it used very widely by many people, I have
> used it and until now always to my advantage. What we do and think is
> a result of our experience, I guess. So far I have made good
> experiences with MVC, so I believe it's a good thing. I haven't heard
> strong arguments against it yet, but if you have any, I am more than
> happy to hear them.
> I am keeping my JS out of my HTML, because it makes it easier to
> reuse code, I can apply the same behaviour to different markup. I can
> simply link in a JS file to an HTML file without having to touch any
> of the markup at all. I can even move my JS between projects using
> different platforms. It doesn't matter whether the server part of my
> web app runs on php, rails, some java framework or whatever. They can
> all statically serve the JS file and it will work as long as they
> produce the same markup.
These are all good reasons, but they have nothing to do with MVC. Call
it separation of concerns, if you will. In true MVC, the model has a
very active role, it encapsulates state and its associated behavior.
Plain HTML markup is lifeless structure. If you insist on making it
into a model, you can add model behavior to it using JavaScript. Then,
on top of that, you can add presentational behavior and call it
controller or presenter if you go for MVP.
JavaScript in itself is not "the controller". It's a means of
implementing behavior. The interesting distinction is whether this
behavior is part of the business logic (model) or presentational
(view/controller).
> Also it makes it easier to debug using Firebug when I have a single
> clean JS-file and don't have to jump around between HTML-tags and JS-
> files. Finally I like it when things are cleanly separated.
When you insist that everything you're doing is so very clean, you're
implying that what others do differently is in some way dirty. Think of
it.
> > but I'm convinced that your arguments don't pull their weight
> > there.
>
> Same goes here. If you do have any arguments, it'd be nice of you to
> share them. It's exactly these arguments that I was hoping for.
Please read again what I wrote previously. Consider my comparison of
generated HTML/JS with the output of a compiler. Consider how state,
behavior, and presentation are separated in the representation the
developer is working on (models, view, controllers, helpers, ...) and
how they are separated -- or not -- in the result generated by a
template engine. I say that it is the separation of concerns in the
source representation that counts. You're claiming that it has to be
present in the generated result.
If you don't appreciate the distinction I make in the previous
paragraph, it won't make much sense for me to continue the discussion.
Your argument for separation of concerns in the output is that it gives
you better modularity:
> I am keeping my JS out of my HTML, because it makes it easier to
> reuse code, I can apply the same behaviour to different markup. I can
> simply link in a JS file to an HTML file without having to touch any
> of the markup at all. I can even move my JS between projects using
> different platforms. It doesn't matter whether the server part of my
> web app runs on php, rails, some java framework or whatever. They can
> all statically serve the JS file and it will work as long as they
> produce the same markup.
That's fair enough. Still, I can have the same advantages even if I'm
using RJS and other practices you abhor. Nothing keeps me from using
highly decoupled, unobtrusive scripts on top of that. But when it comes
to application-specific scripting, I'd like to use all the tools made
available by the framework, including RJS. No, I won't port that app to
PHP, certainly not.
> I don't feel my views respected, I feel judged unfairly and I feel
> helpless trying to convince you that I am not intending what you are
> perceiving. I cannot say where the problem lies, I tried, but it
> seems I can't express myself clearly enough in this case. I don't
> feel this is good grounds for further discussion, although I was
> hoping it would be possible, as I really appreciated your initial
> reply.
Most of the time I'm a really nice person and I didn't know that I could
be intimidating at all. That I'm responding rather forcefully has a
very specific reason: Our positions are probably not very far apart,
but I think you're doing it/them a disservice.
A case in point is your MVC analogy applied to HTML/CSS/JS. As I tried
to show before, it does not get off the ground. However, I do think
that there are good arguments for separation of
structure/presentation/behavior on the client-side *if* one is working
at that level. I'm not sure, and open to arguments either way, whether
the same forces and solutions apply at the higher level of Rails views.
If you'd like to discuss something less loaded, and as you are a
JavaScript afficionado, I'd appreciate your comments (in the proper
place) on this
http://schuerig.de/michael/blog/index.php/2007/12/22/javascript-fsm/
(yes, I'm a bit self-promotional here).
And I thought it was going to be a javascript flying spaghetti
monster! What was I thinking?
-Corey
You... pirate! You!
Arrrr,
For the logical structure, I suggest a separation into generic/framework
code. Put generic code into an application object.
var Application = {
...
};
Then for each resource have an object or class. For example
var User = {
...
};
or
var User = Class.create({
...
});
Have these specific objects/classes be initialized/instantiated
appropriately. How? Two possibilities: Arrange it so that only the
relevant files are loaded per controller or write config information in
a script block in the header.
i like the idea of separating javascript by extracting the behavoir.. i
have no experience with it yet .. do you have a simple example of how to
do it for an autocompleter?
--
Posted via http://www.ruby-forum.com/.
> To both of you: If you are using a lot of JS, what structure do you
> use? Something like Phillip wrote earlier? One JS file per controller?
> How do you serve/include your JS? For the moment, I like Phillip's
> approach, maybe with an added convention that if there is a JS-file by
> the name as the controller then it is included automatically without
> having to be added to Phillip's global array.
>
The reason I use a global array is to have a finer level of control
over what gets loaded when. I don't always do things the "proper"
way, so I will mix administrative actions in a controller with non-
administrative ones, then control access to them by a :require_admin
before_filter. I might be naive, but so far this has been working
out very well for me. In that situation, I might have some
Javascript that is specific to the administrative functions, so I
don't want it loaded if the user/action is not an admin. There might
be another way to accomplish this, but it has worked out well for me
to use the global array and assign the specific js or two action by
action.
On the other hand, if my JS applies to essentially all of the actions
in a controller, I can see how it would be DRYer to have a construct
that will automatically load a .js file with the name of the
controller. If for no other reason than to know how to do it, I
think will see if I can make that happen.
Thanks for sharing your thoughts, Stefan.
Peace,
Phillip
That's not what I was trying to say. You could use one of the available
JS dependency mechanisms to load only the files that are needed; as far
as I can tell, these mechanisms all involve adding script elements to
the document head.
However, I had in mind something simpler entirely. For the kind of apps
I've been working on most of the time, I usually don't have a
collection of loosely coupled scripts adding this or that behavior to a
page. Rather, I have some fairly general framework code +
application-/resource-specific code + configuration.
I don't have a good example handy, but have a look at this page
http://www.schuerig.de/michael/webdesign/demo/form.html
and its linked scripts, in particular
http://www.schuerig.de/michael/webdesign/demo/javascripts/boilerplate.js
and in there the last few lines of BoilerPlate#baseInitialize.
Stefan Klein wrote:
>
> I hope this is enough of an example to give you an idea. You can also
> go one step further and use Dan Webb's behaviour code in lowpro. It
> allows you to attach a behaviour to a CSS class rather than to an
> individual id as above.
>
> That way you could attach a behaviour to a CSS-class 'date' and then
> add as many <input type="text" class="date"/> as you wanted to your
> page and they would all expose the same behaviour without any extra
> JS.
>
>