Best Way to Include JavaScript from partials

Very often, partials need their own JavaScript. The best way to do this
is with
<% content_for :head do %>
<%= javascript_include_tag ‘myjsfile’ %>
<% end %>

The problem is that you can end up including the same JavaScript file
more than once. This can break certain types of JS, install the same
event listener twice, and other badness.

The solution would be to use a Set to keep track of the files included.
The tricky part? The partials need to add to the Set, but the layout
head needs to be able to read it. The layout needs to read it after
the partials have added to it. Doing this right is tricky. content_for
does it, but I haven’t been able to duplicate it.

Any ideas?

This is just an idea off the top of my head, i.e. i haven’t tried it.
Could you override the content_for method, (alias old_content_for …
yada yada). Your new content_for will keep an array just like you
suggested and check if it’s been loaded already. If you do it like this
you don’t have to worry about the scoping of your array which seems to
be the issue you were having.

/app/helpers/application.rb:

def add_javascript(js_file)
@additional_javascripts ||= []
@additional_javascripts << js_file
end

def import_additional_javascripts
if @additional_javascripts
javascript_include_tag *@additional_javascripts
end
end

/app/views/some_controller/some_action.rb:

<% add_javascript “path/to/js_file” %>

/app/views/layout/application.rb:

<%= javascript_include_tag :defaults %> <%= import_additional_javascripts %>

To make sure the same file is not required twice, you can use the
Array#uniq
method on the @additional_javascripts object.

Jimmy,

I had tried something like that. The problem was that the method in the
head is being called before the view has a chance to add the
javascripts, and consequently is nil. Were you able to get what you
wrote to work?

Robert

Jimmy Kittiyachavalit wrote:

/app/helpers/application.rb:

def add_javascript(js_file)
@additional_javascripts ||= []
@additional_javascripts << js_file
end

def import_additional_javascripts
if @additional_javascripts
javascript_include_tag *@additional_javascripts
end
end

/app/views/some_controller/some_action.rb:

<% add_javascript “path/to/js_file” %>

/app/views/layout/application.rb:

<%= javascript_include_tag :defaults %> <%= import_additional_javascripts %>

To make sure the same file is not required twice, you can use the
Array#uniq
method on the @additional_javascripts object.

I have thought about this for a while. I’ve not implemented it, but
it’s
along the lines of the content_for method. This is untested and at the
moment just my mind rambling

in the application helper

def demand_javascript( *list )
@demanded_javascripts ||= Set.new

find new items in list

tmp = ( @demanded_javascripts | list ) - @demanded_javascripts

content_for :javascripts do
javascript_include_tag *tmp
end
@demanded_javascripts.add list
end

Then in your view you can call as many times as you like

<% demand_javascript “my”,“javascript”,“files” %>

I hope that works. I think I’ll give it a try when I get home.

Robert,
Yes, I use this code regularly in all of my rails apps. As far as I
know,
the view code is always run before the layout code. If you tried
something
similar or tried the code I posted, and it failed, I would be willing to
help you debug it, just tell me how it failed.

Jimmy

Jimmy Kittiyachavalit wrote:

Robert,
Yes, I use this code regularly in all of my rails apps. As far as I
know,
the view code is always run before the layout code. If you tried
something
similar or tried the code I posted, and it failed, I would be willing to
help you debug it, just tell me how it failed.

Jimmy

I found the problem I was having. The view code is run before the
layout. However, I had some partials from within the layout add
javascriipts. Partials in the layout are not run before the layout
itself, and so they can’t add to the head.

Is there any solution for this? Or should I just do these manually?

On 11/30/06, Daniel N [email protected] wrote:

Cheers
Daniel

Not quite enough information. :frowning: The svn is at

http://svn.devjavu.com/liquid/resource_on_demand

Daniel ----- wrote:

I have just released a really simple plugin for this. It ensures that
there
is no duplication of javascripts. Stylesheets can also be included
similarly.

You can get the lowdown at

http://www.agilewebdevelopment.com/plugins/resource_on_demand

Excellent!

Any solution to the problem I raised: demanding javascripts from
partials called within the layout (not the view)?

On 12/1/06, Robert J. [email protected] wrote:

http://www.agilewebdevelopment.com/plugins/resource_on_demand

Excellent!

Any solution to the problem I raised: demanding javascripts from
partials called within the layout (not the view)?

I wouldn’t think it would make a difference. Does it not work in this
situation?

I have just released a really simple plugin for this. It ensures that
there
is no duplication of javascripts. Stylesheets can also be included
similarly.

You can get the lowdown at

