thanks for reading, I know the question might sound fairly common, and well I wont deny the fact that maybe I'm just formulating what i want wrongly.
Lets start by shooting it straight, then specifying.
I have a cakephp app with 2 layouts, one layout renders the whole "public" page, and lets call it admin "admin" layout that will render only actions to authenticated users.
in my admin layout, I am printing an element which is the navigation bar.
what I want to do is, without setting on every single controller, the options to set a variable containing the navigation bar values (yes dynamically filled from a specific model)
I want to be able, to set a variable, which will contain a list of values gotten from a model.
The Model is called "Section", which is a table, that contains a list of "sections" of the application.
this navigation bar, needs to print the values of each section, so I need them to be dynamic, hence, I think (again I might be wrong) i need to set that variable somewhere, to make it available to the element, so when the layout "admin" is rendered, the menu bar, is actually filled with the available values of the sections.
I tried doing a Configure::write on AppController, but no dice, it just allowes me to use a variable on controllers, when what I want to do is, loop through the arrah "sections_for_menu" or whatever we call it, and then print the options avaliable on the menu bar.
so you are a bit more comfortable with the idea, the nav bar is a bootstrap based "navbar" with "dropdowns".
i should be able to
<ul class="mycoolclass">
<?php
foreach($sections as $section) {
echo '<li>" . $section . "</li>';
}
</ul>
and thus, printing each value on a new list with its link, and whatnot.
I have been reading with no luck, and have to admin that I am myself fairly new to cakephp, been using it for no longer than 2 weeks.
Any help reaching a solution to this need, is highly appreciated.
UPDATE:
Hi #nunser, thank you very much for your reply.
indeed I'm using an element
this is my layout "base"
<body>
<?php echo $this->element('admin_navbar'); ?>
<!-- container -->
<div class="container-fluid">
<!-- , array('element' => 'flash') -->
<?php echo $this->Session->flash('flash'); ?>
<?php /*echo $this->fetch('content');*/echo $content_for_layout; ?>
</div>
</body>
I'll try your suggestion and see how it goes
I have a controller "SectionsController" which is in charge of well all Section related actions on the app, what I need is to set in the global variable, a list of sections, so I can print the link inside my navbar!
lets assume the following scenario
$this->set('sections_for_navigation', $this->Section->find('list', array('fields' => array('id', 'name'))));
so then i can access the variable $sections_for_navigation from my element, and render the list of sections.
[19-02-2014 - update] tried it.
based on what #nunser suggested, im actually able to, beforeFilter, setting the value, as if i var_dump it, it actually gives me the array as i expected.
public function beforeFilter(){
$this->loadModel('Section');
$secciones = $this->Section->find(
'list', array(
'fields' => array(
'id',
'name'
)
)
);
$this->set('navigation', $secciones);
Then call
public function beforeFilter() {
parent::beforeFilter();
}
from the controllers, did the trick!, now, do i have to add a beforeFilter to every controller to share the navigation through all the app?, is there a way to avoid having to add that method to every single controller? (not that it bothers me, so far it does what i need,which is actually great)
For those kind of navigation things, I do it in beforeFilter or beforeRender in the AppController.
public function beforeFilter() {
//queries and stuff that gets the array for navigation
$this->set('mainNavigationBar', $navigation)
//remember to pass that variable to the element in the view
}
As of to where, beforeFilter or beforeRender, it depends on what type of rule you want to stablish. For example, if you will have same elements for navigation everywhere, be it that the user has permission or not, or if you might want to alter the navigation variable depending on the action being executed, I'd go with doing it in beforeFilter. That way you can tweak things in navigation if, for example, the user doesn't have permission to access any Section (totally making up the model relation here). Though for that, you might want to keep access to the navigation array in the controller.
Tweaked example
protected $_mainNav = array();
public function beforeFilter() {
//queries and stuff that gets the array for navigation
$this->set('mainNavigationBar', $navigation)
//remember to pass that variable to the element in the view
$_mainNav = $navigation;
}
//other controller
public function randomAction() {
//some reason that makes you modify the navigation
unset($_mainNav[0]); //making that up...
$this->set('mainNavigationBar', $navigation)
}
Now, if you won't change the navigation values (once you've added them dynamically), then go with beforeRender, that way you can check permissions and other stuff before bothering with queries for navigation (example to follow would be the first one).
If values of the navigation change per controller, overwrite the function like
RandomController
public function beforeFilter() {
parent::beforeFilter();
$_mainNav = array('no nav'); //example
}
And that's it. Don't know if you need more detail than that, if so, please explain you problem a little more. Oh, and you mention "to make it available to the element", so I'm guessing you're using an element for the navigation bar. If not, please do.
Related
I have an element called userbar on every page - it tells the user if he/she is logged in or not. I created this element and echoed it in default.ctp:
<?php echo $this->element('userbar', array('text' => 'You are not logged in.')); ?>
Now it shows on every page. However, I can't find anywhere how to change this text. For e.g., I would like to access this element from some controller and change it. How?
You set a view variable and then use that.
<?php
class MyController extends AppController
{
function myaction () {
$this->set ('my_var', 'You are not logged in');
}
}
?>
And then in the view:
<?php echo $this->element ('userbar', array ('text' => $my_var)); ?>
Considering this is something you'd do on every page request its best to put it in the AppController::beforeFilter().
There are other ways to do this. But if you render the element in the controller you still need to set a view variable and echo that in the view.
Hope this helps.
I think that vanneto did a good job on answering the question very specifically. But based on my opinion what happens here is a design flaw. That's why I add this answer to give you another option on how to approach this question. Because I see this kind of solutions and on the longer run they cause issues.
The case is a logged-in or logged-out text.
Let's say that you use the Auth component:
http://book.cakephp.org/2.0/en/core-libraries/components/authentication.html
We will start at the controller, likely you have something like this in the AppController:
public function beforeFilter() {
parent::beforeFilter();
$this->set('userIsLoggedIn', AuthComponent:: loggedIn());
$this->set('loggedInUser', AuthComponent::user());
}
So what this does: At every request it sends the logged in user to the view. Now you could say the controller could have an if statement to detect which text should be sent out but it's not really necessary.
In you element you could do that also.
So in your element put something like:
if($userIsLoggedIn) {
echo 'User is logged in.';
}else{
echo 'You are not logged in!';
}
Generally we move a bit more to helper to implement this kind of logic because they are classes which have more options for well styled coding. But it's also doable simply with an element.
So now you got the texts right. Then you get to the point: Does a static text belong to the element? No, it does not. So what would improve it is to implement it like:
if($userIsLoggedIn) {
echo __('User is logged in');
}else{
echo __('User is not logged in');
}
That way you can put the static texts into your .po files. If you don't know what they are:
http://book.cakephp.org/2.0/en/core-libraries/internationalization-and-localization.html
The element can now be used also if your site becomes multi language for example. Or you could let your textwriter edit the texts without touching the source code.
As you see it's a different approach but I think it will give you more clear code. It decoupled the code, the controller does his task, the element does his task and the text is also seperated out because it doesn't belong hardcoded in the views.
In terms of code it's not much more so I would strongly advise some solution which looks like this. Could also be done with a helper.
Some sources on this kind of approaches:
http://en.wikipedia.org/wiki/Object-oriented_programming#Decoupling
http://en.wikipedia.org/wiki/Single_responsibility_principle
I'm making a custom module for PyroCMS, and I want to get the section menu working with regard to applying the current class. The CMS php, which I don't want to change looks like this:
<li class="<?php if ($name === $active_section) echo 'current' ?>">
When I'm viewing /admin/courses/ this is correct, and the first navigation element has the class, current.
$name is taken from the language file, as set up in details.php.
$active_section is taken from the view, and is equal to
$this->_ci_cached_vars['active_section']
However when I view /admin/courses/chapters/, 'courses' is still determined by the system to be the current section, so the navigation is confusing.
What I need is a way of changing the value of $active_session in the view acording to which function of the controller (index, chapters or pages) is being used.
I've tried changing the value of $this->_ci_cached_vars['active_section'] in each controller function, but that doesn't work. Any ideas?
I'm sure there's something basic I'm missing completely.
Got it.
I'm using multiple methods in one controller, and the 'protected $section = 'courses'; line, which happens before the index method, was setting the section for everything.
It couldn't be set a second time within another method, but there is a way to define a section within a method.
$this->template->active_section = 'section';
Starting my method as follows gave me what I wanted.
public function chapters(){
//Set active section
$this->template->active_section = 'chapters';
...
}
I need a bit of feedback to make sure I'm not completely missing the point of CakePHP/MVC.
I'm designing a mini-cms in CakePHP--essentially a photo album. I'm looking to have a standard, boiler-plate drop down menu on every page. Naturally, the menu needs to be dynamic as the user deletes and adds albums.
My goal: Build something that moves in the opposite direction of View::actionRequest() (ie, instead of view calling back to the controller, have the controller push a set variable to the View class before it renders.) My understanding is that View::requestAction() is not very graceful and is quite slow.
This is my model for the table containing the menu keywords. app/Model/ModelItem.php
class MenuItem extends AppModel {
public function buildMainMenu() {
return $this->find('all');
}
}
Since I want it everywhere, I put the call in the AppController
class AppController extends Controller {
public function beforeFilter() {
$this->loadModel('MenuItem');
$this->set('mainMenuItems',$this->MenuItem->buildMainMenu());
}
}
And this is an element that gets dumped into the top of my default layout for the CSS
- Element File: app/View/Element/navigation.ctp
- Layout: app/View/Element/Layout/default.ctp
<ul id="navigation">
<?php
foreach($mainMenuItems as $item) {
echo "<li>".$item['MenuItem']['name']."</li>";
}
?>
</ul>
I end up with a lovely, bullet list of all the items in the table. Am I completely mucking this up? Do I have it all wrong? I have no idea.
I don't see anything wrong with your approach. Here are a few notes though, completely optional:
I'd move the menu fetching code from beforeFilter() to beforeRender(). There no reason to do it that early, you may throw an exception before the menu is needed, or you may redirect your user to another page. Additionally, you have more information about the request there, so you can do things like highlight the current page/item/user in the menu.
Move the element rendering code into some sort of a MenuHelper, which will make your layour cleaner (e.g. $this->Menu->render() in the layout). You could also use the HtmlHelper to render your items through cake, and let cake take care of markup correctness.
If you move the rendering into a helper, implement some sort of caching scheme for the whole thing, to speed things up - if it's necessary. Whether the caching is possible or not, or whether you should do it in your controller or your helper is entirely up to you and your app :)
I guess the overall advice is to try to isolate bits of functionality from the rest of the app; it's easier to read, maintain and modify.
I'd parcel it up into an element and use request action to fetch the data when you output the menu in the layout/view:
/app/View/Elements/main_menu.ctp:
$mainMenuItems = $this->requestAction('/menu_items/buildMainMenu');
<ul id="navigation">
<?php
foreach($mainMenuItems as $item) {
echo "<li>".$item['MenuItem']['name']."</li>";
}
?>
</ul>
and include it in your layout/view with:
echo $this->Element('main_menu');
This way, if you have a view/layout (e.g. Ajax) that doesn't need the menu you wont waste a database query.
I am currently involved in the development of a larger webapplication written in PHP and based upon a MVC-framework sharing a wide range of similarities with the Zend Framework in terms of architecture.
When the user has logged in I have a place that is supposed to display the balance of the current users virtual points. This display needs to be on every page across every single controller.
Where do you put code for fetching sidewide modeldata, that isn't controller specific but needs to go in the sitewide layout on every pageview, independently of the current controller? How would the MVC or ZF-heads do this? And how about the rest of you?
I thought about loading the balance when the user logs in and storing it in the session, but as the balance is frequently altered this doesn't seem right - it needs to be checked and updated pretty much on every page load. I also thought about doing it by adding the fetching routine to every controller, but that didn't seem right either as it would result in code-duplication.
Well, you're right, having routines to every controller would be a code-duplication and wouldn't make your code reusable.
Unlike suggested in your question comments, I wouldn't go for a a base controller, since base controllers aren't a good practice (in most cases) and Zend Framework implements Action Helpers in order to to avoid them.
If your partial view is site-wide, why don't you just write your own custom View Helper and fetch the data in your model from your view helper? Then you could call this view helper directly from your layout. In my opinion, fetching data through a model from the view doesn't break the MVC design pattern at all, as long as you don't update/edit these data.
You can add your view helpers in /view/helpers/ or in your library (then you would have to register your view helper path too):
class Zend_View_Helper_Balance extends Zend_View_Helper_Abstract
{
public function balance()
{
$html = '';
if (Zend_Auth::getInstance()->hasIdentity()) {
// pull data from your model
$html .= ...;
}
return $html;
}
}
Note that you view helper could also call a partial view (render(), partial(), partialLoop()) if you need to format your code in a specific way.
This is a pretty simple example, but to me it's enough is your case. If you want to have more control on these data and be able to modify it (or not) depending on a particular view (or controller), then I recommend you to take a look on Placeholders. Zend has a really good example about them here on the online documentation.
More information about custom view helpers here.
When you perform such a task, consider using the Zend_Cache component too, so you won't have to query the database after each request but let's say, every minute (depending on your needs).
What you are looking for is Zend_Registry. This is the component you should use when you think you need some form of global variable. If you need this on EVERY page, then you are best adding it to your bootstrap, if you only need it in certain places add it in init method of relavent controllers.
application/Bootstrap.php
public _initUserBalance()
{
$userId = Zend_Auth::getInstance()->getIdentity()->userId;
$user = UserService::getUser($userId);
Zend_Registry::set('balance', $user->getBalance());
}
application/layouts/default.phtml
echo 'Balance = ' . Zend_Registry::get('balance');
That wee snippet should give you the right idea!
In this case, I usually go with a front controller plugin with a dispatchLoopShutdown() hook that performs the required data access and adds the data to the view/layout. The layout script then renders that data.
More details available on request.
[UPDATE]
Suppose you wanted to display inside your layout the last X news items from your db (or web service or an RSS feed), independent of which controller was requested.
Your front-controller plugin could look something like this in application/plugins/SidebarNews.php:
class My_Plugin_SidebarNews
{
public function dispatchLoopShutdown()
{
$front = Zend_Controller_Front::getInstance();
$view = $front->getParam('bootstrap')->getResource('view');
$view->sidebarNews = $this->getNewsItems();
}
protected function getNewsItems()
{
// Access your datasource (db, web service, RSS feed, etc)
// and return an iterable collection of news items
}
}
Make sure you register your plugin with the front controller, typically in application/configs/application.ini:
resource.frontController.plugins.sidebarNews = "My_Plugin_SidebarNews"
Then in your layout, just render as usual, perhaps in application/layouts/scripts/layout.phtml:
<?php if (isset($this->sidebarNews) && is_array($this->sidebarNews) && count($this->sidebarNews) > 0): ?>
<div id="sidebarNews">
<?php foreach ($this->sidebarNews as $newsItem): ?>
<div class="sidebarNewsItem">
<h3><?= $this->escape($newsItem['headline']) ?></h3>
<p><?= $this->escape($newsItem['blurb']) ?></p>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
See what I mean?
Quick question about general MVC design principle in PHP, using CodeIgniter or Kohana (I'm actually using Kohana).
I'm new to MVC and don't want to get this wrong... so I'm wondering if i have tables:
categories, pages, notes
I create a separate controller and view for each one...? So people can go to
/category/#
/page/#
/note/#
But then lets say I want to also be able to display multiple notes per page, it would be bad to call the note view in a loop from the page view. So should I create some kind of a function that draws the notes and pass variables to that function from the note view and from a loop in the page view? Would this be the best way to go about it, if not how else should I do it...?
Thanks,
Serhiy
Yes, instead of just passing 1 entity (category, page, note) to your view, pass a list of entities. With a loop inside the view, you can display the whole list.
That view may call another one (or a function) that know how to display one entry.
I would personally have a "show" method for one item and a "list" method for multiple. In your controller you can say something like $page_data['note'] = get_note(cat_id,page_id) for the "show" method and $page_data['notes'] = get_all_notes(cat_id) for the "list" method.
Then in your view, you loop over the $page_data['notes'] and display HTML for each one. If the list view is using the same "note" HTML as the "show" view, create a template or function to spit out the HTML given a note:
// In your "list" view
foreach($n in $page_data['notes']){
print_note_html($n)
}
//In your "show" view
print_note_html($n)
The print_note_html function can be a helper method accessible by all views for Notes. Make sense?
You can loop in the View. The View is allowed can also access the model in MVC. See: http://www.phpwact.org/pattern/model_view_controller
You don't need to have a controller (or model) for each table.
In CodeIgniter I create a separate helper file where I put functions that return the markup for UI elements that may need to be included multiple times in the one view.
In your example, I would create a function to return the markup for a note.
application/helpers/view_helper.php
function note($note)
{
return '<div class="note">' .
'<h2>' . $note->title . '</h2>' .
'<p>' . $note->contents . '</p></div>';
}
I would normally auto-load this helper file. And then in the view I would do something like this.
echo note($note);
For a list of notes in a view, I would iterate the list calling this function.
<div class="note-list">
<?php foreach ($notes as $note) : ?>
<?php echo note($note); ?>
<?php endforeach; ?>
</div>
I found that including a view many times in another view was slow. Thats why I did it this way.
Edit
I just dug into the CodeIgniter Loader class and sure enough a PHP include is being done every time you call
$this->load->view('view_name');
This means that if you use this method to display a list of 20 notes, you're going to be doing 20 separate includes.