How difficult would it be to add a default `ApplicationView` 🤔?


I am wondering how difficult it would be to add a default and generic ApplicationView. Currently, most of my views are empty. For example:

defmodule Myapp.Web.UsersView do
  use Myapp.Web, :view

defmodule Myapp.Web.CommentsView do
  use Myapp.Web, :view

# etc

I tried introducing plug :set_default_application_view to the :browser pipeline. All it does is simply put_view(conn, ApplicationView). However, I quickly understood that the path to the template is built based on the name of the “View” itself.

What I’m trying to achieve is removing all those blank views and replacing them with a default `ApplicationView’ that renders the templates based on the name of the controller.

i.e. UsersController should use ApplicationView by default and render templates from the templates/users directory.

How difficult would it be to achieve that?

Also, does it make sense to make it a default … in Phx 1.3 maybe :smiley:

Doesn’t render/4 fit your use?

The views aren’t really blank since the accompanying templates become methods in the view.

Surprised you don’t have any additional helper logic for your views right now? That’s generally where that stuff goes.

I don’t thin so. As far as I tested (and checked the source code), if I using something like that:

render conn, Myapp.Web.ApplicationView, "show.html", user: user

The relevant template path will be “templates/application”, and I’d like it to be under the name of the controller. e.g. “templates/users”.

Also, I’d like to use a simpler render - like this one render conn, user: user

Hm, maybe I have to read a bit further about how templates work … thanks.

You can get what you want with the following, but I wouldn’t want to compute the controller subdirectories for every request. As other have said, a better option would be to simply plug put_view and call render/3 with an explicit template name, i.e: render(conn, "user/index.html", ...). If you really want what you describe you can do the following, but I would opt for the former:

# you in your pipeline
plug :put_view, {MyApp.Web.AppView, "app.html"}

# in your :controller block in web.ex
import Phoenix.Controller, except: [render: 2]
import MyApp.Web.Controller

# in your :view block in web.ex
use Phoenix.View, root: "lib/my_app/web/templates",
                  namespace: MyApp.Web,
                  pattern: "*/**"

defmodule MyApp.Web.Controller do
  def render(conn, assigns) do
    view = Phoenix.Controller.view_module(conn)
    template =
      |> Phoenix.Controller.controller_module()
      |> Module.split()
      |> List.last()
      |> Phoenix.Naming.unsuffix("Controller")
      |> Phoenix.Naming.underscore()
      |> Path.join(action_name(conn))

    Phoenix.Controller.render(conn, view, template, assigns)

# in your controllers
render conn, foo: bar

# your app view
defmodule MyApp.Web.AppView do
  use MyApp.Web, :view

# your web/templates dir
# templates/
#   app/
#     user/
#       show.html.eex
#     product/
#       show.html.eex
#     profile/
#       show.html.eex
#       edit.html.eex
#     whatev/
#       ...

That’s an awful lot of work to avoid potential empty view modules that are ready for presentation layer functions as soon as you need them. tldr; use multiple view modules and carry on happily with life :slight_smile:


If you want to really get into the details:

Thanks a lot @chrismccord!

but I wouldn’t want to compute the controller subdirectories for every request

Isn’t that already done for computing the subdirectory based on the name of the view module?
UsersView => “users/” subdirectory. UsersController => “users/” subdirectory. i.e. we are not adding additional calculations but rather switching them from calculating based on the View’s name to calculating based on the Controller’s name… or am I missing something?