http://www.agilewebdevelopment.com/plugins/resource_on_demand

In your head tag of the layout

<%= include_on_demand %>

anywhere in your views

<% demand_javascript “some”, “javascript”, :defaults %>

<% demand_stylesheet “a”, “style”, “sheet” %>

This plugin is very simple but I hope useful :slight_smile:

Cheers
Daniel

On 12/1/06, Daniel N [email protected] wrote:

Daniel ----- wrote:

I would have thought that you could include the
<%= include_on_demand %> in the head tag and then use

<% demand_javascript “my_script” %> in you partial.

I will check it when I get home (not for about 8 or 9 hours though! )

Ok I’ve had a chance to play with it and I’ve found a way that it can be
done. It’s a bit of hackery though.

You can put a yield call in your head tag so that you can get
javascripts
there. But, before your yield in the head tag, you need to render your
partials. You can put them elsewhere in your layout by including these
into
content_for blocks.

In your layout (HTML interspersed where ever )

<% content_for :my_partial do %>
<%= render :partial => ‘my_partial’ -%>
<% end %>

<%= yield :javascripts %>

<%= yield :my_partial %>

In the context of the plugin that I released, if your interested. (I’ve
changed the method names since last night. I wasn’t entirely satisfied
with
them)

<% content_for :my_partial do %>
<%= render :partial => ‘my_partial’ -%>
<% end %>

<%= require_on_demand %>

<%= yield :my_partial %>

and then anywhere in your partial, or views
<% require_javascript “my”, “javascript”, :defaults %>

At least this works on edge :wink:

I hope that will get you going.

Cheers
Daniel

Daniel ----- wrote:

On 12/1/06, Daniel N [email protected] wrote:
Ok I’ve had a chance to play with it and I’ve found a way that it can be
done. It’s a bit of hackery though.

You can put a yield call in your head tag so that you can get
javascripts
there. But, before your yield in the head tag, you need to render your
partials. You can put them elsewhere in your layout by including these
into
content_for blocks.

Great idea - thanks.

On 12/1/06, Daniel N [email protected] wrote:

similarly.

I wouldn’t think it would make a difference. Does it not work in this
situation?

I would have thought that you could include the
<%= include_on_demand %> in the head tag and then use

<% demand_javascript “my_script” %> in you partial.

I will check it when I get home (not for about 8 or 9 hours though! )

On 12/2/06, Michael S. [email protected] wrote:

<%= yield :javascripts %>

end

app/views/layouts/application.rhtml

app/views/whatever/index.rhtml

<% require_css ‘that’ -%>
Partial 2

Initially this was all I thought it was also. But when you have a
partial
in you layout it is not so simple.

The partial in the layout does not seem to be processed prior to the
layout
being rendered. It seems to be rendered more inline than the view.

Following your suggestion above,

app/views/layouts/application

.rhtml

<%= include_required_js %> <%= include_required_css %> <%= yield %>
 <%= render :partial => 'part3' %>
>

Any javascript required in the part3 partial will not be added. This
was
the problem, that partials in the layout are not included.

Also thanx for the |= I didn’t know about that one.

Cheers
Daniel

On Friday 01 December 2006 08:25, Daniel N wrote:

In your layout (HTML interspersed where ever )

<% content_for :my_partial do %>
<%= render :partial => ‘my_partial’ -%>
<% end %>

<%= yield :javascripts %>

<%= yield :my_partial %>

This content_for and yield looks overly complicated to me. In
particular, as content_for does not do much more than set an instance
variable. A tiny bit of code in ApplicationHelper does all that’s
necessary.

Michael

app/helpers/application_helper.rb

module ApplicationHelper
def require_js(*js)
@required_js ||= []
@required_js |= js
end

def require_css(*css)
@required_css ||= []
@required_css |= css
end

def include_required_js
javascript_include_tag(*@required_js)
end

def include_required_css
stylesheet_link_tag(*@required_js)
end
end

app/views/layouts/application.rhtml

<%= include_required_js %> <%= include_required_css %> <%= yield %>

app/views/whatever/index.rhtml

<%= render :partial => ‘part1’ %>
<%= render :partial => ‘part2’ %>

app/views/whatever/_part1.rhtml

<% require_js ‘this’ -%>
<% require_css ‘that’, ‘another_one’ -%>
Partial 1

app/views/whatever/_part2.rhtml

<% require_js ‘this’, ‘something_else’ -%>
<% require_css ‘that’ -%>
Partial 2


Michael S.
mailto:[email protected]
http://www.schuerig.de/michael/