Well I’d like to (try to, but it wont work) keep this post short, so without offering full documentation to my whole view layer, here’s what I’d do to get the above working.
My current code is perhaps overkill in most cases. I built it to offer maximum reusability and minimal client code at the expense of a large application codebase.
Now this could work without my template engine entirely and work with php as a template engine, the reason I use my template engine is not for this, really but more for the pagination type example below.
//Common user-related display logic, probably already defined somewhere.
abstract class UserViewModel extends ViewModel {
public function getUser() {
return $this->model->user->fromSession();
}
public function isReturningUser() {
//Here, as previous example I could change it to $this->model->user->isLoggedIn() && $this->model->user->isVerified();
return $this->model->user->isLoggedIn();
}
}
class WelcomeMessageViewModel extends UserViewModel {
public function getTemplate(TemplateLoader $templateLoader) {
return $templateLoader->load('welcomemessage.tpl');
}
}
welcomemessage.tpl
<!-- visible when isReturningUser is true -->
<section:isReturningUser visible="true">
Welcome back {model property="getUser()::name"}
</section:isReturningUser>
<!-- visible when isReturningUser is false -->
<section:isReturningUser visible="false">
Welcome, Guest, Please login or register.
</section:isReturningUser>
And that would be it. Of course I have a top level “View” layer (defined by the framework, rather than each time…) which automatically maps the ViewModel to the template… the template can be reused with a different viewmodel (substituting the logic) or the viewmodel can use a different template (substituting the html).
It’s a one way data flow, the template pulls data from the viewmodel and the viewmodel pulls data from the domain model creating a very strict separation of concerns. I wont go into pagination in detail but this would be everything i’d need to do to implement it in my system as is:
class UserListViewModel extends ViewModel Implements PagedDataViewModel {
//random variables settable by controller
public $searchCriteria = 'Tom';
public $page;
public function getTemplate(TemplateLoader $templateLoader) {
return $templateLoader->load('userlist.tpl');
}
public function find($limit, $offset) {
//Ask domain model for related records
return $this->model->user->find(array('name' => $this->searchCriteria), $this->limit, $this->offset);
}
public function getCurrentPage() {
//Page currently being viewed
return $this->page;
}
public function getRecordsPerPage() {
//I want to show 10 records per page...
return 10;
}
public function getTotalResults() {
return $this->model->user->countFind(array('name' => $this->searchCriteria));
}
}
userlist.tpl
<!-- The vars are optionally specified, the view defines some defaults -->
<section:pagelist numvar="num" classvar="class" classcurrent="currentpage" classpage="page" >
<a href="userlist/{num}" class="{class}">{num}</a>
</section:pagelist>
<!-- the search results, the name of these sections can be speficied by optional functions in the viewmodel -->
<section:result into="item">
<div>
<p>{item property="firstname"}</p>
<p>{item property="lastname"}</p>
</div>
</section:result>
In short the binding is done by the specified view.Here’s a simplified version of how that works (using hardcoded everything for the sake of simplicity), this is just part of the framework and not defined per project or each time pagination is used.
class PagedDataView extends View {
public $sectionName = 'result';
public $pageSection = 'pagelist';
public $currentClass = 'current';
public function output() {
$perPage = $this->viewModel->getRecordsPerPage();
$pageNo = $this->viewModel->getCurrentPage();
if (empty($pageNo) || !is_numeric($pageNo) || $pageNo < 1) $pageNo = 1;
$result = $this->viewModel->find($perPage, ($pageNo-1)*$perPage);
$totalRecords = $this->viewModel->getTotalResults();
$totalPages = ceil($totalRecords/$perPage);
for ($i = 1; $i <= $totalPages; $i++) $this->template->appendSection($this->pageSection, array('num' => $i, 'class' => $i == $pageNo ? $this->currentClass : ''));
$this->template->addHook($this->hook->objectSet($this->sectionName, $result));
return parent::output();
}
}
sorry, as expected this post got long
Now you’re going to ask about template hooks aren’t you?.. in brief they just match tag sections in the template and enable control of the contents.
Feel free to PM me, but this is way beyond the scope of the initial discussion.