I trying to write my own MVC (Please don't suggest me any existing mvc I want to make my own) and now to render a page I do this:
Header:
<!DOCTYPE html>
<head>
<!-- Some contents -->
</head>
<body>
Footer:
</body>
</html>
Index function of a controller:
public function index(){
$this->view->render('header');
$this->view->render('index');
$this->view->render('footer');
}
Render function:
public function render($file){
require "views/$file.php";
}
Instead of doing that how I can do something like this?
Layout:
<!DOCTYPE html>
<head>
<!-- Some contents -->
</head>
<body>
<?= $page //$page will be replaced with index.php content ?>
</body>
</html>
Index function of a controller:
public function index(){
$this->view->render('layout', 'index');
}
You can have a render function like:
function render ($template, array $environment = array()) {
extract($environment);
ob_start();
include $template;
$contents = ob_get_clean();
return $content;
}
The extract() function will import all variables from the array into the current scope making them available to your template.
http://nl1.php.net/manual/en/function.extract.php
ob_start() will buffer all content from that point onwards.
ob_get_clean() will get the contents of the buffer end ends the buffering of output.
http://nl1.php.net/manual/en/function.ob-start.php
Related
I usually use Smarty template engine, so i separate database quesries and other logic from HTML template files, then assign received in PHP variable into Smarty via their function $smarty->assign('variableName', 'variableValue'); then display correct template file with HTML markup, and then i can use within that template my assigned variables.
But how correctly it will be done with .php file tempaltes, without Smarty?
For example, i use that construction:
_handlers/Handler_Show.php
$arData = $db->getAll('SELECT .....');
include_once '_template/home.php';
_template/home.php
<!DOCTYPE html>
<html>
<head>
....
</head>
<body>
...
<?php foreach($arData as $item) { ?>
<h2><?=$item['title']?></h2>
<?php } ?>
...
</body>
</html>
It's work. But i heard that it's not the best idea to do that.
So is this approach correct? Or maybe there's other way to organize it?
Give me advice, pelase.
Including templates in such a manner like in your example is not best idea because of template code is executed in the same namespace in which it is included. In your case template has access to database connection and other variables which should be separated from view.
In order to avoid this you can create class Template:
Template.php
<?php
class Template
{
private $tplPath;
private $tplData = array();
public function __construct($tplPath)
{
$this->tplPath = $tplPath;
}
public function __set($varName, $value)
{
$this->tplData[$varName] = $value;
}
public function render()
{
extract($this->tplData);
ob_start();
require($this->tplPath);
return ob_get_clean();
}
}
_handlers/Handler_Show.php
<?php
// some code, including Template class file, connecting to db etc..
$tpl = new Template('_template/home.php');
$tpl->arData = $db->getAll('SELECT .....');
echo $tpl->render();
_template/home.php
<?php
<!DOCTYPE html>
<html>
<head>
....
</head>
<body>
...
<?php foreach($arData as $item): ?>
<h2><?=$item['title']?></h2>
<?php endforeach; ?>
...
</body>
</html>
As of now template hasn't access to global namespace. Of course it is still possible to use global keyword,
or access template object private data (using $this variable), but this is much better solution than
including templates directly.
You can look at existing template system source code, for example plates.
So I have my views split up basically between three (3) files:
-- Header file
$this->load->view('templates/header', $data);
-- Main Body file
$this->load->view('login_view', $data);
-- Footer file
$this->load->view('templates/footer', $data);
Now I just recently started building, but I've noticed it's really annoying to retype the header and footer on every controller to tell it to load. Is there a way to automatically load the header and footer view on every request?
I found an article long time ago, but i can't seem to find it now, basically the author, (which i forgot) override the showing of output. this method of output will access your views regarding the given controller/method and will try to search in your views directory automatically.
Use at your own risk
Application/core/MY_COntroller.php
----------------------------------------------------------------------------
class MY_Controller Extends CI_Controller
{
protected $layout_view = 'layouts/application'; // default
protected $content_view =''; //data
protected $view_data = array(); //data to be passed
public function __construct()
{
parent::__construct();
}
public function _output($output)
{
if($this->content_view !== FALSE && empty($this->content_view)) $this->content_view = $this->router->class . '/' . $this->router->method;
$yield = file_exists(APPPATH . 'views/' . $this->content_view . EXT) ? $this->load->view($this->content_view, $this->view_data, TRUE) : FALSE ;
if($this->layout_view)
{
$html = $this->load->view($this->layout_view, array('yield' => $yield), TRUE);
echo $html;
}
}
}
Application/views/layouts/layout.php
----------------------------------------------------------------------------
<html>
<head>
<title>master layout</title>
</head>
<body>
<!-- this variable yeild is important-->
<div><?=$yield;?></div>
</body>
</html>
This is what i use to create my template. Basically you need a directory structure as follows.
+Views
|+layouts
||-layout.php
the layout.php will serve as your master template
How to use?
extend the controller
class User Extends MY_Controller
{
public function create_user()
{
//code here
}
public function delete_user()
{
//use a different master template
$this->layout_view = 'second_master_layout';
}
public function show_user()
{
//pass the data to the view page
$this->view_data['users'] = $users_from_db;
}
}
Just create directory in your views and name it with the controller name i.e user then inside it add a file you named your method i.e create_user
So now your Directory structure would be
+Views
| +layouts
| |-layout.php
| |-second_master_layout.php
| +user
| |-create_user.php
Just Edit the code to give you a dynamic header or footer
Here is the simple example which i always do with my CI project.
Pass the body part as a $main variable on controller's function
function test(){
$data['main']='pages/about_us'; // this is the view file which you want to load
$data['something']='some data';// your other data which you may need on view
$this->load->view('index',$data);
}
now on the view load the $main variable
<html lang="en">
<head>
</head>
<body>
<div id="container">
<?php $this->load->view('includes/header');?>
<div id="body">
<?$this->load->view($main);?>
</div>
<?php $this->load->view('includes/footer');?>
</div>
</body>
</html>
In this way you can always use index.php for your all the functions just value of $main will be different.
Happy codeing
Using MY_Controller:
class MY_Controller extends CI_Controller {
public $template_dir;
public $header;
public $footer;
public function __construct() {
parent::__construct();
$template_dir = 'templates'; // your template directory
$header = 'header';
$footer = 'footer';
$this->template_dir = $template_dir;
$this->header = $header;
$this->footer = $footer;
}
function load_views ($main, $data = [], $include_temps = true) {
if ($include_temps = true) {
$this->load->view('$this->template_dir.'/'.$this->header);
$this->load->view($main);
$this->load->view('$this->template_dir.'/'.$this->footer);
} else {
$this->load->view($main);
}
}
}
Then load it like: $this->load_views('login_view', $data);
You can do with library.
Create a new library file called template.php and write a function called load_template. In that function, use above code.
public function load_template($view_file_name,$data_array=array()) {
$ci = &get_instatnce();
$ci->load->view("header");
$ci->load->view($view_file_name,$data_array);
$ci->> load->view("footer");
}
You have to load this library in autoload file in config folder. so you don't want to load in all controller.
You can call
$this->template->load_template("index",$data_array);
If you want to pass date to view file, then you can send via $data_array
There is an article on this topic on ellislab forums. Please take a look. It may help you.
http://ellislab.com/forums/viewthread/86991/
Alternative way: Load your header and footer views inside the concern body view file. This way you can have batter control over files you want to include in case you have multiple headers and footer files for different purposes. Sample code shown below.
<html lang="en">
<head>
</head>
<body>
<div id="container">
<?php $this->load->view('header');?>
<div id="body">
body
</div>
<?php $this->load->view('footer');?>
</div>
</body>
</html>
I am trying to find the cleanest way to merge multiple html files into one html file. This way I can easily change parts of the html or show them only on certain pages. The file list is as followed:
page.tpl (header, footer, head info)
sidebar.tpl (contains sidebar and sidebar blocks)
nav.tpl(contains navigation links in nested UL)
The page.tpl file looks like this:
<!DOCTYPE html>
<html>
<head>
<title>Page Title</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="author" content="Brandon" />
<meta name="robots" content="noindex,nofollow" />
<meta name="keywords" content="" />
<meta name="description" content="" />
<?php print $stylesheets; ?>
<?php print $scripts; ?>
</head>
<body>
<section id="wrapper">
<header>Header Title</header>
<nav><?print $nav; ?></nav>
<section><?php print $content; ?></section>
<aside> <?php print $sidebar; ?><aside>
<footer>© 2011 Brandon License: GPLv2</footer>
</section>
</body>
</html>
The main function I have to include everything is:
function theme($tpl, $vars = array()) {
extract($vars);
ob_start();
require($tpl);
$template = ob_get_contents();
ob_end_clean();
return $template;
}
$tpl is set to the page.tpl file.
I tried $vars['nav'] = file_get_contents('nav.tpl'); above the theme function just to give it some data to work with. If I remove the $tpl variable and the require() function, I see the UL nav list but when I add back the page.tpl file back in I get this error:
Warning: extract() expects parameter 1 to be array, null given
This works(shows UL nav list):
$vars['nav'] = file_get_contents('nav.tpl');
function theme($vars = array()) {
extract($vars);
ob_start();
$template = ob_get_contents();
ob_end_clean();
return $template;
}
This doesn't:
$vars['nav'] = file_get_contents('nav.html');
theme('page.html', $vars) //page.html is set to correct directory.
function theme($tpl, $vars = array()) {
extract($vars);
ob_start();
require($tpl);
$template = ob_get_contents();
ob_end_clean();
return $template;
}
Any help on getting this to work correctly would be appreciated.
UPDATE: This is my current index.php file:
<?php
define('ROOT_DIR', getcwd());
require_once(ROOT_DIR . '/core/includes/boot.inc');
boot_start(BOOT_FULL);
// Based off of Drupal's drupal_bootstrap(). Set's ini_set's, database
//and starts sessions. This works just fine and I haven't coded any
//theme/template code into it. The only thing boot_start() does for theme is
//load the .inc file that has the theme() function. The .inc gets included
// otherwise I would have gotten a "call to unknown function" error.
$vars['nav'] = file_get_contents(ROOT_DIR . '/core/templates/nav.tpl');
theme('./core/templates/page.tpl', $vars);
I don't quite understand why I am getting the error from extract(). When I add $vars['nav'] without including 'include($tpl)', extract works just fine. It isn't until I try to include the page.tpl file.
The page.tpl file should be loaded on every page request that outputs anything. So I think I only need theme($vars) instead of theme($tpl, $vars = array())
Is there a way I can include page.tpl without passing it to theme(), while passing $vars so that $vars['nav'] overrides the <?php print $nav; ?> tag in page.tpl? Thanks.
SOLVED: Man, I can't believe it took me this long to fix this. Since theme() returned and not echo'ed the data, I had to assign $theme = theme('page.tpl', $vars); then echo $theme; Besides a few PHP notices, it works.
I personally just like to make a file for the individual parts. and then include them.
<?php include('relative/link.php'); ?>
if you want to edit the content in a section I would use variables.
header.php
echo $foo;
index.php
$foo='bar';
include('header.php');
when we include a file it grabs the contents and injects it in the current file and then will process it.
This is more of a style question. I have a template file header.php in which I define a PrintHeader() function.
Callers of this function can specify, via global variables, the title of the page and any Javascript scripts to include when printing the header (because surely not every page will have the same title or want to include the same scripts). I chose to use global variables rather than function arguments because the latter would require the interface to change when adding new arguments.
Is this considered "good" style, and is there a "better" way to do what I'm trying to do?
header.php (simplified)
<?php
function PrintHeader()
{
global $pageTitle, $scripts; // Set by the caller of this function
echo <<<HEADER
<html>
<head>
<title>$pageTitle</title>
HEADER;
if( !empty($scripts) )
{
foreach($scripts as $script)
{
echo " <script type=\"text/javascript\" src=\"$script.js\"></script>\n";
}
}
echo " </head>\n";
}
?>
index.php (simplified)
<?php
$pageTitle = 'Welcome';
$scripts = array('script1', 'script2');
require('header.php');
PrintHeader();
// Print the rest of the page
?>
is there a "better" way to do what I'm trying to do?
sure.
I see no point in defining and calling a function at all. as well as in using heredoc.
header.php (dramatically simplified):
<html>
<head>
<title><?=$pageTitle?></title>
<? if( !empty($scripts) ): ?>
<? foreach($scripts as $script): ?>
<script type="text/javascript" src="<?=$script?>.js"></script>
<? endforeach ?>
<? endif ?>
</head>
index.php:
<?php
$pageTitle = 'Welcome';
$scripts = array('script1', 'script2');
require('header.php');
?>
but still it's not the best way, as it seems you're not using a template where it most valuable - to output page contents itself.
So, I'd make it in three parts:
links.php (simplified):
<?
//include our settings, connect to database etc.
include dirname($_SERVER['DOCUMENT_ROOT']).'/cfg/settings.php';
//getting required data
$DATA = getdata("SELECT * FROM links");
$pagetitle = "Links to friend sites";
//etc
//and then call a template:
$tpl = "links.tpl.php";
include "main.tpl.php";
?>
where main.tpl.php is your main site template, including common parts, like header, footer, menu etc:
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>My site. <?=$pagetitle?></title>
</head>
<body>
<div id="page">
<? include $tpl ?>
</div>
</body>
</html>
and finally links.tpl.php is the actual page template:
<h2><?=$pagetitle?></h2>
<ul>
<? foreach($DATA as $row): ?>
<li><?=$row['name']?></li>
<? endforeach ?>
<ul>
notice native HTML syntax, which is highlighted, readable and centralized in one place instead of being split between numerous functions and files
The point is in having separate template for the every PHP page as well as main site template for them all. With such setup you'll get a lot of advantages such as custom error pages, multiple representations of the same data (say, HTML, JSON or XML) by switching only templates without changing the code and many more
The use of global variables is certainly not advisable, and I question the necessity of using heredoc as you have - not that there is anything inherently wrong with heredoc, just that you seem to have rather arbitrarily utilized it in this sample template.
It is not elegant to use a return-value of a function as the output of each template - this defeats one of the purposes of templates which is re-usability.
Take a look at smarty, if not to directly use it (after all, why re-invent the wheel), at least to get an idea of how a rendering class is used to shuttle in the variables that a template needs without resorting to messy globals.
Here's a very quick overview of a way to do templating:
You have a template class that you can assign data to and then render a template.
Template.php:
class Template
{
protected $data = array();
public function assign($key, $value)
{
$this->data[$key] = $value;
}
public function render($file)
{
extract($this->data);
require $file;
}
}
You then have your template, header.php:
<html>
<head>
<title><?php echo $pageTitle; ?></title>
....
In index.php, you then use the template class to assign data and render your template.
$tpl = new Template;
$tpl->assign('pageTitle', 'My page title!');
$tpl->render('header.php');
This is just a simple example to demonstrate the idea, and could give you a good starting point.
While "better" may be in the eye of the beholder, I would suggest having some sort of functions that set the page bits rather than exposing raw variables. For instance, instead of doing $pageTitle = 'Welcome'; you could have set_page_title('Welcome');.
For JavaScript you could have a function that adds to the current script set -- rather than possibly replacing it all -- such as add_javascript($code);. This will allow a developer to set all of these without having to keep track of what the variable name was, and also without needing to global it as well if they want to set it from within a function.
This is an alternative using output buffering.
p/example_page/index.php is one of your pages:
<?php
ob_start() ?>
<h1>Example</h1>
<p>This is the page content</p>
<?php $main = ob_get_clean();
ob_start() ?>
<script defer src="js/example_page/example.js"></script>
<?php $script = ob_get_clean();
$title = 'Example page';
include 'templates/base.php';
templates/base.php is your reusable layout:
<!doctype html>
<html>
<head>
<script defer src="js/main.js"></script>
<?php echo $script ?>
<title><?php echo $title ?> - Example website</title>
</head>
<body>
<header>
<nav aria-label="Main menu"></nav>
</header>
<main><?php
echo $main;
?></main>
<footer>Example footer</footer>
</body>
</html>
Global variables are generally considered bad, and should be avoided if possible.
Rather than listing every variable in the interface, as you said things could change, pass a single array to the PrintHeader() functions:
<?php
function PrintHeader($opts=array()) {
if(!isset($opts['title'])) $opts['title'] = 'Default Title';
echo <<<HEADER
<html>
<head>
<title>$opts['title']</title>
HEADER;
if(!empty($opts['scripts'])) {
foreach($opts['scripts'] as $script) {
echo " <script type=\"text/javascript\" src=\"$script.js\"></script>\n";
}
}
echo " </head>\n";
}
$opts = array('title'=>'Welcome',
'scripts'=>array('script1', 'script2'));
require('header.php');
PrintHeader($opts);
?>
This way, you can add new capabilities in the function without breaking old code.
I have a PHP script that creates HTML by calling PHP class that I have created. The class creates all the HTML tags one of which is a tag that loads an external JS file. When I try to access the functions from said file nothing happens. Any Ideas?
index page:
function main(){
$content = "Heres some text for you";
$page = new Page($title="MyTitle", $script="external.js", $content=$content)
echo $page->toString();
}
function __autoload($className){
require_once $className . '.class.php';
}
class page:
//class constructor
function __construct($title='untitled', $script='', $content='Default Page class page'){
$this->title = $title;
$this->script = $script;
$this->stylesheet = $stylesheet;
$this->content = $content;
// $this->currentUser = $currentUser;
}
// creates tag structure for HTML pages
function toString(){
return <<<END
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<script type="text/javascript" src="jquery.js"></script>
// Heres the link to the external JS file
<script type="text/javascript" src="$this->script"></script>
<script type="text/javascript">
test();
</script>
<title>$this->title</title>
<link type="text/css" rel="stylesheet" href="$this->stylesheet" />
</head>
<body>
$this->content
<p id='content'>page content</p>
</body>
</html>
END;
}// end toString function
} // end class Page
?>
External JS:
function test(){
alert("ext. JS test works");
}
You cannot have any spaces before the ending identifier of your heredoc:
END;
should be:
END;
I would also check to make sure that the path to your external.js file is correct. Are any of the other things working? Like the title or css? You also are not passing $stylesheet into your __construct anywhere which produces an error trying to set $this->stylesheet, maybe the whole script is failing to load because of that?
Don't see anything that stands out....
Are you sure the JS file is accessible in the same directory as your script (may want to apply an absolute or relative path if necessary)?
You might also, since you have jquery (assuming it's loaded), try putting the call to test(); in an "on ready" block, like so:
$(document).ready(function () {
test();
});
Other than that, I'd use your given browsers debugging tools to see if you can glean anything useful (like the script not even being loaded as a resource).
Good luck!