Combining Page Elements from Different Applications
See Complex Web Pages with the Zend Framework?
In BW Rox we often have the problem that we want to reuse similar pieces of layout across different pages. More precisely, we have different problems:
- The content of every page wants to be embedded in a shared layout. Usually they all want the same layout, but some may want a different layout. Looking at the DOM tree, the shared part in this context is the root.
- A page may want to embed content from a different application. For instance, the startpage wants to use content from the messages and forums applications. And, the forum wants to show user avatars. Looking at the DOM tree, what we want to share here is specific branches and leaves of the DOM tree.
- Different layout elements want to share the same design. For instance, they would like to reuse the layout for tables or pagination links. The thing we want to reuse here is the root and structure of a specific DOM branch.
An important point is that, if views want to reuse things then by default they're going outside their own limited scope. We EITHER double the code (by actually copying the layout code used) OR we allow views to access something outside themselves (I write views since, even if it happens through the controller, it's still the case that the view is "aware" of it's outside world).[Fake51]
There are some possible solutions to this.
Two-Step Views
See Complex Views with the Zend Framework - Part 5: The Two-Step View Pattern.
This concept attempts to solve problem 1.
The application renders some content fragments, which are then given to the layout template. The layout has some slots for these content fragments.
This is how we used to do it with MyTB / Platform PT. Well, MyTB used a global registry to pass around the text fragments. And the classes are quite big. But basically, it's the same pattern.
pros
- quite good layout reuse
- lose coupling between layout and content.
cons
- communication with text fragments is not that powerful. We can't customize every part of the layout.
- changing the parameter slots can break stuff.
questions
- the fragments have to be passed around in some way, or the pieces have to be assembled.
- what is the role of the controllers in this game?
Parameterize views
(more or less an implementation of the two-step view, but with some tricks..)
A possible solution to problem 1 would be to have a view-generator pick up the view, check basic parameters and then generate a layout-frame based on that. This frame is then filled with content from the view.
This allows for generating a basic layout that views can specify should be used, as well as adding other layout frames later on, that apps can then specify. Changing a basic layout wouldn't effect other layouts and vice-versa, plus views would be agnostic about the overall framework: they don't call any methods or set any properties outside themselves, they just expose a minimal set of properties that the view-generator then picks up. The controller would be responsible for notifying the view-generator about which view to use. Something like:
// in class Controller: $view = new View; $view->generateContentForMessages(); $view_generator = new ViewGenerator; $view_generator->generateView($view); // in class View: $this->layout = "BasicRox"; $this->main_content = $content; return; // in class ViewGenerator: $layout = $view->layout; $this->loadLayout($layout); /* check through layout for content needed, grab this from the view */ /* output layout + content */
Inheritance / Polymorphism (inherit from PageWithRoxLayout)
See Polymorphic View classes for layout reuse - good or bad? on sitepoint.com
This concept attempts to solve problems 1. and 3.
The base class defines a generic layout, which is specialized in the subclasses.
This is the newer way we are going in BW-Rox, with the 'PageWithRoxLayout' etc.
pros
- Flexibility: The application can decide which of the layout-generating methods should be overriden or not. Can offer a lot more extension points than the slots of a layout template.
- Powerful customization: method overriding is a lot more powerful for customization than parameter slots. For instance, methods can be called more than once, and they can take arguments. And, overriding methods can call back on parent methods.
- Allows a tree of inheritance with more than one specialization step. For instance, ProfilePage extends MemberPage extends PageWithRoxLayout.
cons
- Inheritance in PHP is a quite static concept. For instance, what if we want to replace the base class for skinning?
- Base class is growing quite big, and the base class methods are tightly coupled with the child methods.
- That's exactly the points why people say composition is better than inheritance.
difficulties
- Who gives the necessary information to the base class? We don't want the base class having to grab that information from global scope!
Dynamic (simulated) Inheritance
This would be a variation of the above, taking away some of the unsexyness of static relations between classes.
Instead of having one page or layout element be the subclass of another, we can simulate a behaviour like this. We dynamically staple together a multi-layered sandwich of view components, where the "younger" layers override or replace functionality defined in the "older" layers. This functionality can be expressed in methods, but there could be other solutions. The rules for the overriding can be defined by the method names, or through a dynamic mapping at runtime ("the layer 'b' will override the 'footer()' method with 'b.bottom()', and the 'sidebarLeft()' method with 'b.navigationBar()'.")
pros:
- highly flexible approach
- fine-grained layout reuse
- very powerful for skinning: at any position in the sandwich, we can throw in one or more "skinning layers".
- has a taste of aspect-oriented programming.
cons:
- complicated to find out which implementation of a function is called in the end.
- changing an extension point can easily break stuff. And in this architecture, everything would be an extension point.
Composite View
See
- Complex Views with the Zend Framework - Part 3: Composite View Pattern
- Complex Views with the Zend Framework - Part 4: The View Factory
This concept attempts to solve problem 2.
The idea is to use pieces from somewhere else inside the current page. Simple idea.
The question is, where does the current page get these other layout elements from, and who is responsible for initializing the other layout elements.
Invoking External Controllers
That's the way it's done in MyTB. Create an instance of SearchmembersController, and let it print a search box.
This approach has three problems:
- The views have to know about external controller classes
- The controller has to retrieve necessary information from global scope
- The controller gets too many responsibilities. The original responsibility is deciding what to do with http requests - that's perfectly enough for one class.
Widgets
Instead, the View can create widgets. But, this does again have its problems.
- The views have to know about external widget classes
- The widget have to retrieve necessary information from global scope
Widget Factory
Instead of using constructors, we use a factory. The factory knows everything that is necessary to create any layout element, or it can delegate this task to application factories. The toplevel factory can be assembled in a toplevel controller, and then given to the view components.
The factory can be easily hidden away in an object, so it feels like just calling a layout-generating method.
This solves problem 2 and 3 through composition, while allowing for easier changes, lesser chance of breaking things by changing layout, keeps the MVC triads fairly closed in on themselves. The best solution, I would say. But please, do not use the word widget. It is horrible and "2.0". Go with helpers instead. [Fake51]
Cross-Application Model Access
Sometimes we don't want to reuse finished layout elements, but instead want to access parts of a model. For instance, if we want to display the latest forum posts on the startpage, but want to define the layout in the startpage's application, instead of the forum application. We only want the forum app to give us some forum data to play with.
Model-Aware Views
In the traditional setup, a page or layout element is given the data by a controller. It a strict understanding of this concept, a view element is not aware of the model itself. The problem with that: The controller has to know every piece of data the view will need. This reduces the possibilities of the view programmer.
In many places on the web we find people advocating the idea that the view should have full read access to the model, instead of depending on the controller.
You'll find that not only "many places on the web" but also in the general theory of MVC - reading through http://st-www.cs.uiuc.edu/users/smarch/st-docs/mvc.html (one of the early texts on the design) shows the same idea: a view should know about it's model. However, this does NOT mean that everything in the model should be available to the view: only what's needed. We could make a host of characteristics visible through a _get overload and at the same time make sure that redesign of a model doesn't completely break an app. [Fake51]
Model Locator: Get Data from other Applications
The application's own model can be given to the view by its respective controller - fine with that. However, what if the view component wants to use data from other apps? In this case, it would have to create a model out of global scope (class constructor) - bad bad bad.
Instead, we give the view component a factory to obtain whatever model it needs. The pattern is called a ServiceLocator? (hopefully lemon-head is using this term correctly here).
The model locator can be hidden away to make the view feel as-if dealing with a big mighty model.
questions:
- what exactly would the model locator return?
- How would the interface look like?
why not to use it:
- Fake51 thinks this is a bad idea: Views should not be allowed to access to models of other MVC triads: way too close coupling. It doesn't matter that it takes place through a factory, as long as you let a view that should be limited to a MVC triad access another MVC triad you'll end up with problems. [Fake51]
- response to below: while this is true, that doesn't mean that MVC triads should be allowed to touch each other. If you allow that, then stop using MVC: you have just voided the reasoning behind the design. What you COULD do is to extract common code/data and put it in external objects accessible to all MVC triads - then generate these objects through a factory. That way, no dependency between MVC triads and hence no problem of complete failure if just one app breaks down (because all other pages thought it was a great idea to take a date-time selector specified in that app). [Fake51]
why to use it:
- lemon-head says that nowadays we have to stop thinking of "MVC triads" as distinct and strictly independent worlds. Many pages in modern websites display a mix of content from everywhere, and sometimes we just want our template to loop through an array of something from somewhere.
