How to edit controller's Variables in view before loading in template

Hello, I want to edit Variables of action controller before loading in template , for ex :

def index(conn, _params) do

    breadcrumb = [
      %{
        title: "کامپوننت آسان پرداخت",
        link: "طراحی سایت و بهینه سازی",
      }
    ]
    render(conn, "index.html", breadcrumb: breadcrumb)
  end

I want to edit breadcrumb like this : views/blog_view.ex

def index(conn, _params) do

    new_breadcrumb = [
      %{
        title: "test",
        link: "test",
      }
    ]
    edited_breadcrumb = @breadcrumb ++ new_breadcrumb
    render(conn, "index.html", breadcrumb: edited_breadcrumb)
  end

this path is <%= render TrangellHtmlSiteWeb.BlogView, "_breadcrumb.html", conn: @conn, breadcrumb: @breadcrumb %>

But I couldn’t edit breadcrumb can you help me ?

Maybe like this?

@breadcrumb = @breadcrumb ++ new_breadcrumb

if you meant this, it wouldn’t work

  def index(conn, _params) do

    new_breadcrumb = [
      %{
        title: "کامپوننت آسان پرداخت",
        link: "طراحی سایت و بهینه سازی",
      }
    ]
    # edited_breadcrumb = breadcrumb ++ new_breadcrumb
    @breadcrumb = @breadcrumb ++ new_breadcrumb
    render(conn, "index.html", breadcrumb: breadcrumb)
  end

Error :

== Compilation error in file lib/trangell_html_site_web/views/blog_view.ex ==
** (CompileError) lib/trangell_html_site_web/views/blog_view.ex:14: undefined function breadcrumb/0
    (stdlib) lists.erl:1338: :lists.foreach/2
    (stdlib) erl_eval.erl:670: :erl_eval.do_apply/6
    (elixir) lib/kernel/parallel_compiler.ex:198: anonymous fn/4 in Kernel.ParallelCompiler.spawn_workers/6

or

  def index(conn, _params) do

    new_breadcrumb = [
      %{
        title: "tts",
        link: "طراحی سایت و بهینه سازی",
      }
    ]
    # edited_breadcrumb = breadcrumb ++ new_breadcrumb
    @breadcrumb = @breadcrumb ++ new_breadcrumb
    render(conn, "index.html", breadcrumb: @breadcrumb )
  end

Errors

Warning	Elixir	undefined module attribute @breadcrumb, please remove access to @breadcrumb or explicitly set it before access	13:5
Warning	Elixir	undefined module attribute @breadcrumb, please remove access to @breadcrumb or explicitly set it before access

I am sorry, I have been doing some Rails these days (It’s unfortunate I could not choose Elixir stack).

Mixing Elixir, Rails at the same time is kind of painful :slight_smile:

1 Like

Your @breadcrumb is a module attribute, assigned at compile time, you can not use it that way.

I’m currently not using phoenix, but I skimmed the documentation. The argument you named _params is named assigns throughout the documentation, I’m pretty sure that it contains a field :breadcrumb, just look out for it. Logger.debug inspect(assigns) to learn about its structure.

Also, please remember, that you made :breadcrumb a map, you can not modify it using Kernel.++/2. You need to use either the map update syntax (%{foo | bar: :baz}) or use the Map module. Also there is Kernel.put_in/2 and /3 which might help you to update the assigns in a single sweep.

2 Likes

