Calling other controllers?

So how do people do this? It’s far harder than it appears to build a dashboard!

I want to have a nice page with blocks of content, like a customer contact sheet, a notes field, a task module, etc.
So naive me says, 'I’ll build a controller/view/template system for each module! Then have a main page that just renders each template for that module, which will call the appropriate action on the controller and load the data in. Easy!"

But no: you can render a template, and even render a view… but the controller that actually gets the info for the note, or the customer data–apparently that cannot be called! Can it?

Do I really have to build a master controller that does the work of every single controller for a module? That will be thousands of lines per action, possibly! What a tangled, coupled mess!

It cannot possibly be this hard to render multiple isolated, different pieces of content on a page. Surely there’s a solution if you want a child template that is more than just static html, without having to laboriously build up every single assign in advance in one huge controller action.

How is this supposed to be done?

Controllers are just functions, so you can call them as you like. But each controller (which is usually the last in the plug chain) will return a rendered document.

So id write a function for each of the dashboard cards which manipulates the conn and the controller pipes through them rendering it at the end.

1 Like

So for example, my Web.Admin controller that is responsible for laying out the dashboard would have a series of defp’s, one for each module, like defp notes and defp product, and they just call the corresponding controller. Ok, but what do I do with the returned value-- I call it like notes = MyWeb.Notes.Controller (it puts a string of HTML into the “notes” variable, right?) and then add notes to the assign?

And then just render that assign in my master template? Or am I getting this wrong?

You can call views instead of controllers to render each note. Then collect them with controller and return the result.

2 Likes

Don’t put the logic in the controller, put it in other modules, then just call those from the requisite controllers and pass the data on to the views.

5 Likes

Hmm. I may have to rethink this a bit. Turns out, when you call a controller, it runs through it’s logic and then it sends a response to a client. So if you call another controller, it renders its view and sends the reply. Thus, you can only call one controller that renders a view and template before a response is sent to the browser.

Kind of destroys the whole “call a series of controllers” bit. The only thing I’ve found is to call the function render_to_string on a view inside of the child controller and return that string.

Which, of course, is automatically html escaped and thus when you try to output it to the screen, you get a nice perfectly rendered string of html, that is not actually html. Only if I call the Phoenix.HTML.raw function on the string do I actually get html… and that’s suboptimal.

So it seems that trying to use a child controller to render its own views and templates is not going to work.

If I understand you correctly OvermindDL1, the proper thing to do would be something like this.

I have a notes module, which is supposed to allow a note to be written and displayed. For my dashboard controller, it should call into the Notes context and get a map of the appropriate notes, however the Notes context determines that(scoped to the current user, or product or whatever). Then, in my dashboard controller, it takes that map of notes and throws them into the notes assign in the Conn. In my dashboard template, I then render the Notes.View as a partial and pass the conn, and it renders the appropriate template, using the Notes assign. I do the same thing for my tasks module/context, and anything else.

So the flow is basically 1) Dashboard calls function in the child module that returns a map of data; 2) Dashboard assigns that map to an assign in Conn, 3) in the dashboard template, render the child view partial, passing in the assign from Conn to the child view/template. 4) Repeat as necessary to call all my separate modules.

Is that about it?

That’s probably a bad idea, try rendering to iodata instead. Would be faster and more efficient.

That’s what I’d do (and currently do, I have a similar setup at work with some backend absinthe mixed in). :slightly_smiling_face: