Page 1 of 2
MVC, HTML, and JSON - what goes where?
Posted: Tue May 15, 2007 12:04 pm
by inghamn
So we've been rockin' and rollin' with an MVC design for our content manager. Now, my usability person wants a few drop downs to be repopulated on the fly. Hey, no problem - I've done quite a few bad AJAX implementations. Now to do a good one.
In this case, we want to start with a small list of users in a <select> and repopulate it with the full list if the user desires. I've even got a users controller that current displays the list of users in HTML, using a template for the site and all that.
My first stab at this involves passing format=JSON in the URL. And doing a REST style service. Where I start getting concerned is that, as time goes on, and we want to support JSON, XML-SOAP, XML-RPC, etc, that's a whole lot of stuff going on inside switch{} statements. Is there a better way to organize all this? Having 90% of the code inside of a switch make me feel like I should split it out into seperate controllers.
Here's my first attempt at adding JSON to the controller.
Code: Select all
$userList = new UserList();
$userList->find();
$format = isset($_GET['format']) ? $_GET['format'] : '';
switch ($format)
{
case 'json':
$template = new Template('JSON');
$template->blocks[] = new Block('users/userListJSON.inc',array('userList'=>$userList));
break;
default:
$template = new Template();
$template->blocks[] = new Block('users/userList.inc',array('userList'=>$userList));
}
$template->render();
Posted: Tue May 15, 2007 2:40 pm
by inghamn
I guess it boils down to two options:
1. Front Controller checks format and loads different controllers for different output formats.
or
2. Every controller checks format and loads appropriate views.
Are there other options out there?
What solution makes the most sense?
Posted: Tue May 15, 2007 3:03 pm
by Christopher
I think I would make JSON, XML-SOAP, XML-RPC calls each be a method in the related Action Controller. I am assuming that they all share one or more common models, so they would be different Views run by the same Controller.
Posted: Tue May 15, 2007 3:24 pm
by inghamn
Fair enough. That's pretty much the route I'm taking right now. The code I posted is essentially my Users Action Controller
I'm just about to launch into adding RSS support across the content manager. I'm looking down that path and seeing myself typing the same stupid format code into all my controllers and was hoping for a better way.
Kinda thinking out load here...but...what if I put View stuff into a directory structure that included the format? ie..
/blocks
/html
/json
/rss
/xml
Then, the controllers don't need to parse format, they just pass it onto the Template, and let the Template load the appropriate files?
In this case Template is serving as a Composite View. Does it make more sense to put HTML, JSON, RSS, etc logic into the View? Or should that logic really stay in the controller?
Posted: Tue May 15, 2007 5:43 pm
by Christopher
inghamn wrote:Fair enough. That's pretty much the route I'm taking right now. The code I posted is essentially my Users Action Controller
I was thinking that the Front Controller would select the output type:
Code: Select all
class MyController {
function __construct() {
$this->userList = new UserList();
$this->userList->find();
}
function jsonAction () {
$template = new Template('JSON');
$template->blocks[] = new Block('users/userListJSON.inc',array('userList'=>$this->userList));
$template->render();
}
function defaultAction() {
$template = new Template();
$template->blocks[] = new Block('users/userList.inc',array('userList'=>$this->userList));
$template->render();
}
}
Obviously you want to move the duplication in the Actions into the View.
inghamn wrote:I'm just about to launch into adding RSS support across the content manager. I'm looking down that path and seeing myself typing the same stupid format code into all my controllers and was hoping for a better way.
Kinda thinking out load here...but...what if I put View stuff into a directory structure that included the format? ie..
/blocks
/html
/json
/rss
/xml
Then, the controllers don't need to parse format, they just pass it onto the Template, and let the Template load the appropriate files?
In this case Template is serving as a Composite View. Does it make more sense to put HTML, JSON, RSS, etc logic into the View? Or should that logic really stay in the controller?
This is a really good question and there seem to be many answers. I think go with the file structure that seems to make sense to you -- it will probably evolve as you get into it anyway. I do think Composite View makes sense where the View just picks the right template, if you can get away with that.
Thanks
Posted: Wed May 16, 2007 7:14 am
by inghamn
Thanks, arborint. That all makes sense.
I'm launching into building RSS support across the whole application today. I can let you know how it goes. Even, though I'm not using the Zend Framework for MVC, if folks are interested, I can post code for the solution I end up with.
Posted: Wed May 16, 2007 7:59 am
by kyberfabrikken
FWIW, I usually implement handlers for supporting fileformats, such as RSS and JSON, as separate methods on the same controller, which would handle the normal (text/html) type request.
Instead of requesting the content-format as a query string parameter, you might want to use content-negotiation, which is the way, HTTP was intended to do this. Basically, your ajax-request should send an 'Accept' header, with the content-type that it expects, and in your controller you can then detect this, and dispatch to the appropriate handler accordingly.
Posted: Wed May 16, 2007 10:39 am
by inghamn
Instead of requesting the content-format as a query string parameter, you might want to use content-negotiation, which is the way, HTTP was intended to do this. Basically, your ajax-request should send an 'Accept' header, with the content-type that it expects, and in your controller you can then detect this, and dispatch to the appropriate handler accordingly.
For JSON, in the case of AJAX, I think I could work that out. In the case of RSS, I'm not sure how I'd implement it. People "subscribe" to RSS, essentially, by bookmarking a URL. Woulnd't I have to come up with some URL that told the person's browser to add RSS to their 'Accept' header?
Having the output format as part of the URL makes it easy for users to get the page in different ways.
http://mysite/news
http://mysite/news?format=RSS
http://mysite/news?format=XML
or in the case of events...
http://mysite/calendar
http://mysite/calendar?format=XML
http://mysite/calendar?format=ICAL
Once I start down this road for output formats, JSON becomes just another output format.
Posted: Wed May 16, 2007 11:30 am
by kyberfabrikken
inghamn wrote:In the case of RSS, I'm not sure how I'd implement it. People "subscribe" to RSS, essentially, by bookmarking a URL. Woulnd't I have to come up with some URL that told the person's browser to add RSS to their 'Accept' header?
Yes, content-negotiation isn't used a whole lot. Personally, I'm not entirely sure, what I think about the feature. I mostly mentioned it, in case you hadn't thought about that option. Coming to think about it, an RSS feed should probably have its own URL, and not just be identified by a different Accept header.
Posted: Wed May 16, 2007 1:11 pm
by Kieran Huggins
I feel like the site views should be completely separated for each output type, perhaps maintaining a separate directory of templates for each one?
Posted: Wed May 16, 2007 1:18 pm
by Christopher
Kieran Huggins wrote:I feel like the site views should be completely separated for each output type, perhaps maintaining a separate directory of templates for each one?
I think it really depends on the implementation. In many cases the same Model and View can generate the separate output just using different templates. Obviously that does not work with all types. In this way I think the argument for a render tree that can be built of templates sub-Views starts to make sense. Then you can use templates for the easy stuff and sub-Views for more complex feeds.
Posted: Wed May 16, 2007 3:45 pm
by inghamn
Well, I've implemented a composite view system. Still not using the Zend Framework, though - it's routing system makes my head hurt :cry:
What I realized with the format issue (at least with my stuff) is that the HTML format was the only format that could have multple possible templates being asked for. In a controller, you might ask for the two-column, html template or the three-column, html template. But all the other formats would really only have one template. JSON, iCAL, RSS - there really isn't alot of different layouts you would want to use for those.
Cacth me, if I'm missing anything there.
Although the View code in my project supports passing Model data to the Views (templates, and blocks) I've left that code out in these examples. Trying to keep it clear on the formatting problem.
Code: Select all
abstract class View
{
abstract public function render();
}
class Template extends View
{
private $filename;
public $outputFormat = 'html';
private $blocks = array();
public function __construct($filename="default",$outputFormat='html')
{
$this->filename = $filename;
$this->outputFormat = $outputFormat;
}
# Template files must include a call to $this->includeBlocks(),
# when they're ready for the main content
public function render()
{
include APPLICATION_HOME."/templates/{$this->filename}.inc";
}
# Callback function for template files
private function includeBlocks()
{
foreach($this->blocks as $block) { $block->render($this->outputFormat); }
}
}
class Block extends View
{
private $filename;
public function __construct($filename)
{
$this->filename = $filename
}
public function render($outputFormat='html')
{
include APPLICATION_HOME."/blocks/$outputFormat/{$this->filename}";
}
}
After updating the Views to handle different outputs, I still wasn't able to comletely remove Format checking from inside a controller. (Front Controller or Action Controller or whatever controller routing you've got.)
I was able to boil it down to just an Either-OR check. Either there was a format requested, or we're using the default template, which would be defined in the controller. In the example below, I leave the default file and format defined in the Template class. So my controller code now looks like:
Code: Select all
$userList = new UserList();
$userList->find();
$template = isset($_GET['format']) ? new Template($_GET['format'],$_GET['format']) : new Template();
$template->blocks[] = new Block('users/userList.inc',array('userList'=>$userList));
$template->render();
This works so far. However, I'm concerned about the need to pass the $outputFormat as a parameter to the block->render(). Waiting until render time to throw an Exception if there's no matching file *might* cause problems later on.
Posted: Wed May 16, 2007 10:15 pm
by Christopher
First, I think it should be:
Code: Select all
abstract class Template
{
abstract public function render();
}
class View extends Template
{
You also might want to think of making a Template class that does templating, and a View class that contains support to composites Templates.
Posted: Thu May 17, 2007 5:17 am
by kyberfabrikken
arborint wrote:First, I think it should be:
I would prefer:
Code: Select all
interface View
{
public function render();
}
class Template implements View
{
...
}
A template is not the only technique, for rendering output (Depending of course on, what you put in the name 'Template').
Posted: Thu May 17, 2007 8:16 am
by inghamn
arborint wrote:
You also might want to think of making a Template class that does templating, and a View class that contains support to composites Templates.
I'm definitely keeping my options (and hopefully mind) open. I think we're all talking to the same idea. The only issue is semantics - which seems good.
In an attempt to be clear...in the work I've done thus far.
View = output base class
Template = Boilerplate content
Block = chunks of content composited inside the Template.
Sadly, if there's already been confusion on words I've chosen, then maybe it's time I renamed sfuff. 'Block' has always bothered me anyway, I just couldn't come up with a better word. 'Subview' maybe? Subtemplate? The mind boggles.
