With Phoenix 1.2 we get the ability to do this but I’m trying it without success. I posted a question here:
I think this is a very good improvement for folder/components organization so I post this question here too.
With Phoenix 1.2 we get the ability to do this but I’m trying it without success. I posted a question here:
I think this is a very good improvement for folder/components organization so I post this question here too.
Easiest way, just make a new view that is module-spaced to the directory you want the files to be.
I do precisely that with a lot of my re-usable HTML components so I can easily embed them around.
What do you mean by module-spaced?
I was trying to use the new :path and :pattern options to allow wildcard template inclusion as well as customized template directory locations, but your suggestion seems to solve that without the need of these.
Thanks!
The ‘name’ of a View module defines the directory path it loads.
For example I have a helper view so I can embed other eex parts into larger areas:
defmodule MyServer.ComponentsView do
# Stripped code that does not matter
use MyServer.Web, :view
end
This defines a view that will load eex from my web/templates/components
directory.
You can customize how it loads further by instead of 'use’ing your Web’s :view you can just use Phoenix.View, root: "web/templates", namespace: MyServer
and change whatever you want. Or you can go even lower level and call the EEX generator to generate functions in your module. I just make new View’s with appropriate names.
Could you point to a repo online that uses this technique? I also had a similar issue, but I just share a single SharedView
without much customization at this point. So I have multiple a.html.eex
, b.html.eex
, etc. templates with a single view.
See the pattern
option to Phoenix.View, i.e. in your web.ex you can set:
use Phoenix.View, root: "web/templates", pattern: "**/*"
Which would then precompile all templates within the view’s folder as before, as well as all nested directories within the template folder.
I tried both approaches (OvermindDL1 & ChrisMcCord) and they both work.
Nevertheless, using “pattern” (see Chris answer) avoids having to create new views for the subfolders, and, as such, makes our “web/views” folder much cleaner and simple.
Thank you both.
I have made the change to the view part of web.ex as suggested by mccordchris.
I have a folder that I created under lib/web/templates/layout. The name of the folder is member. In it, I have a layout file that I called member.html.eex.
Then in my controller function, I wrote this
render conn, "faqs.html", layout: {Donations.Web.LayoutView, "member/member.html"}
I am having this error
Could not render "member/member.html" for Donations.Web.LayoutView, please define a matching clause for render/2 or define a template at "lib/donations/web/templates/web/layout". No templates were compiled for this module. Assigns:
Thanks for your help. I am using phoenix 1.3
Those do not match? ^.^
Also, note the glob pattern needs to be pattern: "**/*
Hey guys - thanks to both @chrismccord and @OvermindDL1 for the thorough overview here. I have a question about this pattern, though. Are there any drawbacks to using the root use Phoenix.View
approach here in lieu of the use MyApp.Web , :view
approach. Is there a way to do this with MyApp.Web
or are these mutually exclusive?
Thanks in advance for any comments here.
Mike Zazaian
Also, now that I look at it, I’m wondering if this approach is different with Phoenix 1.3-rcX. Are there any substantive changes here? I’ll probably end up digging into the Phoenix.View module here anyway, but I figured it wouldn’t hurt to ask.
Thanks again,
Mike
Hey all - so for my purposes I’ve resolved this problem. Rather than trying to work around the use MyApp.Web, :view
call, I went straight to the source and added the pattern
option directly to the primary MyApp.Web.view
definition:
# /lib/my_app/web/web.ex
def view do
quote do
use Phoenix.View, root: "lib/my_app/web/templates",
pattern: "**/*",
namespace: MyApp.Web
..
end
end
This allowed me to create multiple sub-dirs within each view root directory (for my purposes this was web/templates/page
), and then to call renders inside of master index.html
template using relative paths from that root directory:
# /lib/my_app/web/templates/page/index.html.haml
# please note that all of my view templates use HAML
.container
-# Top Section
= render MyApp.Web.PageView, 'sections/_top_section.html'
-# More Stuff Section
= render MyApp.Web.PageView, 'sections/_more_stuff.html'
-# Final Stuff Section
= render MyApp.Web.PageView, 'sections/final_stuff.html'
Additionally, because I ended up with about 12 partials overall, and because my app name was pretty long, I decided to alias the PageView module within itself for easier partial calls:
# /lib/my_app/web/views/page_view.ex
defmodule MyApp.Web.PageView do
use MyApp.Web, :view
alias MyApp.Web.PageView
end
This made my primary index template a big more svelte:
# /lib/my_app/web/templates/page/index.html.haml
.container
-# Top Section
= render PageView, 'sections/_top_section.html'
-# More Stuff Section
= render PageView, 'sections/_more_stuff.html'
-# Final Stuff Section
= render PageView, 'sections/final_stuff.html'
Anyway, happy to answer any questions, or field any fire for doing any of these things incorrectly according to convention. Also please note that I’m using Phoenix 1.3-rc2, so this may be different for Phoenix 1.2.4 apps.
# /lib/my_app/web/web.ex
def view do
quote do
use Phoenix.View, root: "lib/my_app/web/templates",
pattern: "**/*",
namespace: MyApp.Web
..
end
end
Doesn’t using Phoenix.View
like this mean that if you define a Page.SectionView
you could either use render PageView, 'sections/_top_section.html'
or render Page.SectionView, '_top_section.html'
?
Not trying to advocate against your approach but just trying to think through the implementation (and make sure I’m understanding everything myself!).
If you were to use the PageView
render, it would call the template path /templates/page/sections/_top_section.html.eex
. If you were to use the SectionView
, it would call the template path /templates/section/_top_section.html.eex
.
Edit: Remember, the path is always relative to the view being used. The PageView
is prefaced with /page
and the SectionView
is prefaced with /section
. So when you call render using the PageView
and set the path as /sections/file.html
, remember that it’s prefaced with /page
, making the actual path /page/sections/file.html
.
Hey there @axelson - thanks for the question! I’m new-ish to using Phoenix in practice myself so I’ll do my best to field this one.
@steve’s answer seems to be correct at least relative to what you may have meant. We’re only using web/templates/page
as the presumed root dir for our view because we’re using the PageView to render templates.
That said, it seems like maybe you’re asking if adding namespaces to the view module itself would presume a new directory for each namespace in the template path. Is that correct? I’m not sure if that was just a typo.
If that’s what you meant, if you added a Page
namespace to your SectionView, i.e. MyApp.Web.Page.SectionView
, then it would automatically look for templates in web/templates/page/section
, but not web/templates/page/sections
. The directory structure is simply mirroring the camel-cased portion of the module before View
. Consider:
# web/views/page/section_view.ex
module MyApp.Web.Page.SectionView do
use MyApp.Web, :view
# by default will look for templates in the root dir web/templates/page/section
end
However if you named your view MyApp.Web.Page.SectionsView
, then both PageView
and Page.SectionsView
would use templates in the page/sections
directory, though only the Page.SectionsView
module would use that directory as its template root.
Does that answer your question? I’m glad to be discussing this because I’m learning a lot myself.
This was very very useful. Thank you.
How has this changed with 1.7+?