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
Related
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',
)
)
I have been going through the gridfield class documentation here;
http://doc.silverstripe.org/framework/en/reference/grid-field
Here is the code in question. While it does display a grid-field it adds a button on each columns. How would I edit this code to not display the buttons? The buttons are links to a non-existent page.
Link to rendered page; http://www.silverstripe.org/assets/Uploads/Capture28.JPG
public function AllPages() {
$gridField = new GridField('pages', 'All pages', SiteTree::get());
$dataColumns = $gridField->getConfig()->getComponentByType('GridFieldDataColumns');
$dataColumns->setDisplayFields(array(
'Title' => 'Title',
'URLSegment'=> 'URL',
'LastEdited' => 'Changed'
));
return new Form($this, "AllPages", new FieldList($gridField), new FieldList());
}
The cause:
The SilverStripe GridField is pretty well built.
The Basic GridField has pretty much no features at all. Its just a plain table containing the data you want.
All other functionality is added by so called "Components" which are managed by the GridFieldConfig.
When you create a GridField like you did, without specifying a config, it will Create a config for you (GridFieldConfig_Base).
The class GridFieldConfig_Base is just a normal GridFieldConfig with some components already added.
One of those components that is already added for you is called the GridFieldSortableHeader which allows you to press on fields to sort the table (which is what produces those buttons that you see).
The reason the Links of the Buttons are dead is probably because there is some routing problem (The GridField is not that well tested in FrontEnd yet) or you maybe have forgotten to add the action AllPages to $allowed_actions.
Solutions:
Plain table
if you don't really need any feature of GridField, and you just want a plain table, the easiest way is to just set an empty config:
public function AllPages() {
$config = GridFieldConfig::create();
$dataColumns = GridFieldDataColumns::create();
$dataColumns->setDisplayFields(array(
'Title' => 'Title',
'URLSegment'=> 'URL',
'LastEdited' => 'Changed'
));
$config->addComponent($dataColumns);
$gridField = GridField::create('pages', 'All pages', SiteTree::get(), $config);
return Form::create($this, __FUNCTION__, FieldList::create($gridField), FieldList::create());
}
Remove just the Sortable Header
$gridField->getConfig()->removeComponentsByType('GridFieldSortableHeader');
// if you don't have a SortableHeader, you probably also don't want a filter
$gridField->getConfig()->removeComponentsByType('GridFieldFilterHeader');
Replace the Sortable Header with a text only header row
Unfortunately, there is no normal header in SilverStripe at this time, but the great gridfieldextensions module from Andrew Short brings you one.
Get the module on GitHub or Packagist
$gridField->getConfig()->removeComponentsByType('GridFieldSortableHeader');
// if you don't have a SortableHeader, you probably also don't want a filter
$gridField->getConfig()->removeComponentsByType('GridFieldFilterHeader');
$gridField->getConfig()->addComponent(new GridFieldTitleHeader());
fix the Sortable Header
if you wish to have the sort functionality, you will have to fix the routing.
It has been a while since I last used the GridField in frontend. I can only tell you that it did work at some time.
perhaps routing does not work because your form action (AllPages) is not accessible as URL, if that's the case, its pretty easy to fix: just add AllPages to your $allowed_actions of your Controller.
if the form is accessible, then its probably a bug in GridField, I would need to debug that to tell you any more. If that is the case, please reply via comment or contact me on IRC and I will take a look at it.
UPDATE: I just answered another frontend GridField question, and went a bit more in depth. perhaps this is also helpful to you: https://stackoverflow.com/a/22433159/1119263 (see Option 2)
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.
I have a few general questions about modifying Magento's admin section and would be grateful to have them answered. I'm new to Magento so please bear with me.
My goal is to add a new column with a product attribute (e.g. "Size") to the "Category Products" table within the Catalog -> Manage Cateories section (see screenshot below).
Having little Magento dev experience, I'm not quite sure where to start. I had a look in some of the Grid.php files under the adminhtml directory, and while I see a bunch of statements like addColumn(...), I'm not sure where I'd slot in my new attribute column.
Also, I assume that instead of modifying any core files directly, I'd copy them to the same path under the local folder and edit or somehow extend them there? Do I have to edit any config files or do anything else for the change to be reflected? Am I - by doing this - in effect creating my own module?
I also read that I should disable "Compilation" before I make any changes. Why is this? Is there anything else to consider?
Again I am very grateful for any help and appreciate that my questions must seem basic. Any supplementary resources you could point me towards would be appreciated. Thanks.
Indeed you should start by understanding what file to edit and how to edit it. In this case you want to modify app/code/core/Mage/Adminhtml/Block/Catalog/Category/Tab/Product.php but, like you said, you should not modify the file in its current location. There are two ways to modify the file in the "correct" way.
(harder but more extensible) Create a new Module in local and tell Magento in the etc/config.xml that you are overwriting that Block (which is just a php class) with a different block in this new Module and have the new class extend the core Block class. Then you just need to overwrite one function (_prepareColumns).
(easier) Copy the file from app/code/core/Mage/Adminhtml/Block/Catalog/Category/Tab/Product.php to app/code/local/Mage/Adminhtml/Block/Catalog/Category/Tab/Product.php and modify the function you want (_prepareColumns)
If you are new to Magento, I recommend going with the second option because its easier. Magento will always load the file from local before it loads from core so the file in core will no longer be used and your version in local will be used. To find out more, read this article from Alan Storm
Now in order to add the column you want, do something similar to the SKU field
$this->addColumn('size', array(
'header' => Mage::helper('catalog')->__('Size'),
'index' => 'size'
));
in the order you want it (between Product Name and SKU). I am assuming that your Products have a field called size that you can retreive with $product->getSize()
Max solution was pretty spot on but missing some important steps, I'll elaborate on his original method
Create a new local override of the Product Tab by copying app/code/core/Mage/Adminhtml/Block/Catalog/Category/Tab/Product.php to app/code/local/Mage/Adminhtml/Block/Catalog/Category/Tab/Product.php
There are 2 functions involved in modifying the grid view. _prepareCollection and _prepareColumns
_prepareColumns by adding a call to the addColumn function just like:
$this->addColumn('size', array(
'header' => Mage::helper('catalog')->__('Size'),
'width' => '80',
'index' => 'size'
));
_prepareCollection, by default the product collection loaded in the grid only has a few attributes(name,sku,price) what you need to do add our now attribute by ->addAttributeToSelect('size') now if you are only working with a textfield attribute then this is the extend of the modifications you have to do however if your attribute is for example a dropdown you will need to do further changes to the prepare collection:
(optional) dropdown attributes only store the value of the option that was select so we need to provide an options array to the addColumns call so Magento can display the values correctly, we can do that in the following maner:
on your local copy of Products, add the following to the _prepareColumns functions
$attribute = Mage::getModel('eav/config')->getAttribute('catalog_product', 'colour');
$options = array();
foreach( $attribute->getSource()->getAllOptions(true, true) as $option ) {
$options[$option['value']] = $option['label'];
}
$this->addColumn('colour', array(
'header' => Mage::helper('catalog')->__('Colour'),
'width' => '80',
'index' => 'colour',
'type' => 'options',
'options' => $options
));
While those are some very thorough question and I'm sure you will learn a lot, there is a ready-made solution; Enhanced Admin Product Grid has the ability to add arbitrary attributes as columns.
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.