RubyOnRails like content_for

467 views
Skip to first unread message

Milan Jarić

unread,
Jan 11, 2015, 5:19:42 PM1/11/15
to phoeni...@googlegroups.com
Hi,

Could anyone give me a tip how to implement feature content_for like it is in ruby on rails? (rails doc are here just for reference  )

What I need is to include bunch of javascript related to specific view but render it at the end of website layout template. Here is example, which may be inaccurate due elixir language difference comparing to ruby.

<html>
<head>
<title>some title<//title>
<link href="..." />
</head>
<body>
   
<%= @inner %>
 
<script type="text/javascript" src="/jquery.js"></script>
 
<script type="text/javascript" src="/phoenix.js"></script>
 
<%= render_content @conn, :view_javascript_includes %>
</body>
</html>



Then in view

<p>Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s</p>

<% content_for @conn, :view_javascript_includes do %>
<script type="text/javascript">
$
.ready(function(){
    alert
("Executed after all scripts are included");
});
</script>
<% end %>

Chris McCord

unread,
Jan 12, 2015, 12:09:42 AM1/12/15
to phoeni...@googlegroups.com
Hi Milan,

We don’t provide `content_for`. Adding support for it gets really complex with an immutable language and it’s difficult to do without process registry hacks. What you can do instead is embed data as connection assigns prior to rendering.

#controller
conn
|> assign(:page_title, “Page Title!”)
|> render(“index.html”)


# layout
<title><%= @page_title || “Home” %>
<body>
  <%= @inner %>
</body



Make sense? You should be able to accomplish the same types of things as content_for, but not within the subview. 

Chris

--
You received this message because you are subscribed to the Google Groups "phoenix-talk" group.
To unsubscribe from this group and stop receiving emails from it, send an email to phoenix-talk...@googlegroups.com.
To post to this group, send email to phoeni...@googlegroups.com.
Visit this group at http://groups.google.com/group/phoenix-talk.
To view this discussion on the web visit https://groups.google.com/d/msgid/phoenix-talk/9bc384f7-da28-4404-bf21-05884c1affd4%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Milan Jarić

unread,
Jan 12, 2015, 1:56:26 AM1/12/15
to phoeni...@googlegroups.com
It makes sense but it is not somthing I want to do with my controller. Simply it is not its resposibility and makes my view action too smart. Some things need to be done on layout level or osme point out of view.

I allways had to do website which has hidden gems. One type of them is technical like I explained above, second is with shared widgets which should be rendered on layout and third which is not that common when action need to comunicate with outer part of view e.g. widget.

I see that all this issues are related to nature of elixir/erlang so I was thinking to try making some agent insted using plug, since plug requires to have conn struct replaced in execution pipline. Then spawn new agent pid per each reqest in order to have some kind of bag where I can put values from view or controller if required.

The thing is I recently started to explore erlang so Im not sure if this antipattern here :)

Chris McCord

unread,
Jan 12, 2015, 9:13:12 AM1/12/15
to phoeni...@googlegroups.com
Your other option is to `put_layout(:none)` in your pipeline/controller, then explicitly render what you need in your views:

def render(“index.html”, assigs) do
render SomeView, “index.html”, Dict.merge(assigns,
layout: SomeLayout,
title: “Some Title”,
widget: render(“widget.html”, assigns)
)
end


This isn’t as convenient as `content_for`, but as a full-time Ruby/Rails developer for the last five years, I haven’t missed content_for yet in Phoenix. I’m open to elegant solutions though if you have ideas :)

Chris
> --
> You received this message because you are subscribed to the Google Groups "phoenix-talk" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to phoenix-talk...@googlegroups.com.
> To post to this group, send email to phoeni...@googlegroups.com.
> Visit this group at http://groups.google.com/group/phoenix-talk.
> To view this discussion on the web visit https://groups.google.com/d/msgid/phoenix-talk/eb3ea5ea-21ce-4d8f-8eb9-3a58b330719f%40googlegroups.com.

Milan Jarić

unread,
Jan 12, 2015, 5:24:55 PM1/12/15
to phoeni...@googlegroups.com
Thanks. That could work for some cases. I will give it a try with different approaches  and I hope it would not be trouble to come back here to discuss is it proper way of thinking since I'm still struggling to accept elixir/erlang way of thinking. It is like a bad habit :)

tr...@blit.com

