I've been thinking about this problem for the past few days and have gotten no-where! Even after extensive Googling, I've not found anything that begins to really answer my question satisfactorily. When it comes to an application Layout, where in the Controller do you populate the data for shared Views within a Model-View-Controller (MVC) paradigm?
To expand on this, imaging that we have a site layout like this:
| || || |
| || |
| || || |
In the first build of our application, the "Secondary Content" is static; that is, it is hard-coded in the template. For each page that is requested by the user, the only job the Controller has to perform is to assemble the "Primary Content" data and include the primary View:
- Route request to controller.
- Load data for primary content.
- Render primary content.
- Render layout.
This is fine. This makes sense to me.
But, imagine that a few months later, you say to yourself, "Hey, it would be awesome if my Secondary Content contained my latest Tweets!" Now, the secondary content is no longer static - it requires data to be loaded and a secondary view to be rendered. And, to complicate matters, it would probably be nice if the "Tweets" view was cached for some period of time (since people aren't tweeting every second).
Now, the request processing has to look something like this:
- Route request to controller.
- Load data for primary content.
- Render primary content.
- Check secondary content cache.
- Load data for secondary content.
- Render secondary content.
- Render layout.
Right now, I am not sure how to solve this problem is a "clean" way. That is to say, I have no idea how to solve this problem in a proper Model-View-Controller (MVC) context. I know that in MVC, my View isn't supposed to know about my Model; I know that it is the Controller's job to aggregate the appropriate data and to make it available to the View.
That said, I definitely don't want to add this secondary content population and caching to every existing Controller in my application. That seems like a poor move, especially since the specifics of my Layout might continue to change over time. I wouldn't want to have to update every single Controller every single time new dynamic content is added to the Layout.
Can a Layout have its own Controller? Right now, I only think about Controllers in terms of processing events; but, can a separate Controller also help render a Layout? If so, this "Layout Controller" could load and render "default" views if the "Event Controller" has not already populated them.
Right now, I am a bit stumped. I've solved this problem a number of different ways in the past; but, all of those solutions used procedural spaghetti code in which the Controller and the View were basically one-and-the-same.
If anyone has suggestions, I would be supremely grateful!
Looking For A New Job?
- ColdFusion & AngularJS Web Developer for rapidly growing startup - at Intuvo
- Senior Coldfusion Web Developer (UK - Kent) at EPG Health Media
- Web Developer at Bridge Catalog
- ColdFusion Developer at Company Confidential
- FT Remote Web Application Developer (ColdFusion) at Quality Matters
I'm subscribing to this because I have the same exact question. I have a feeling it revolves around controller event chaining, meaning you have one event for primary and one for secondary and a third that chains the two together for output.
Dunno if that actually helps you, but Symfony2 adds the "render" function to the Twig template renderer. "Render" refers to an action from a controller, which itself can render another template. So the MVC pattern is still applied and you have quality code.
Maybe you should have a look at it.
That's kind of in the direction my mind is going - that an event might be processed by multiple controllers in one pass.
Since you and I have the same types of questions, I'm just going to let you post since you have the blog and I don't, :). I'll just follow the answers, :)
I seem to remember the way that Model Glue works is really helpful for this kind of problem.
There is a config layer that maps to various controller methods. A single request action can trigger calls to multiple controller methods and render multiple views.
The net result is that, by the time your first bit of view is to be rendered, all the data is available.
It's been a few years since I've had the chance to work with MG. If you aren't familiar with it, I'd check out the docs - sounds like it might be what you're after.
So, are you saying that you have Controller methods that don't select layouts - only render views? That's where I get a bit stumped - the way I have traditionally thought about Controllers is that their job was to select a Layout to populate and render. I had never considered that a Controller method could simply populate a view (without also defining a Layout).
I believe this would have to happen outside of a config layer. The reasoning being, it is the job of the Controller to define the Layout for the response. As such, the requirements of the secondary content cannot be known until after the primary Controller has executed.
I think the population of the secondary content would have to happen after the primary Controller has finished; perhaps as part of the top-level routing?
So confusing :)
Ben, I think that you should abandon MVC, it is a fad concept that will die out as you have to twist and contort just to do what you want to do.
That is just my opinion. :-)
I have this kind of thing going on in one of my sites. The layout event-handler has its own controller notification, which populates the sidebar. Mach-II has the built-in ability to specify caching of content, which simplifies this. Example:
<event-handler event="home" access="public">
<notify listener="adminListener" method="getNews" resultArg="results"/>
<view-page name="home" contentArg="content"/>
where the caching is defined in the configuration:
<property name="Caching" type="MachII.caching.CachingProperty">
<parameter name="defaultCacheName" value="default"/>
<key name="type" value="MachII.caching.strategies.TimeSpanCache"/>
<key name="scope" value="application"/>
<key name="timespan" value="0,1,0,0"/>
<key name="cleanupIntervalInMinutes" value="3"/>
The subroutine in the "home" event-handler wraps the rendered page content in the layout html.
Shouldn't the controller handle getting the data from the request for both the primary and secondary areas before passing it to your view? Hence using two models. Looking around i have not seen a way around it other then big capitals saying a view SHOULD NEVER directly call a controller.
In cfWheels, I'd use a filter() in the controller's init() function, which gets the data required for the sub sections. i.e getSidebar()
Then use includePartial() to include the sidebar; the joy of this is this can be cached seperately to the main content in cfwheels :)
@Ben, in ModelGlue, the config layer is absolutely part of the Controller. It defines how requests are handled and manages the routing.
No doubt, I have gotten pretty far without it in my career. But, I am definitely feeling the pain of not having better separation of concerns. So, I think it's worth the exploration.
I'm not too familiar with Mach-II; are you saying that the Layout has its own set of controller functionalities?
I agree that the Controller would have to get both chunks of data before actually rendering the view. So the question becomes, does that happen in One controller? Or in Two controllers?
Of course, in my world, so far, the Controller is still CFM pages with CFSwitch / CFInclude statements; so, the concept of one vs. two is really a bit more fuzzy than if they were ColdFusion components (CFC).
But, are you saying that each controller has to make an explicit call to getSidebar()? Or that factored out somewhere across controllers?
Ah, gotcha - I see what you're saying. I tend to think of my routing and my Controllers as one functional layer; I think we are on the same page then.
Yes, but you have the option of doing it once in the master controller.cfc (which all other controllers extend) and using super.init() OR, using it in each controller's own init(). With filters you can specific 'except' or 'only' if you only want to specify a subset of views.
This "render" function only calls a controller method and renders the returned result. So no, it doesn't always entirely render the layout, but sometimes only a small part of it (for example, a dynamic list).
I don't really see a problem with that, and I can't see a more better solution, but I'd be pleased to be proven wrong :)
Using ColdBox, you can have the Controller render a view or you can have a view call the Controller.
Actually, you have two concepts in presentation: Views and Viewlets. A View is a content container and a Viewlet is reusable bits of content.
So your controller function contains event.setView('path/to/view'), which renders view.cfm. In view.cfm, you can have content and/or you can render viewlets.
You do this is two ways: you either run a controller method that renders the viewlet or you render the viewlet that call the controller method.
I prefer to render a viewlet and let it call the controller method. This way, the viewlet know how to get the data it needs to render itself. You can do the same thing in the controller method, call the data, then render the viewlet, but I'd prefer the other process. It seems cleaner.
So view.cfm would contain:
#renderView( view: "path/to/viewlet" )#
and viewlet.cfm would start:
<cfset runEvent("module:handler.method") />
This makes for very lean controller methods and presentation code.
Ben, I'm not sure that I totally understand your question, but generally, if the layout needs to use content that is common to all views and is not supplied by any particular event-handler, then I'd have the layout event notify the listener to retrieve the content, caching it as necessary.
I've been using Mach-II for years, I can't imagine building an application of any complexity without it.
I would normally use a helper class/function shared across controllers if i was using multiple controllers or a this->loadsecondmodelcall method in a singular controller then return the data as a named key array to the view ['primary_data']['secondary_data'].
I guess my world/take is different as i use CodeIgniter/PHP, sorry.
> <cfset runEvent("module:handler.method") />
I think this is the thing that Ben is wanting not to do. This works great, but if you want to keep your views agnostic, it doesn't work so well. ColdBox introduces a fair amount of coupling between Model, View and Controller - it works nicely, but the coupling is there all the same.
Adrian Moreno seems to have the same method as me.
In FW/1, I would create a separate view for the twitter widget that can then be included in the primary view. Then in your controller, call a service method that loads all of the data necessary for the widget. It helps to separate your view and logic necessary for the widget.
@Dominic So you want multiple controller methods that can get different data to render the same view? That should be done in the Service Layer. You shouldn't need multiple controller methods unless there's something else going on. Even with that, you can override Controller methods using Routing and Modules. You can even use Modules to override Views and Viewlets.
So is it coupled? Sure. But it's still reusable.
Ok cool, thanks for the clarification!
I don't see a problem with it either - I had just never really thought about a Controller not choosing a Layout. I'm fairly new to MVC, so my understanding of all this stuff is probably short-sighted on a number of fronts.
In the post, I was saying that I have solved this problem before, pre-MVC, by simply centralizing various "Includes" in my procedural code. It sounds like this is basically the same thing. So, that makes me feel more comfortable.
I think this sounds similar to what I used to do / have been doing with my CFSwitch / CFInclude style of rendering for the last few years. Basically, my layout would have property like:
... then, in my Layout, I would check to see if this property was set; if so, I would CFInclude the given template (which would gather its own data and render its own viewlet). Or, if not set, I may leave it blank, or render some default content.
I'll have to look at it more - it sounds like I don't understand it enough to ask the right questions.
Good sir, no apologies please :) I'm super thankful for your feedback. And I think philosophically, ColdFusion and PHP are solving all the same problems. I think I understand what you're doing and I think it makes sense to me.
Yeah, exactly - I think this is what I do now, only with CFInclude rather than runEvent().
I agree with the what you're saying in how the Views get included; that I can wrap my head around. The big question mark for me is, Who loads the data for the Twitter view? Does each event-based controller have to load it explicitly? Or, can I have a non-event-based controller load it in a centralized fashion (perhaps after the event-based controller has routed the event and loaded the primary content).
Your service method would load the data for the twitter view. Your primary view controller would invoke a call to your service method to include it in your view. FW/1 also has a before method for controllers that can be useful in these scenarios if your twitter view is going to be included on every view within a particular section.
@Adrian, I'm not saying it's bad, just coupled. A decoupled approach *is* doable though. Model Glue (which I believe is based on Mach II), achieves it very well.
Ben, this is an excellent question you bring up and something I've also questioned myself.
Unfortunately I dont have time to answer this in detail now ...but I cover this tonight on the cfmeetup.
I actually use this concept on my website for my tweet stream...which is cached as well. I guess I'll have to open my personal site code for ridicule :-)
There is nothing wrong with views knowing about the model. The model should not know about views, or controllers, or users, but views are ok to know about the model.
Also, my understanding is that a controller is there to do job associated with a request, that is anything that is not specific to that request does not need to go into the controller.
For this specific problem, I would simply bypass the controller and call the twitter service from the view. Yes, just like that, simple and efficient.
In this scenario we're in the controller for the site (index.cfm), on the "checkout" page.
As you might imaging, we need a shipping address, billing address, and other checkout information to display. We want to reuse the shipping/billing address logic and form because they are the same.
We render our first view (billing form) and save it into a variable via a simple reusable includeIT function. (http://screencast.com/t/KfDAcMBGM4)
We then render our second view (shipping form) and save it into another variable.
Then we render our third view and save it into another variable. This third view contains information displayed on the page, and also outputs our first 2 views where we want them displayed on the page inside of it.
The mainTemplate is setup to always output the "request.theContent" variable. Basically:
This means that on a site wide scale you can render as many views as you want and include them in each other, but it's all setup and rendered in the main site controller for organization and ease of use.
This is basically what most of the frameworks out there do, but without the overkill of needing an entire framework.
@Kirill, wouldn't that make it mvcvm? model-control-view-model. I think the point of the mvc is to seperate concerns so the view never has to know about the model and only interacts with the controller.
That is a possibility, but having a controller function perform two different operations might not be the best approach for every situation.
I know with ColdSpring you can expose part of your services layer as a web service and you could talk to that via an ajax call from your JavasScript.
A Before/After scenario sounds like it would be good for this kind of stuff. When I first started to think about this stuff, I was wear about including calls for secondary content in every event action; but, I wouldn't be too concerned with limiting it to once per Controller.
But, that is a whole other topic that I am still relatively new to. So many question marks, so few answers in my head :D
I'm looking forward to it - I already have it the calendar!
I found this line fascinating:
... "Also, my understanding is that a controller is there to do job associated with a request, that is anything that is not specific to that request does not need to go into the controller."
I have to let that marinate for a bit. Definitely something possibly profound in that thought.
What you have here lines up pretty well with the mental model I currently have. That said, all of the cart-rendering stuff is very relevant to the "checkout" process. But, what if you had something like "Recently Viewed Items" that shows up in the site template no matter what page you were on - where would you pull that data? From within the mainTemplate.cfm?
The actual back-end data logic to get or prep the data I do in my model (cfcs). Those always return objects, or arrays of objects .
(always being like 95% of the time)
All of the actual calls to get this data or save new data come from my "controller".
Which for me is my index.cfm.
Every page on the site goes through this index.cfm controller. The index.cfm file calls all of the data up from the model and all of the view pages to display said data.
If there was something that always exists on every page I have outside of my case statement figuring out what page I'm on.
Sometimes there are pages that I don't want to get this information, (say ajax calls). So I do common naming patterns on URLs, or whatever other logic you need.
By doing this, you can literally open up the index.cfm file for a project, and get a solid high level view of what everything is your project is doing. It also gives you an ease of use to reuse variables modules of your code.
The simplest way to do this is to have the controller request the data for each content area from the service layer (using 2 method calls) and then pass it on to the view in a struct. If the data for the secondary content area is to be cached, then that can be handled by the service.
If you need a renderer in the mix, then the controller could pass the data to the renderer cfc after the service call, which would pass the rendered content back to the controller, and then the controller would pass the rendered content plus the data in a struct off to the view.
I don't concern myself with whether using a renderer violates MVC principles. If somebody would argue with me that it does, then my reply is simply that a renderer IS part of the view.
There's no need of another controller. What you need is something like Zend_View_Helper design pattern. See
This pattern provides the way to present content dinamically and can be put everywhere in the view.
It's like a shortcut from the model to the view without calling controller methods
The book you mentioned last week says we use different programming paradigms to prevent code duplication.
From that standpoint having the same code in two (or more) different controllers would not be optimal.
just my 2 cents ...
By the way, I've been using MVC in CF for many years as part and parcel of the Fusebox, ModelGlue and FW/1 frameworks. I've tried some of the more complex variations to architecture that folks have recommended over time, but when I have to come back to the code months or years later, I get _really_ irritated having to follow the "wiring" back to the code I actually need to edit.
So to me, in the case you've presented here, simplicity trumps being clever, which means in every controller method (reflecting a unique url/page request) where the [cached] secondary content is needed, I'll repeat the service call requesting it. That gives me maximum flexibility, so if the client comes along and asks "Can you please remove the secondary content on this specific page and replace it with XYZ", it's painlessly simple to do. It also makes it completely transparent to me when I go back in to change it months later. I don't have to recall the exotic architecture I used to get that secondary content to appear - it's right in front of my nose in the controller method for the page.
To me, FW/1 is ideal for learning MVC because it helps you to implement a simple, clean architecture.
@Kirill is right. The view is supposed to know about the model. That's how MVC works. (More precisely, it knows the model's interface -- and this may be where you got confused -- but not its implementation.)
When it comes to aggregation, the aggregate list of tweets is a model. It may have references to individual tweets (which are each instances of a Tweet model). Similarly, there would be a view that outputs a list of tweets and has a reference to the aggregate model. It may also delegate the printing of each tweet to instances of a tweet view. The tweet views in that case would each get a references its corresponding tweet model.
In practice, a simple data structure such as an array of structs will probably suffice for your model. The view would be responsible for both looping over the array and printing the appropriate HTML for each tweet.
Another thing you may be missing is there are other components in the system that aren't part of MVC. For example, there should be a component that talks to the API and gets the tweets (building and returning an instance of the model). The controller talks to that API component. There may also be filters and caching mechanisms between the API component and the controller.
Hope this helps.
If there are ansynchronous part to show the best way to implement is use a View_Helper
It bypass controller and get data from the model to display it.
yes it's true. I've the same opinion, but there are some cases (like this one) where this pattern helps more than the MVC.
It's wrong use it always in programming, but have few view_helpers helps a lot to me.
Have you looked at Marionette? Here is an article that I think covers your question - http://lostechies.com/derickbailey/2011/12/12/composite-js-apps-regions-and-region-managers/.
Coming in a bit late here, but it's probably still worth pointing out that MVC as originally conceived actually proposed a hierarchy of controllers, related to a parallel hierarchy of views. In a multi-window desktop app you'd have a view hierarchy that looks something like control->pane->window->desktop, and a controller hierarchy that exactly parallels that. Similar kind of thing for an AJAX app. A web page with independent content areas is not conceptually all that different, even without AJAX. Sometimes the request-response nature of the web is seen as somehow fundamentally different, but the fact is that even the most multitasking asynchronous desktop GUI boils down to a single master event loop somewhere in the guts of the OS, and that basically corresponds to a front controller in web-speak.
So, no need to limit yourself to "the" controller. IMHO, view helpers are basically controllers with the typical controller decision-making behaviour reduced down to just a single case. If that works for you, fine, but for me it's conceptually cleaner to just call them controllers and plug them into the controller tree.
I would like to add a +1 for Mach-II handling this very elegantly. In a Mach-II app, your composite views are generally built by a piece of xml configuration called a "subroutine" which tells the system which files to render into your main layout.
For example, a subroutine called "renderLayout" may pull in your header.cfm file, pull in your footer.cfm, make any necessary controller calls to grab the data for your dynamic sidebar (socialController.getTweets), and then render your sidebar.cfm file using the results from your getTweets call and any other data you needed to grab.
As far as caching, Mach-II has built in caching capabilities which work simply by wrapping a cache tag around the parts of your subroutine which you want to have cached and specifying various attributes which tell Mach-II how long you want to cache the response for, etc.
Btw, I'm a big fan of InvisionApp and this blog Ben, thanks for all that you do!
That makes a lot of sense; and, in many ways is how I have been doing things with pure CFM files (ie. action/query + view files). As I'm trying to move into a more OO-style approach to organization, perhaps I'm just over-thinking things.
I think you bring up a good point re: caching in the Service. I guess there are two things that you can cache:
* Cache data.
* Cache view-rendering.
Caching the data make sense in the Service (or at least in some sort of cache-decorator or something).
Also, I'm with you on "simplicity." I don't want to over-think / over-engineer this stuff (too much). I mean clearly, I'm trying to learn, which means that I almost sort of want to err on the side of unnecessary decoupling. But, for the most part, I want to keep things clean and simple.
Thanks, I'll take a look.
I think I agree with you that the View can know about the Model; I think what I was saying (or meaning) was that the View shouldn't be able to retrieve the model. So, for example, if the View had code like this:
... that would be fine, since it knows about the Model's interface, but is only using the model that has been made available by the Controller.
What I think would be strange(r) is if the View had code like this:
... In this case, the Tweets View has to know where the tweets are being stored and how to load them. It seem that in that case, the View is overreaching its understanding of the system at large. The Controller should be loading that data and passing it to the view.
So, I think we agree - the View can / should know about the Model; but, I'm saying the View shouldn't know how to "load" the Model.
I'll take a look, thanks!
That makes sense, philosophically. And, I think that's what I've been doing, historically, with my controllers with various includes and property checks. I just need to update / formalize my understanding of how this all fits together in a more component-oriented architecture.
Thanks my man, I really appreciate that! We have some awesome stuff coming down the pipe in InVision!