I'm working on a site where there can be up to 3 levels of hierarchy in the navigation.
main sections in the top header
sub-navigation on the left
optional sub-sub navigation below that
In the past, when I've rolled my own with PHP, I would create a file navigation.php holding a class and arrays for all the sections and sub sections and a couple of functions to output the navigation. I'd set variables on every page in the site (current_section ='', current_sub_section='') so the navigation function would know what to highlight.
In CodeIgniter, I'm wondering if this is still a good approach to use?
I'm going to assume that your main nav sections almost directly map to your controllers, ie.
Home | News | Events
In your top bar, maps to:
/home
/news
/events
If this is the case, you already have a simple way of selecting your nav array.
You can put an array of nav item-link pairs in your controller constructor & pass them along to a sub-view in your output.
Example:
class HomeController extends CI_Controller
{
private $nav;
public function __construct()
{
parent::__construct();
$this->nav = array(
array('Browse', site_url('news/browse')),
array('Edit', site_url('news/edit'))
);
$this->load->vars(array('NavigationArray' => $this->nav));
}
// ...
}
Now what you've done is auto-registered a variable in all your views $NavigationArray that contains an array of Display Name - Link pairs.
You can then load a basic Navigation View that builds up your subnav from that variable (as it's availiable everywhere).
<? foreach($NavigationArray as $entry): ?>
<?=$entry[0];?>
<? endforeach; ?>
And below that you can look for the existance of a sub-nav array that you could optionally set in your controller, or whatever (the third optional nav you talked about)
<? if(exists($SubNavigationArray)): ?>
<? foreach($SubNavigationArray as $entry): ?>
<?=$entry[0];?>
<? endforeach; ?>
<? endif; ?>
Please remember, this example is very basic, but this is generally how we pass data around, I wouldn't want you to put global variables anywhere and try to queue off them. Simply "load" variables into the view engine, and they'll be availiable when you go to render your views/subviews.
This way the controller controls what nav items get displayed.
Also note:
You can pass the variables explicitly instead of trusting they will exist in the scope of your view.
$this->load->view('myview', array('NavigationArray' => $this->nav));
Hope this helps.
Here's basically what I do to determine "active" links in Codeigniter:
$active_class = '';
$url = site_url('your/link/url');
if ($url == current_url())
{
$active_class = ' class="active"';
}
$link = '<a href="'.$url.'"'.$active_class.'>Link Text</a>";
Keep in mind this is a basic example and usually run in a loop. The best way depends on what your navigation array looks like, and what you consider to be "active" (f you want to "activate" links whose href partially match the url).
The best way is to use 3 different views, a top view, a left view and a bottom view then in your controller you can pass the apporiate vars to each view so you would do something like
$top[current] = something;
$top[current_sub] = somethingelse;
$this->load->view('top_nav', $top);
$this->load->view('left_nav',$left);
...
Then in your views you can handle the variables passed to them.
Related
For my Codeigniter site, I started by making a view for each controller situation. This was impractical, as it would require going back to the code for each to make a change. So I changed approach and operated on a 'default' controller with optional fields. I then thought I could load special views as needed into it.
I put together this view with optional fields with fields for $title, $search_bar on/off etc. However, now came the content area. I was able to load more views into this default view using:
$data['content_views'][]='blocks/login';
$this->load->view('default/a', $data);
and in the 'default'view:
if(isset($content_views)&& (is_array($content_views)))
{
foreach($content_views as $content_view)
{
$this->load->view(&$content_view);
}
}
(and that works fine)
Two questions:
Am I making things to complex? Is this an accepted way of doing this? Or have I misunderstood the functioning of a view and how they are intended to work?
I want a way to mix the $content_view, i.e. a block of text, then a view. I'm not quite sure as to how to proceed. Say I want a message first, then a view, then more text. This method will only accept views.
Can anybody help me create this flexible approach?
Yeah I would say you're making things a little complex. While I may not be following your description well enough to know precisely how to respond, I can tell you how I do it:
First the whole site is run through a template so the header and footer are the container file and all views needed within the site are rendered as page type views - like an article page, a gallery page, etc. Components are loaded and passed to the views as strings:
$content['sidebar'] = $this->load->view('components/sidebar', $data, true);
That last true says to render as string.
Essentially, this means the page views are pretty much html with php echoing out the necessary elements. No views calling other views, which is how I read your example.
CI loads views progressively, so your controller can output like so:
$this->load->view('header', $header_data);
$view_data['sidebar'] = $this->load->view('components/sidebar', $sidebar_data, true);
$this->load->view('content', $view_data);
$this->load->view('footer', $footer_data);
and in content view, handle the sidebar like so:
<?php if(isset($sidebar)): ?>
<nav>
<?php echo $sidebar; ?>
</nav>
<?php endif; ?>
And, assuming you populate those arrays for each view it will render header, then content, then footer. And also render sidebar if it is present.
So combining everything, I'm basically saying you can load in sections in your controllers progressively, passing sub-views as strings to whichever section makes sense. That keeps your view controlling in the controller where it belongs and not in the view files themselves. In my experience, I have not had to write a site that was so complex that this construct wasn't perfectly suitable if the site is planned well.
To start out: Not Wordpress! Just plain old PHP. Here's what I'm trying to do:
I've got a horizontal navigation bar at the top of my page with the links 'Home, About, Info, Contact'
Most of the pages also have a vertical navigation bar, the sidebar.
If I'm on the Home page, no sidebar needs to be shown.
If I'm on the About page there has to be a sidebar with various other subjects. The Contact page needs to show a sidebar with a Route Description and Contact Form link etc.
I was thinking about achieving this with $GLOBAL variables and put something like $GLOBAL['sidebar] = 'home' $GLOBAL['sidebar'] = 'contact' etc... on top of every page. In the PHP file that would render my sidebar I would use an if structure to see what sidebar needs to be rendered. But using global variables is something I've always been taught is wrong and shouldn't be used. After that, my mind drifted to $SESSION variables, but that would actually be exactly the same but with some extra concerns like session_start() etc.
I'm self-taught in PHP so I don't know what could be best used to solve this particular (and I presume very common) "issue". Any insights about this matter would be greatly appreciated. Thanks.
The solution will depend a lot on how you've set up your code structure already, but in more general terms the way I usually do it is to:
Include the sidebar as part of your template included on every page.
Set up your sidebar along the lines of this:
<?php
if (sizeof($sidebarModules)>0) {
?>
<div id="sidebar">
<?php
if (in_array('contact',$sidebarModules)) {
// display contact form
}
if (in_array('route',$sidebarModules)) {
// display route description
}
if (in_array('login',$sidebarModules)) {
// display login
}
?>
<\div>
<?php
}
?>
Then at the top of each page make sure you define the array $sidebarModules. Something like:
<?php
$sidebarModules = array(
'contact',
'login',
'description'
);
?>
I'm not sure if this is the best solution, but it's worked well for me in the past.
Edit:
or do it more efficiently if you're templates are named in a standard convention, e.g.:
<?php
if (sizeof($sidebarModules)>0) {
?>
<div id="sidebar">
<?php
foreach ($sidebarModules as $module) { // loop round all modules
include($module.'_tpl.php'); // include module template
}
?>
<\div>
<?php
}
?>
Let say I separate my page into 4 parts. One is banner, one is login, one is main, another is footer. Banner and Footer is static, it won't change. and login is based on different user right or login status, lastly the main is based on many thing to do it. So, I have 4 parts of element:
http://localhost:8888/my_app/index.php/static_page/banner
http://localhost:8888/my_app/index.php/static_page/footer
http://localhost:8888/my_app/index.php/user/login_fragment
http://localhost:8888/my_app/index.php/system/main_fragment
But the user only see one page, but it combine all the staff in...So, instead of using iframe to put them together, how can I let these page separate the logic, but can combine in one view? Thank you.
I want to share what I have done. Although not an original idea(do not remember from where I got), hopefully this will help you. I have an 'includes' [application/views/includes] folder, which contains the following files:
footer.php, header.php, navigation.php and template.php
Now the code in the template.php is as the following:
$this->load->view('includes/header');
$this->load->view($main_content);
$this->load->view('includes/footer');
For usage, let me give you an example:
$data = array (
'page_title' => 'Users Listing',
'title' => 'Users Listing',
'main_content' => 'users/showlist',
);
$this->load->view('includes/template', $data));
The elements in the $data array, will be available in the view [application/views/users/showlist.php] as $page_title, $title etc. You can also send arrays or HTML content.
Just call load->view multiple times
in your controller method:
function index()
{
$this->load->view("banner",array(/** some parameters if you want **/));
$this->load->view("main",array(/** some parameters if you want **/));
$this->load->view("footer"); // this one doesn't need parameters maybe
}
See the views documentation page
Note: within a view file itself (e.g. inside banner.php) you can call load-view again to insert a nested view.
Note2: you can also return the result of load->view into a variable if you don't want to output it right away, by adding a true parameter as the 3rd parameter:
$login_box = $this->load->view("login",array(),true);
// $login_box contains the view html.
echo $login_box; // or pass it as a parameter to another view if you want
First of all
$this->load->view('filename');
Can be used as many times as you want in the same controller function, and they will appear in the right order.
What you did is separate in controllers parts that must be used in the same controller. I suppose you have these in files like
/application/controllers/static_page.php
/application/controllers/user.php
/application/controllers/system.php
And, without massive modification of CodeIgniter, you can't call a controller from another controller, which is what was stopping you. If you have to use the same processing code for many functions, you have to make either a helper, that you can use anywhere, or the more elegant solution, that is having common controller functions in /libraries/MY_Controller.php.
MY_Controller.php would be called always before a controller is called
<?php
if (!defined('BASEPATH')) exit('No direct script access allowed');
class MY_Controller extends CI_Controller
{
function __construct()
{
parent::__construct();
// you can do any kind of processing here
// in example if you have some view counter
// this place is always called
}
function a_generic_function($rawdata)
{
// do some processing here
return $data;
}
}
Then, you will be able to use this function in any of your controllers:
$this->a_generic_function($rawdata);
Now, the smartest way to deal with a header+footer is making a "layout" file that contains both the header and the footer.
Here's an example of layout.php view:
<!doctype html>
<head>
<meta charset="utf-8">
<title><?php echo $title; ?></title>
</head>
<body>
<div id="container">
<header>This page has been made by <?php echo $user_name; ?>/header>
<div id="main" role="main">
<?php echo $main_content_view; ?>
</div>
<footer>Write your footer here</footer>
</div>
</body>
</html>
You see there's $main_content_view in the middle.
With this you will do, in your controller:
// add the HTML for the login to the variable
// TRUE means we don't output it in the browser, but put it in a String
$this->viewdata["main_content_view"] = $this->load->view('login', $data, TRUE);
// want to add more data? just ADD it to the variable, like a String
$this->viewdata["main_content_view"] += $this->load->view('main', $data, TRUE);
// done? send it to the layout, and pass it the $this->viewdata
$this->load->view('layout', $this->viewdata);
What else can you do with this? You can use the $this->viewdata like:
$this->viewdata["title"] = 'Function title';
$this->viewdata["user_name"] = $user_name;
so you can edit your layout as much as you want like any other view.
Phil Sturgeon has written a popular template library for Codeigniter which will do what you want in a smarter way than what has been described in the other answers so far. You can read its documentation here: http://philsturgeon.co.uk/demos/codeigniter-template/user_guide/
The documentation might take a while to understand because it lacks examples.
You can download the template library here: https://github.com/philsturgeon/codeigniter-template
I want to use the if statement to select specific pages that have <body class="section-category"> and output custom content for only those pages.
My PHP is not very good and I hope this is a very simple task to do. Can anyone give me a tip?
I would not check for the existence of a certain body class. Those classes are only the results of other if-else logic and they can easily be overridden or altered, breaking your page template. It's better to check the values those classes are based upon. If I were you, I would try to figure out how this body class was generated and re-use that code.
For example, if your theme's template.php does something like this:
$body_classes[] = 'section-' . form_clean_id(arg(0));
Then I would put this in my template.php:
<?php if (form_clean_id(arg(0)) == 'category'): ?>
// Do fancy stuff!
<?php endif; ?>
It sounds like you want a flag available for certain pages; one that you can read out and act upon in your theme.
In template.php:
function YOURTHEME_preprocess_page(&$vars) { //Many themes already have this function implemented
if (stripos($vars['foo-bar'], 'section')) {
$vars['is_section'] = TRUE;
}
else {
$vars['is_section'] = FALSE;
}
}
Then in your page.tpl.php
<?php if ($is_section): ?>
<p>I am in a section</p>
<?php endif; ?>
That way you keep logic where it belongs: in the preprocessors. And you keep logic out of the template, where it most certainly does not belong!
The if (stripos($vars['foo-bar'], 'section')) { can most probably be made a lot smarter, if you do a var_dump($vars) there, all available variables will dump on your screen (browser). I am certain you will find a variable that you can check against, and that is less 'fuzzy' then a body class. After all: that class is meant for one thing only: to serve as class in the body-tag. And not for checking if you are in some section.
You have the full Drupal available there too, so you can even use any function that (e,g, the section) modules provide.
I would look into a module like Context or Panels to do this- it will serve you better in the long run than coding it in.
So I have a menu that I want to generate a main menu based on the authenticated user's access level. No problem creating the menu, however I want to automatically create the generated menu in my "header" view. So in my controller I am calling the "header" view, but I don't want to pass this dynamic part of the header like this:
$data['menu'] = 'Some Generated HTML Menu';
$this->load->view('header',$data);
I would rather it already be included in my header file, but I'm not exactly sure how to do this (aside from adding the $data declaration from inside of my constructor).
I don't know if this will help you, but this was how I did my template system for the longest time before moving on to a more advanced method.
Top Head: views/inc/top_head.php
<html>
<head>
<!-- all of your imports you want across all pages -->
Bottom Head: views/inc/bottom_head.php
I do it this way so that I can split and add custom Javascript things and maybe bring in special case imports.
</head>
<body>
<div id="main_container">
<div id="navigation">
<?php
// DO YOUR NAVIGATION MAGIC HERE
if($is_logged_in) :
// BAM MAGIC DONE
else :
// No magic show here
endif;
?>
</div>
Footer: views/inc/footer.php
This is where you will put in your footer things etc....
</div>
</body>
Now we are at the point that we need to actually fill content in to the template
Index Page: /views/some_controller/index.php
<?php $this->load->view('inc/top_head.php'); ?>
<?php $this->load->view('inc/bottom_head.php;) ?>
<h1>Hello</h1>
<p>Some filler content and stuff I guess would go here...Of course</p>
<?php $this->load->view('inc/footer.php'); ?>
So there, we have a quick template system. Now to show you what I have done for a controller
<?php
class Some_Controller extends Controller {
public $page_data;
public function __construct() {
parent::__construct(); // Load parent constructor
// This is page data that we obviously don't want to keep retyping
$this->page_data = array(
'is_logged_in' => FALSE, // Obviously do some test here
'page_title' => 'Some Title'
);
}
public function index() {
$this->_load('some_controller/index');
}
/** Should think of a better name but meh */
private function _load($view) {
$this->load->view($view, $this->page_data);
}
}
I hope this helped in some way or another. Mind you this is a quick write up. If I really wanted this to go into production, I would have moved the _load function into a parent class and extended it. I would also probably move the page_data variable along with it.
You could just make $data['menu'] = an multi-dimension array of (button_name, url)'s. Then in the view you pass this array to a plugin which generates the html menu based on that array.