unread,
Jan 30, 2015, 2:22:21 AM1/30/15
to phoeni...@googlegroups.com
A common content_for use for me us loading my javascripts at the bottom of the layout and then using content_for :javascripts to get any js code defined in my templates to render just before closing the body tag in the template.

I'm considering making a plug that can perform server side includes (SSI), only instead of full blown SSI http://en.wikipedia.org/wiki/Server_Side_Includes, something simpler.  A example:

<content for="javascripts">
</content>

and in the layout just use a self closing tag with id:

<content id="javascript"/>

Good idea?

Chris McCord

unread,
Feb 2, 2015, 1:17:49 PM2/2/15
to phoeni...@googlegroups.com, tr...@blit.com
You could plug your scripts in the controller, and render them from the layout:

    # some_controller.ex
    plug :put_scripts, ["foo.js", "baz.js"]
    plug :action
    ... 
    defp put_scripts(conn, scripts), do: assign(conn, :scripts, scripts)


    # application.html.eex
    <%= for script <- @scripts || [] %>
    ...
    <% end %>

Scott Parker

unread,
Feb 21, 2015, 8:58:19 PM2/21/15
to phoeni...@googlegroups.com
Sorry to resurrect an old thread, but I too would love something like content_for within Phoenix. Since you mentioned ideas earlier, I'll throw one out: since the result of EEx.compile_string within the template engine is a AST, it should be theoretically possible to walk the complete tree of layout + partials to match named blocks within the layout with their values within the ASTs of children.

I'm am still coming to grips with the Phoenix codebase so this may be impractical for some reason I'm overlooking (e.g. Phoenix converts these ASTs to string blobs too early to be of use) but does something like this sound reasonable-ish? I find myself longing for something to keep this kind of logic within the templates. Perhaps there is some convention-over-configuration way to do this as well (e.g. naming a template in a particular way automatically plugs it into the layout when views from its folder are rendered) 

What do you think?

Thanks!
SP

Milan Jarić

unread,
Feb 21, 2015, 9:29:08 PM2/21/15
to phoeni...@googlegroups.com
Hi Scott,

The problem is actually the nature of erlang/elixir language. Once you pass parameter to function you and function is changing it, it is changing a copy!

This means, event you create content_for @conn function the @conn is copy inside it. So outer template will work with it's own copy of @conn structure.


I have an idea where I would make an agent whoci should hold request "state". @conn structure holds request ID so it should be enough to identify request once some template call content_for @conn ... it will change the state and once you want to get that state back to any template simply call render_content @conn.

I was busy with my current job so haven't had time to work on this idea yet, anyway, as soon as I start I will send link here to git repo.
Message has been deleted

Steve Pallen

unread,
Feb 22, 2015, 2:10:53 PM2/22/15
to phoeni...@googlegroups.com
Funny, I came to the group to post this exact question a couple hours ago....

I've been playing around with a solution using ets. This is only a rough prototype, but it does seem to work...

 
Steve

Milan Jarić

unread,
Feb 23, 2015, 11:58:08 AM2/23/15
to phoeni...@googlegroups.com
Hi Steave,

Did you try to return tuple {:ok, ""} instead of nil in content_for function of view module



def content_for name , do: block do
:ets.insert :phoenix, {name, block}
{:ok, ""}
end


Or set safe atom as first element of tuple.

Could you please try since Im curious what should happen. Btw, what would happen if 2 concurent requests occur? since I belive it would give unexpected result if strings are different eg. You pass username as additional data.

Steve Pallen

unread,
Feb 24, 2015, 10:27:43 AM2/24/15
to phoeni...@googlegroups.com
Milan,

Returning {:ok, ""} does not work. Phoenix.HTML.safe does not like it. However, returning nil from content_for when called with <%= content_for ...%> is working fine. 

Good catch on the concurrent requests. Probably need to add self to the :ets key and remove the entry at the end of the request. 

Milan Jarić

unread,
Feb 25, 2015, 9:05:09 AM2/25/15
to phoeni...@googlegroups.com
Hi Steve,

I checked again your template. It should be safe to remove equal sign before content_for since you dont need to rener anything at yhat point :)

Steve Pallen

unread,
Feb 25, 2015, 9:39:19 PM2/25/15
to phoeni...@googlegroups.com
Can't remove the = sign. When you do, it returns the html in both the page and the layout. 
Reply all
Reply to author
Forward
0 new messages