I've got the following situation. As in your typical ZF2-Application there is existing a layout.phtml-view-script for the layout and a view-script specific to the called action. In my case it's team/frontend/index.phtml.
My problem is concerned with the headtitle. The headtitle-parts are currently set directly within the view-script-files, like the following:
layout.phtml
<?php
echo $this->headtitle('My Application')->setAutoEscape(false)->setSeparator(' | ');
team/frontend/index.phtml
<?php
$this->headtitle('Our team');
This is - as intended - resulting in My Application | Our team. So far so good.
Now I am writing a new module, which - beside some other features - also should provide the possibility for some SEO-stuff. One of the SEO-tasks is to overwrite the title of a the inner view script: team/frontend/index.phtml but not for layout.phtml. The new title is coming from the database, however this is not relevant for this problem.
So for this action I want to be able to produce an outcome like this: My Application | The faces behind the team. As you can see I only want to overwrite the everything the action-view-script sets, but not the title part of the layout.phtml.
Since it's a completly different module, which would add this functionality and both modules should work independendly I hope this is solvable through events/the EventManager.
Sidenote: the new module is called: Node
I tried 2 things, both resulting in the same thing:
I attached to the MvcEvent::EVENT_RENDER EventManager within the onBootstrap-method of my Node\Module.php and (in another attempt) I fetched EventManager of Zend\View\View and attached to the ViewEvent::EVENT_RENDERER_POST.
In the callback-function I fetched the title from the database and set it by fetching the HeadTitle-View-Helper.
Both attempts resulted in a final headtitle of My Application | Our team | The faces behind the team meaning the parts were just appended although I used the SET-Parameter within the callback function.
This is a simplified version of the callback-function:
$viewHelperManager = $serviceLocator->get('viewhelpermanager');
// Get new title-part from database
$titlePart = '...' // In this case "The faces behind the team"
// Set HeadTitle
$headtitle = $viewHelperManager->get('headtitle');
$headtitle($node->getNodeName(), 'SET');
As you can see here I am using SET as the second parameter. I do understand, why it's not working: it's too late the event seems to be triggered when the action-view-script and the layout-view-script are finished processing. However I need a possibility to hook in before the layout-view-script is processed, so that I can overwrite the action-view-scripts' headtitle.
I hope you understand what I mean.
Any thoughts ideas on this? Is there an event which is triggered for every view-script in the queue?
UPDATE 2015-10-14 - 13:10
I've further investigated the code and the event triggering within the code of ZF2. Because of the structure in which it is written, my request is just not possible in the way I wanted it to be.
Like Wilt's and akond's anwers the headtitle should be generally dealt with in the action or in particular other places, however not within the view-script itself.
Wilt posted the link to the ZF2-docs concerning headtitle. There they provide an example of how to set the headtitle within an action.
I knew how to do that, however in the Album-Module Tutorial (http://framework.zend.com/manual/current/en/user-guide/database-and-models.html#listing-albums) they set the headtitle within the view, so I went this way...
Of course, it's no problem for me to fix my other modules (like my Team-Module), however I will run into problems with vendor-modules. If authors of other module keep setting their headtitles within their view-scripts, my Node-Module won't stand a chance. The only thing I could do in that case is to overwrite their view-scripts and remove the setting of the headtitle...
In my opinion you are experiencing this problem because of contamination of the view with data. In other words, you should not have put neither 'My Application' or 'Our team' into the view.
What you should be having instead is a model/helper, that provides view with an appropriate head title. Something along the lines of:
$this->view ()->headtitle ('Our team');
in the controller action and
echo $this->headtitle
in the view.
View should only render data that is provided by model. In our case, the view is a model in its own right. That's bogus.
You should set your head title in your controller not in your view.
Check the ZF2 documentation on head title to see how to properly use this view helper:
$headTitle = $viewHelperManager->get('headTitle');
$headTitle->setSeparator(' | ');
$headTitle->append('My Application');
$headTitle->append('Our team');
In the view only:
<?php echo $this->headTitle() ?>
Outputs the following html:
<title>My Application | Our team</title>
In your other module, in the other controller you can set new variables for the headscript.
Another possibility (don't think it is better) would be to pass your titles as variables to your view in a parameter. For example like this:
$view = new ViewModel(array(
'titles' => array(
'layout' => 'My Application',
'action' => 'Our team'
)
));
$view->setTemplate('template');
return $view;
Now in the views you can do:
$this->headtitle($this->titles['layout'])->setAutoEscape(false)->setSeparator(' | ');
and
$this->headtitle($this->titles['action']);
This last solution is more dirty, because you are not supposed to set your head title like that in the view if you ask me. But it will work and it suits your current solution more (less refactoring).
UPDATE
If you are afraid of others overruling the title you set in their views then you can also extend the the HeadTitle view helper and overwrite the '_invoke' method so it does not allow the overwriting the $title if it is already set.
And then re-register (overwrite) it in the view helper manager:
'view_helpers' => array(
'invokables' => array(
'headTitle' => 'My\Custom\View\Helper\HeadTitle',
)
)
Related
I am newbie to yii. I am doing a small application in Yii.
Lets suppose I have some tables like product, sales, discount, customer,
Now I have done all the Models and Controllers(crud) for these tables. Now when admin wants to enter one new product then he is typing
http://localhost/application/index.php?r=product
. In the same way he has to enter discount to go discount section. Now I want to render all the modules in one application just like dashboard. Where admin can directly make change what he wants from that single page. So can someone kindly tell me how to solve this issue. Any help and suggestions will be really appreciable.
EDIT
I have gone through some links but I did not found any documentation there.
First of all you should think of what should be presented on dashboard, you have choosen some entities already. From that entities there might be different criteria for showing items:
One will show latest products
Other will show last edited posts by user
And maybe sales will show highest sales?
Now, you should choose whenever to allow some actions on theese items.
For product you could have some (sample) quick buttons/link: publish, update
For customer there could be orders
Now, to acomplish this, you would have to define several dataproviders, setup several listviews, and put all that into your DashboardController. No! From Yii conventions, and MVC generally, there should be: fat model, this controller, and wise view.
Taking above into account, you should create widget for each type of data. Widget should be "independent", this will be like "model" for your dashboard. Should contain all logic required for type of entity, and should not require any configuration (for autocreation too).
For dashboard widget you should also create some base class for this purpose, so dashboard widget will look consistently: to have some layout.
A good start for this purpose is CPortLet - this already defines somethink like dashboard widget, with title, and div around it's content.
Here is some example to start with portlets:
class ProductDashboard extends CPortlet // Or intermediate class, ie. DashboardItem
{
protected $_products = array();
public function init()
{
$this->_products = new CActiveDataProvider('Product', array(
'criteria'=>array(
'with'=>array('...'),
'order'=>'t.sort_order ASC',
'condition'=>'...',
'together'=>true,
),
));;
$this->title= 'Newset producs';
parent::init();
}
protected function renderContent()
{
$this->render('productDashboard');
}
}
In portlet view, views/productDashboard.php just place listview:
$this->widget('zii.widgets.CListView', array(
'dataProvider'=>$dataProvider,
'itemView'=>'_productView',
'enablePagination'=>true,
));
In _productView place anything about product:
<h4><?= CHtml::encode($data->name); ?></h4>
<p><?= CHtml::encode($data->description); ?></p>
<p>
<?= CHtml::link('Update', array('/product/update', 'id' => $data->id)); ?>
<?= CHtml::link('View', array('/product/view', 'id' => $data->id)); ?>
... More actions ...
</p>
Finally in your dashboard index view place those portlets:
$this->widget('path.to.ProductDashboard');
$this->widget('path.to.SalesDashboard');
....
Or in some automated way:
// Possibly user defined only etc.
$widgets = array('ProductDashboard', 'SalesDashboard', ...);
foreach($widgets as $name)
$this->widget($name)
First, lets take a look on this. Uh, almost 200 pages, but let me leave it here and refer to it in the following answer.
So, we want a page that can manage edit/delete/update actions with the table, and Yii can help you with it in 2 ways:
1st Is for lazy codders, or a guys who just start to work with framework. Just go to the documentation and find out the 1.6 Creating First Yii Application. This article will helps you to set up the basic configurations with demo models/views/controllers to play with it. The result of this Demo Installation is like your dashboard required with more features to explore
2nd step require a lot of code to show up here, and it will be just an instruction how to build everything step-by-step that you can do in the 1st step automatically with Yii. Just ask if you'd like to know about it more.
Cheers!
It sounds like you want to implement a menu. Assuming that you have at least gone through the Creating First Yii Application mentioned by Ignat B., you can read the CMenu class documentation to learn about them, and your modifications would go in the layout.php file in protected\views.
If its a list of menu that you are looking for, you might try this extension: http://www.yiiframework.com/extension/emetrotile
All you need to do is follow the instructions in the source then call it where you want it to load similar to the one below:
$this->widget('ext.emetrotile.EMetroTile', array(
'Tiles'=>array(
array('title'=>'Test Title', 'tiles'=>array(
array('content'=>array('test1-a','test1-b'), 'liveTileOptions'=>array('data-speed'=>750, 'data-delay'=>3000,'data-stack'=>true)),
array('content'=>'test2', 'position'=>'bottom'),
array('content'=>'test4', 'position'=>'bottom'),
array('content'=>'Blog', 'style'=>'vertical', 'url'=>'http://blog.expressthisout.com'),
array('content'=>'test3', 'style'=>'horizontal'),
array('content'=>'test5', 'position'=>'bottom'),
array('content'=>'test6', 'position'=>'top'),
))
)
));
As Syakur Rahman says, emetrotile is a good extension for creating a dashboard. I've created a nice 3 x 2 menu with it, with an image on the front of each, text on the back, and they flip in a sequence one after the other. It has a very cool effect, but it does have a Windows feel to it.
Drew Green wrote the original js seems to be improving it all the time, See http://www.drewgreenwell.com/projects/metrojs
Ok, the subject makes no sense so Ill try to better describe it here.
Zend Frame work in use here. And I have run into a problem passing variables to my views, well the views included into the "top.phtml" that make up the template. What I am trying to do is implement a breadcrumb concept. The bread crumb file is included into the top.phtml before the content view file. So the breadcrumb variable isn't defined as far as the breadcrumb file is concerned.
I can print_r my array of settings for the breadcrumbs within the controllers view, no problem so it is working I know that much, just anything above that view in particular in the order of things can't get the variable. So I guess what I am looking to have answered is is there a means off passing a variable to the overall scheme of things similar in concept to
$this->view->variable_name = blah;
where something as high as the top.phtml can pick it up for use?
You may be looking for Placeholders.
Example:
Setting a placeholder value from a controller:
$this->view->placeholder('some_placeholder_name')->set('blah');
Setting a placeholder value from a view
$this->placeholder('some_placeholder_name')->set('blah');
Retrieving the placeholder value in a view script or layout:
$value = $this->placeholder('some_placeholder_name');
Placeholder content is rendered towards the end of your application execution so the value set in your controller should be available in your top level top.phtml view script.
I think this will work:
$this->layout()->breadcrumbs = ...
And then print $this->layout()->breadcrumbs in your top.phtml.
Zend Layout
After sending hours trying to get placeholder() to work with partialLoop(), I finally gave up and hacked a fix to pass vars to a partial:
$vars = (array) $this->getVars();
foreach ($this->rows as $row) {
$partialVars = array(
'row' => $row,
'vars' => $vars,
);
echo $this->partial('row.phtml', $partialVars);
}
ugly, but it worked.
+1 for everyone for giving me a clue, however none of the above worked well in my favor. However between them all, they lead me towards finding my answer which is
Zend_Registry
In my Controller I built my array and passed it to Zend_Registry like
$breadArray = array(
array("icon"=>"i_robot", "href"=>"systems/"),
array("href"=>"metrics","text"=>"Metrics")
);
Zend_Registry::set('breaded_crumbs', $breadArray);
Then in my breadcrumb.phtml which is loaded before the content and view I used
print_r(Zend_Registry::get('breaded_crumbs'));
to see if it was working, and it gave me the array's so I for anyone in the future looking to extend a variable outside of the view itself, the registry seems to be the way to go. I tried placeholder and layout, both gave me errors about not being something or another, and when I got them to work in part I wasn't getting what I was expecting.
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'm trying to learn more about the codeigniter framework by porting an existing website to it -- something that's not too complex, or so I thought.
Currently the site is replicated for its users and presents personalized data based on the url, for example, Joe might have his site at:
www.example.com/joe
www.example.com/joe/random-page.php
And you'd replace "joe" with any given user name. The URLs need to be structured this way: /joe/ isnt FOR joe, its for joe's visitors, so I can't rely on a user login or method of this sort. I could switch to joe.example.com but would rather not.
Is there a way I can make this play nice with Code Igniter?
Currently, it would want to call the joe controller. My initial thought is trying to find a way to have a default controller called when a controller doesn't exist, but if some CI pros have advice on a different, better way to handle this, it would be great.
Upgrade to CodeIgniter 2.0 and use $route['404_override'] = 'controller'; or install Modular Extensions which does the same thing, but for now they use $route['404'] instead.
There are a number of different ways to go about this. Just be warned, both of these solutions require you to edit CI's core files. That means you can't upgrade without breaking these edits. Unfortunately hooks do not suitably address this issue.
The easy way:
line 188-195 in system/vodeigniter/CodeIgniter.php handle the logic for what happens when a controller is not found.
The harder but better way:
There is a hook (http://codeigniter.com/user_guide/general/hooks.html)
pre_controller
But this will not work! The reason is that this is called after the controller has been determined, but before anything is actually executed. In other words, it is too late. The next earlier one
pre_system
is in fact too early, because routing has not been done and anything you do the routing will get overwritten.
So I needed the first controller to look the same, yet end up calling a different controller. The reason was that the page was accessed in a hierarchical way, so that it would be the subsection of a subsection and so on.
What I did was add on line 43 of system/libraries/Controller.php
$this->_ci_initialize();
Basically I had it autoload the libraries BEFORE the controller was called, because I was finding that the libraries were not loaded before the controller was called and I needed it to be done so because I needed to check user access authentication and hook directly into the routing itself.
After I did that, I extended one of the native core libraries that were autoloaded (in this case session, for applicaiton specific reasons) and then executed the rerouting.
$RTR = & load_class( 'Router' );
$this->URI = & load_class( 'URI' );
$this->URI->_fetch_uri_string();
I called this code in the start, then put my logic afterwards. This is a sample of what my rerouting logic looks like
if ( $this->segment( 1 ) == 'institute' )
{
if ( ! in_array( $this->segment( 3 ), $course ) )
{
$RTR->set_class( 'courseClass' );
$RTR->set_method( 'index' );
if ( ! $this->segment( 4 ) )
{
$RTR->set_class( 'course' );
$RTR->set_method( 'index' );
}
else
{
$RTR->set_class( 'course' );
$RTR->set_method( $this->segment( 3 ) );
}
}
The original is much longer. I probably should consider writing some sort of plugin or superior way to handle the rewriting rather than silly spagetti logic. However, I needed extremely fine grain control of the controllers being called based on the URLs. This will literally give you god mode control over your controller based on the URLs. Is it a hack? Yes. Is it inelegant? Absolutely. But I needed it done.
Just remember since this edits the core files, you can't easily upgrade after. I think the Kohana framework has a solution to this.
I kept reading the CI docs after Alex's post and found info on the routes.php file which does exactly what I needed.
It allows you to use regular expressions to rewrite the routes (URLs), much in the same manner as mod_rewrite, so I could strip out the user name and end up passing it as a param.
I have a CakePHP application that in some moment will show a view with product media (pictures or videos) I want to know if, there is someway to include another view that threats the video or threats the pictures, depending on a flag. I want to use those "small views" to several other purposes, so It should be "like" a cake component, for reutilization.
What you guys suggest to use to be in Cake conventions (and not using a raw include('') command)
In the interest of having the information here in case someone stumbles upon this, it is important to note that the solution varies depending on the CakePHP version.
For CakePHP 1.1
$this->renderElement('display', array('flag' => 'value'));
in your view, and then in /app/views/elements/ you can make a file called display.thtml, where $flag will have the value of whatever you pass to it.
For CakePHP 1.2
$this->element('display', array('flag' => 'value'));
in your view, and then in /app/views/elements/ you can make a file called display.ctp, where $flag will have the value of whatever you pass to it.
In both versions the element will have access to all the data the view has access to + any values you pass to it. Furthermore, as someone pointed out, requestAction() is also an option, but it can take a heavy toll in performance if done without using cache, since it has to go through all the steps a normal action would.
In your controller (in this example the posts controller).
function something() {
return $this->Post->find('all');
}
In your elements directory (app/views/element) create a file called posts.ctp.
In posts.ctp:
$posts = $this->requestAction('posts/something');
foreach($posts as $post):
echo $post['Post']['title'];
endforeach;
Then in your view:
<?php echo $this->element('posts'); ?>
This is mostly taken from the CakePHP book here:
Creating Reusable Elements with requestAction
I do believe that using requestAction is quite expensive, so you will want to look into caching.
Simply use:
<?php include('/<other_view>.ctp'); ?>
in the .ctp your action ends up in.
For example, build an archived function
function archived() {
// do some stuff
// you can even hook the index() function
$myscope = array("archived = 1");
$this->index($myscope);
// coming back, so the archived view will be launched
$this->set("is_archived", true); // e.g. use this in your index.ctp for customization
}
Possibly adjust your index action:
function index($scope = array()) {
// ...
$this->set(items, $this->paginate($scope));
}
Your archive.ctp will be:
<?php include('/index.ctp'); ?>
Ideal reuse of code of controller actions and views.
For CakePHP 2.x
New for Cake 2.x is the abilty to extend a given view. So while elements are great for having little bits of reusable code, extending a view allows you to reuse whole views.
See the manual for more/better information
http://book.cakephp.org/2.0/en/views.html#extending-views
Elements work if you want them to have access to the same data that the calling view has access to.
If you want your embedded view to have access to its own set of data, you might want to use something like requestAction(). This allows you to embed a full-fledged view that would otherwise be stand-alone.
I want to use those "small views" to
several other purposes, so It should
be "like" a cake component, for
reutilization.
This is done with "Helpers", as described here. But I'm not sure that's really what you want. The "Elements" suggestion seems correct too. It heavily depends of what you're trying to accomplish. My two cents...
In CakePHP 3.x you can simple use:
$this->render('view')
This will render the view from the same directory as parent view.