Alternative view/layout options
Disclaimer - NO API breaks included!
While someone (I look to you, Jim) can have a shock looking on the source code of the Core\View proposed by this pull-request, as in being completely rewritten, the API compatibility to actual Core\View is complete preserved. To be read:
This pull request does NOT introduce API breaks and the existent code will continue to work with NO changes.
Rationale
Everyone love the Views rendering triplets. Me too. You know, that < template header >< view >< template footer >, but when you need a more complex structure, containing blocks, diverse partials and fragments, that all by-hand rendering can become complicated, even with heavy use of recent introduced View::fetch().
And that complicated thing should be repeated every Method, from every Controller from your Frontend or Backend.
Sure, one can ask: but, while having at our disposition two stacked Middleware, on Routing and Controllers, why we can't compose our Views and instruct the Middleware to do the final job?
Response: yes, we can do that in some measure, but almost View rendering methods (excluding fetch) have an immediate effect, being static functions, arriving to the need of building and passing over complex data arrays, or doing another Code-Fu...
I'm of opinion that offering to end-user the ability to manipulate Views using a High Level API could simplify heavily the work with complex Page structures, like my own experience demonstrated me.
This is the reason of this pull-request and the proposed Core\View, to offer a fluent and powerful API to manipulate the Views and their data, with a strong Laravel-esque taste.
Basic Commands
While the actual Core\View methods are static and they should called independently, the new API works with View instances, then we should build them. We have two methods at disposition, for standard Views and Templates one. An combined usage example is presented bellow:
View::make('Welcome/SubPage')
->shares('title', $title)
->with($data)
->render();
// OR
$page = View::make('Welcome/SubPage') ->with($data);
View::makeTemplate('default')
->shares('title', $title)
->withContent($page)
->display();
Probably you'll notice that we passed the first View instance ($page) as is to second one. That will make it to be automatically fetched to $content variable, on final rendering. Also, you will notice that the View Methods can be chained.
The data can be passed to a View instance in diverse ways. The following commands are equvalent:
$page = View::make('Welcome/SubPage');
$page->with('shinyInfo', $info);
$page->withShinyInfo($info);
$page->shinyInfo = $info;
$page['shinyInfo'] = $info;
To note the variable name transformation by dynamic withX methods.
Also, the View instances can be nested. The following command are equivalent:
// Add a View instance to a View's data
$view = View::make('foo')->nest('footer', 'Partials/Footer');
// Equivalent functionality using the "with" method
$view = View::make('foo')->with('footer', View::make('Partials/Footer'));
To note that nesting assume that the nested View instance is a Standard View, not a Template one.
There is also a new shares() method, similar with actual share() but working for instances.
As rendering commands, we have:
fetch() : will render the View and return the output
render() : will render and output the View and display the output
display() : same as display() but will send also the Headers.
Simple Usage
NOTE: the Triplet Style Rendering works as always, being full supported.
Then you do not need to change your already written code. The following example is from an untouched Welcome's Controller method:
View::renderTemplate('header', $data);
View::render('Welcome/Welcome', $data);
View::renderTemplate('footer', $data);
This pull request introduce also a simple method of auto-rendering, which enter automatically in action when you return from a Controller Method execution a View instance.
Then is enough for an usual template-based rendering to have a command like:
return View::make('Welcome/SubPage', $data)->shares('title', $title);
Practically, that rendering style will execute also in Controller's after() the command:
View::makeTemplate($this->layout, array(), $this->template)
->withContent($result)
->display();
Every Controller have now the ability to specify its Template and Layout, as following:
class Welcome extends Controller
{
protected $template = 'Admin';
protected $layout = 'custom';
/**
* Call the parent construct
*/
public function __construct()
{
parent::__construct();
$this->language->load('Welcome');
}
...
}
WHERE the 'default' Layout is a simple composition of your header/footer files. For details, see:
app/Templates/Default/default.php
That file contains only:
require dirname(__FILE__) .'/header.php';
echo $content;
require dirname(__FILE__) .'/footer.php';
Then, any changes in your header and footer files will be propagated.
Advanced Usage
Rendering with partials (i.e. header/footer) from the standard Views location
If you need to work with partials, for example blocks, header and footer files located in app/Views directory, it is very simple to do that. You have just to compose your views as following:
The following examples use a very simple Template Layout file, called:
app/Templates/Default/custom.php
which contains:
echo $header;
echo $content;
echo $footer;
Rendering with a complete custom Template living on Views folder
return View::makeTemplate('custom')
->shares('title', $title)
->with('header', View::make('Partials/Header'))
->with('content', View::make('Page/Index', $data))
->with('footer', View::make('Partials/Footer'));
OR
return View::makeTemplate('custom')
->shares('title', $title)
->withHeader(View::make('Partials/Header'))
->withContent(View::make('Page/Index', $data))
->withFooter(View::make('Partials/Footer'));
OR
return View::makeTemplate('custom')
->shares('title', $title)
->nest('header', 'Partials/Header')
->nest('content', 'Page/Index', $data)
->nest('footer', 'Partials/Footer');
OR
return View::makeTemplate('custom')
->shares('title', $title)
->with('content', View::make('Partials/Layout')
->nest('content', 'Page/Index', $data));
Rendering in the Triplet Style, but using the new API
return View::makeTemplate('custom')
->shares('title', $title)
->with('header', View::makeTemplate('header'))
->with('content', View::make('Page/Index', $data))
->with('footer', View::makeTemplate('footer'))
Rendering in the Triplet Style, but using some Views as blocks
// Views instances automatically rendered into base View rendering.
$data['latestNewsBlock'] = View::make('Articles/LatestNewsBlock')->withNews($latestNews);
$data['topVisitedBlock'] = View::make('Articles/TopVisitedBlock')->withNews($topNews);
View::share('title', $title);
View::renderTemplate('header');
View::render('Welcome/Welcome', $data); // <-- there the View instances from $data will be fetched automatically
View::renderTemplate('footer');
FAQ
How is possible to have 'View::render($path, $data, $module)' while '$view->render()' accept NO parameters?
The new Core\View speculate its Design to create a Facade which literally transform your (classic) static calls into New Style commands. The process is transparent and well optimized, but also with the result that (compat) View::render($path, $data, $module) and (native) $view->render() behave different.
What are the real commands executed when I do 'View::render($path, $data, $module)' ?
$view = View::make($path, $data, $module);
Response::sendHeaders();
$view->render();
How is possible to execute 'View::addHeader($header)' while there is no such method, statically or not?
The Core\View Facade transparently execute the 'right' command:
Response::adHeader($header);
Why you moved the Header associated methods from Core\View to Helpers\Response?
In my opinion, a View is just an abstraction of the Page Content, NOT being even complete without additional Template header and footer, or layout, which decides the overall Theme. Why it should send HTTP Headers? That's the job of a Response.
In other hand, the Headers support into Helpers\Response exists since its creation, being a Class imported from NovaIgniter (the 3.0-branch), together with the Routing.
Then, I just preserved the existing API while calling the 'right' commands, in my opinion, from the (HTTP) Response.
How is possible to execute 'echo $view' and render it, where $view is a View instance?
If you do as following you obtain an rendering output:
$view = View::make($path, $data, $module);
echo $view;
That's a expected behavior, a Core\View instance having the ability to automatically execute a fetch while casting it as string.