Hello, I’m confused more than before :frowning_face: can you write a sample code for this ? I actually wanted to create a list

  new_breadcrumb = [
    %{
      title: "test",
      link: "test"
    }

one things, I edited my code like this :

  def index(conn, _params) do

    new_breadcrumb = [
      %{
        title: "tts",
        link: "طراحی سایت و بهینه سازی",
      }
    ]
    render(conn, "index.html", breadcrumb: new_breadcrumb )
  end

but it didn’t yet work !! I didn’t update and I imported a new value instead of the old value.

In view , its path : views/blog_view.ex

def breadcrumb(assigns) do
    breadcrumb = [
        %{
          title: "tts",
          link: "طراحی سایت و بهینه سازی",
        }
      ]
    breadcrumb
  end

in Blog template , its path : templates/blog/index.html.eex

<%= render TrangellHtmlSiteWeb.BlogView, "_breadcrumb.html", conn: @conn, breadcrumb: @breadcrumb %>

in partial , its path : templates/blog/_breadcrumb.html.eex

<%= for %{title: title, link: link} <- @breadcrumb do %>
  <%= title %>
<% end %>

Even, it doesn’t work :frowning_face:

breadcrumb should be a map, currently it’s a map wrapped in a list. Try this:

breadcrumb = %{
  title: "کامپوننت آسان پرداخت",
  link: "طراحی سایت و بهینه سازی",
}

You do… My fault. I simply haven’t seen your squarebrackets… So you are fine with using Kernel.++/2, still the other things I said hold.

Inspect the structure of the second argument to your function, see how :breadcrumbs is saved in there, modifiy it to your needs and then return it.

Remember, you are coding the view, and not a controller, you do not define an action, but specify how to render a certain ressource.

As far as I read it in the Phoenix documentation, you are supposed to have a function render in the view, matching on the resource names, returning a map of assigns, so roughly this should do it:

def render("index.html", assigns = %{breadcrumb: old_bc}) do
  %{assigns | breadcrumb: [%{title: "new", link: "breadcrumb"} | old_bc]}
end
1 Like

I have tried this like your code but I have had an error like now :

[error] #PID<0.1535.0> running TrangellHtmlSiteWeb.Endpoint (cowboy_protocol) terminated
Server: localhost:9991 (http)
Request: GET /blog
** (exit) an exception was raised:
    ** (Protocol.UndefinedError) protocol Phoenix.HTML.Safe not implemented for %{breadcrumb: [%{link: "breadcrumb", title: "new"} | %{link: "طراحی سایت و بهینه سازی", title: "test1"}], conn: %Plug.Conn{adapter: {Plug.Adapters.Cowboy.Conn, :...}, assigns: %{breadcrumb: %{link: "طراحی سایت و بهینه سازی", title: "test1"}, layout: {TrangellHtmlSiteWeb.LayoutView, "app.html"}}, before_send: [#Function<0.130566154/1 in Plug.CSRFProtection.call/2>, #Function<4.72544617/1 in Phoenix.Controller.fetch_flash/2>, #Function<0.45862765/1 in Plug.Session.before_send/2>, #Function<1.122369158/1 in Plug.Logger.call/2>, #Function<0.9464484/1 in Phoenix.LiveReloader.before_send_inject_reloader/2>], body_params: %{}, cookies: %{}, halted: false, host: "localhost", method: "GET", owner: #PID<0.1535.0>, params: %{}, path_info: ["blog"], path_params: %{}, port: 9991, private: %{TrangellHtmlSiteWeb.Router => {[], %{}}, :phoenix_action => :index, :phoenix_controller => TrangellHtmlSiteWeb.BlogController, :phoenix_endpoint => TrangellHtmlSiteWeb.Endpoint, :phoenix_flash => %{}, :phoenix_format => "html", :phoenix_layout => {TrangellHtmlSiteWeb.LayoutView, :app}, :phoenix_pipelines => [:browser], :phoenix_router => TrangellHtmlSiteWeb.Router, :phoenix_template => "index.html", :phoenix_view => TrangellHtmlSiteWeb.BlogView, :plug_session => %{}, :plug_session_fetch => :done}, query_params: %{}, query_string: "", remote_ip: {127, 0, 0, 1}, req_cookies: %{}, req_headers: [{"host", "localhost:9991"}, {"connection", "keep-alive"}, {"cache-control", "max-age=0"}, {"upgrade-insecure-requests", "1"}, {"user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36"}, {"accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8"}, {"referer", "http://localhost:9991/blog"}, {"accept-encoding", "gzip, deflate, br"}, {"accept-language", "en-US,en;q=0.9,fa;q=0.8,es;q=0.7"}], request_path: "/blog", resp_body: nil, resp_cookies: %{}, resp_headers: [{"cache-control", "max-age=0, private, must-revalidate"}, {"x-frame-options", "SAMEORIGIN"}, {"x-xss-protection", "1; mode=block"}, {"x-content-type-options", "nosniff"}, {"x-download-options", "noopen"}, {"x-permitted-cross-domain-policies", "none"}], scheme: :http, script_name: [], secret_key_base: "dvb4hlpswTog8giDVaUB45YFIJgvkfEG47TjgBOtSiahPUPTycPCpSbSRjhlmsUp", state: :unset, status: nil}, view_module: TrangellHtmlSiteWeb.BlogView, view_template: "index.html"}. This protocol is implemented for: Atom, BitString, Date, DateTime, Float, Integer, List, NaiveDateTime, Time, Tuple
        (phoenix_html) /Applications/MAMP/htdocs/elixir-ex-source/Trangell_Main/trangell_html_site_umbrella/deps/phoenix_html/lib/phoenix_html/safe.ex:1: Phoenix.HTML.Safe.impl_for!/1
        (phoenix_html) /Applications/MAMP/htdocs/elixir-ex-source/Trangell_Main/trangell_html_site_umbrella/deps/phoenix_html/lib/phoenix_html/safe.ex:15: Phoenix.HTML.Safe.to_iodata/1
        (trangell_html_site_web) lib/trangell_html_site_web/templates/layout/app.html.eex:24: TrangellHtmlSiteWeb.LayoutView."app.html"/1
        (phoenix) lib/phoenix/view.ex:332: Phoenix.View.render_to_iodata/3
        (phoenix) lib/phoenix/controller.ex:740: Phoenix.Controller.do_render/4
        (trangell_html_site_web) lib/trangell_html_site_web/controllers/blog_controller.ex:1: TrangellHtmlSiteWeb.BlogController.action/2
        (trangell_html_site_web) lib/trangell_html_site_web/controllers/blog_controller.ex:1: TrangellHtmlSiteWeb.BlogController.phoenix_controller_pipeline/2
        (trangell_html_site_web) lib/trangell_html_site_web/endpoint.ex:1: TrangellHtmlSiteWeb.Endpoint.instrument/4
        (phoenix) lib/phoenix/router.ex:278: Phoenix.Router.__call__/1
        (trangell_html_site_web) lib/trangell_html_site_web/endpoint.ex:1: TrangellHtmlSiteWeb.Endpoint.plug_builder_call/2
        (trangell_html_site_web) lib/plug/debugger.ex:122: TrangellHtmlSiteWeb.Endpoint."call (overridable 3)"/2
        (trangell_html_site_web) lib/trangell_html_site_web/endpoint.ex:1: TrangellHtmlSiteWeb.Endpoint.call/2
        (plug) lib/plug/adapters/cowboy/handler.ex:16: Plug.Adapters.Cowboy.Handler.upgrade/4
        (cowboy) /Applications/MAMP/htdocs/elixir-ex-source/Trangell_Main/trangell_html_site_umbrella/deps/cowboy/src/cowboy_protocol.erl:442: :cowboy_protocol.execute/4

sorry it doesn’t, it saw original data not the data in the view

Sorry, I misread the docs here and misunderstood some of the JSON examples.

So my first suggestion is to actually try out the docs for yourself, reading them carefully with the context of a project in mind while actively developing in phoenix is a much better point of stand for such stuff.

My second suggestion, after re-skimming the documentation is this:

def render("index.html", assigns = %{breadcrumb: old_bc}) do
  render(__MODULE__, "index.html", %{assigns | breadcrumb: [%{title: "new", link: "breadcrumb"} | old_bc]})
end

Also, from your posted error, it seems as if your original value of :breadcrumb is not a list, you should probably fix that one as well.

Also, again, my answers are solely based on what I read in the documentation, I do not work with phoenix currently, I might misunderstand things.

1 Like

Thank you , I am reading the all of the documents which I have found for 3 days, but I have not been able to fix this.

I understand

I fix this and change it to a list like this in controller:

def index(conn, _params) do

    breadcrumb = [
      %{
        title: "کامپوننت آسان پرداخت",
        link: "طراحی سایت و بهینه سازی",
      }
    ]
    render(conn, "index.html", breadcrumb: breadcrumb)
  end

after changing the cod to a list , it doesn’t work and passes same error

[error] #PID<0.423.0> running TrangellHtmlSiteWeb.Endpoint (cowboy_protocol) terminated
Server: localhost:9991 (http)
Request: GET /blog
** (exit) an exception was raised:
    ** (Protocol.UndefinedError) protocol Phoenix.HTML.Safe not implemented for %{breadcrumb: [%{link: "breadcrumb", title: "new"}, %{link: "طراحی سایت و بهینه سازی", title: "کامپوننت آسان پرداخت"}], conn: %Plug.Conn{adapter: {Plug.Adapters.Cowboy.Conn, :...}, assigns: %{breadcrumb: [%{link: "طراحی سایت و بهینه سازی", title: "کامپوننت آسان پرداخت"}], layout: {TrangellHtmlSiteWeb.LayoutView, "app.html"}}, before_send: [#Function<0.130566154/1 in Plug.CSRFProtection.call/2>, #Function<4.72544617/1 in Phoenix.Controller.fetch_flash/2>, #Function<0.45862765/1 in Plug.Session.before_send/2>, #Function<1.122369158/1 in Plug.Logger.call/2>, #Function<0.9464484/1 in Phoenix.LiveReloader.before_send_inject_reloader/2>], body_params: %{}, cookies: %{}, halted: false, host: "localhost", method: "GET", owner: #PID<0.423.0>, params: %{}, path_info: ["blog"], path_params: %{}, port: 9991, private: %{TrangellHtmlSiteWeb.Router => {[], %{}}, :phoenix_action => :index, :phoenix_controller => TrangellHtmlSiteWeb.BlogController, :phoenix_endpoint => TrangellHtmlSiteWeb.Endpoint, :phoenix_flash => %{}, :phoenix_format => "html", :phoenix_layout => {TrangellHtmlSiteWeb.LayoutView, :app}, :phoenix_pipelines => [:browser], :phoenix_router => TrangellHtmlSiteWeb.Router, :phoenix_template => "index.html", :phoenix_view => TrangellHtmlSiteWeb.BlogView, :plug_session => %{}, :plug_session_fetch => :done}, query_params: %{}, query_string: "", remote_ip: {127, 0, 0, 1}, req_cookies: %{}, req_headers: [{"host", "localhost:9991"}, {"connection", "keep-alive"}, {"cache-control", "max-age=0"}, {"upgrade-insecure-requests", "1"}, {"user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36"}, {"accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8"}, {"referer", "http://localhost:9991/blog"}, {"accept-encoding", "gzip, deflate, br"}, {"accept-language", "en-US,en;q=0.9,fa;q=0.8,es;q=0.7"}], request_path: "/blog", resp_body: nil, resp_cookies: %{}, resp_headers: [{"cache-control", "max-age=0, private, must-revalidate"}, {"x-frame-options", "SAMEORIGIN"}, {"x-xss-protection", "1; mode=block"}, {"x-content-type-options", "nosniff"}, {"x-download-options", "noopen"}, {"x-permitted-cross-domain-policies", "none"}], scheme: :http, script_name: [], secret_key_base: "dvb4hlpswTog8giDVaUB45YFIJgvkfEG47TjgBOtSiahPUPTycPCpSbSRjhlmsUp", state: :unset, status: nil}, view_module: TrangellHtmlSiteWeb.BlogView, view_template: "index.html"}. This protocol is implemented for: Atom, BitString, Date, DateTime, Float, Integer, List, NaiveDateTime, Time, Tuple
        (phoenix_html) /Applications/MAMP/htdocs/elixir-ex-source/Trangell_Main/trangell_html_site_umbrella/deps/phoenix_html/lib/phoenix_html/safe.ex:1: Phoenix.HTML.Safe.impl_for!/1
        (phoenix_html) /Applications/MAMP/htdocs/elixir-ex-source/Trangell_Main/trangell_html_site_umbrella/deps/phoenix_html/lib/phoenix_html/safe.ex:15: Phoenix.HTML.Safe.to_iodata/1
        (trangell_html_site_web) lib/trangell_html_site_web/templates/layout/app.html.eex:24: TrangellHtmlSiteWeb.LayoutView."app.html"/1
        (phoenix) lib/phoenix/view.ex:332: Phoenix.View.render_to_iodata/3
        (phoenix) lib/phoenix/controller.ex:740: Phoenix.Controller.do_render/4
        (trangell_html_site_web) lib/trangell_html_site_web/controllers/blog_controller.ex:1: TrangellHtmlSiteWeb.BlogController.action/2
        (trangell_html_site_web) lib/trangell_html_site_web/controllers/blog_controller.ex:1: TrangellHtmlSiteWeb.BlogController.phoenix_controller_pipeline/2
        (trangell_html_site_web) lib/trangell_html_site_web/endpoint.ex:1: TrangellHtmlSiteWeb.Endpoint.instrument/4
        (phoenix) lib/phoenix/router.ex:278: Phoenix.Router.__call__/1
        (trangell_html_site_web) lib/trangell_html_site_web/endpoint.ex:1: TrangellHtmlSiteWeb.Endpoint.plug_builder_call/2
        (trangell_html_site_web) lib/plug/debugger.ex:122: TrangellHtmlSiteWeb.Endpoint."call (overridable 3)"/2
        (trangell_html_site_web) lib/trangell_html_site_web/endpoint.ex:1: TrangellHtmlSiteWeb.Endpoint.call/2
        (plug) lib/plug/adapters/cowboy/handler.ex:16: Plug.Adapters.Cowboy.Handler.upgrade/4
        (cowboy) /Applications/MAMP/htdocs/elixir-ex-source/Trangell_Main/trangell_html_site_umbrella/deps/cowboy/src/cowboy_protocol.erl:442: :cowboy_protocol.execute/4

If I use this code, my code will go to a big loop and my ram will go to high

def render("index.html", assigns = %{breadcrumb: old_bc}) do
  render(__MODULE__, "index.html", %{assigns | breadcrumb: [%{title: "new", link: "breadcrumb"} | old_bc]})
end

Im forced to stop and start again my terminal or phx server

So that render function actually does call the view function again. So you need a function which actually renders the template without calling the function again.

I do fear you need to do so by using EEx functions directly. Or you could open a feature request on the phoenix issue tracker to get a function that allows you what you want to do.

As I do assume you are accessing @breadcrumb from the layouts template, I’m not even sure how one could work around this limitation easily.

1 Like