As with many coding projects with Magento, it is very easy to get frustrated, even with the smallest of tasks. Well, Adminhtml Grids in Magento aren't too much fun, even for the experienced programmer. Lacking quality tutorial posts on Google as well, I thought it was worthwhile to write up a constructed tutorial on how to create these Adminhtml Grids, in the most simplified manner possible.
Note: This article isn't for the faint of heart -- this is one of the more confusing Magento topics. If you find yourself not getting anywhere, please go back through all of the comments in the XML files below and make sure they all match appropriately to your module.
Let's go ahead and get started by creating your base module definition file and skeleton structure.
app/etc/modules/Foo_Bar.xml
1
2
3
4
5
6
7
8
9
|
<?xmlversion="1.0"?>
<config>
<modules>
<Foo_Bar>
<active>true</active>
<codePool>community</codePool>
</Foo_Bar>
</modules>
</config>
|
app/code/community/Foo/Bar/etc/config.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
|
<?xmlversion="1.0"?>
<config>
<modules>
<Foo_Bar>
<version>1.0.0</version>
</Foo_Bar>
</modules>
<global>
<helpers>
<foo_bar>
<!-- This is where we define our helper directory -->
<class>Foo_Bar_Helper</class>
</foo_bar>
</helpers>
<blocks>
<foo_bar>
<!-- Set a block definition and lookup directory -->
<class>Foo_Bar_Block</class>
</foo_bar>
</blocks>
<models>
<foo_bar>
<!-- This is where we define our model directory -->
<class>Foo_Bar_Model</class>
<!-- Define a resource to create a custom table -->
<resourceModel>foo_bar_mysql4</resourceModel>
</foo_bar>
<!-- Here's our resource model we'll use to create a database table -->
<foo_bar_mysql4>
<class>Foo_Bar_Model_Mysql4</class>
<entities>
<!-- Let's define our table, we'll call it with the baz name, but the real table is foo_bar_baz -->
<!-- After we define our entity, we can call it with our model by calling foo_bar/baz -->
<baz>
<table>foo_bar_baz</table>
</baz>
</entities>
</foo_bar_mysql4>
</models>
<!-- And finally we define our resource setup script -->
<resources>
<foo_bar_setup>
<setup>
<module>Foo_Bar</module>
</setup>
</foo_bar_setup>
</resources>
</global>
<admin>
<routers>
<adminhtml>
<args>
<!-- This is how we load our Adminhtml controllers -->
<modules>
<Foo_Barbefore="Mage_Adminhtml">Foo_Bar_Adminhtml</Foo_Bar>
</modules>
</args>
</adminhtml>
</routers>
</admin>
<adminhtml>
<layout>
<updates>
<foo_bar>
<!--
We again keep a nice naming convention and make our module upgrade proof by placing it in a separate folder
- Since we are in the adminhtml node, this will look for the XML file in the app/design/adminhtml/default/default root folder
-->
<file>foo/bar.xml</file>
</foo_bar>
</updates>
</layout>
</adminhtml>
</config>
|
Now, we can create our Adminhtml XML which defines where our URL shows up in the Admin menu and access control.
app/code/community/Foo/Bar/etc/adminhtml.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
<?xmlversion="1.0"?>
<config>
<menu>
<!--
This item will be created in the Admin menu under Sales
- If you want another section, reference the appropriate adminhtml.xml file in app/code/core/Mage/Modulename/etc
- For example, we found out this was 'sales' by referencing the config/menu node of app/code/core/Mage/Sales/etc/adminhtml.xml
-->
<sales>
<children>
<!-- Here, I like to use the namespacename_modulename_controllername naming convention -->
<foo_bar_baztranslate="title"module="foo_bar">
<!-- This is how the menu text will be displayed -->
<title>Baz</title>
<!-- This is the URL of what we want the menu item to link to -->
<action>adminhtml/baz</action>
</foo_bar_baz>
</children>
</sales>
</menu>
<acl>
<resources>
<admin>
<children>
<!-- Same as above, but instead of referencing the config/menu node, you reference the acl/resources node of adminhtml.xml -->
<sales>
<children>
<!-- Keep the same naming convention as above -->
<foo_bar_baz>
<!-- This is how the ACL text will be displayed on System > Permissions > Roles > Role > Role Resources -->
<title>Baz</title>
</foo_bar_baz>
</children>
</sales>
</children>
</admin>
</resources>
</acl>
</config>
|
app/design/adminhtml/default/default/layout/foo/bar.xml
1
2
3
4
5
6
7
8
9
10
|
<?xmlversion="1.0"?>
<layout>
<!-- Here, we reference the XML node path of our route -->
<adminhtml_baz_index>
<referencename="content">
<!-- We also reference our block by namespacename_modulename/adminhtml_controllername, and name it uniquely -->
<blocktype="foo_bar/adminhtml_baz"name="foo_bar_baz"/>
</reference>
</adminhtml_baz_index>
</layout>
|
We need to create a php file for our helper. There won't be anything in it, but we still need it there to properly load the helper. The name of the default helper is Data, so we will create just that.
app/code/community/Foo/Bar/Helper/Data.php
1
2
3
4
|
<?php
classFoo_Bar_Helper_DataextendsMage_Core_Helper_Abstract
{
}
|
Our grid needs some data, so let's go ahead and setup a very small database table with just an ID and Name fields.
app/code/community/Foo/Bar/sql/foo_bar_setup/mysql4-install-1.0.0.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
<?php
/* @var $installer Mage_Core_Model_Resource_Setup */
$installer=$this;
$installer->startSetup();
/**
* Create table 'foo_bar_baz'
*/
$table=$installer->getConnection()
// The following call to getTable('foo_bar/baz') will lookup the resource for foo_bar (foo_bar_mysql4), and look
// for a corresponding entity called baz. The table name in the XML is foo_bar_baz, so ths is what is created.
->newTable($installer->getTable('foo_bar/baz'))
->addColumn('id', Varien_Db_Ddl_Table::TYPE_INTEGER, null,array(
'identity' => true,
'unsigned' => true,
'nullable' => false,
'primary' => true,
),'ID')
->addColumn('name', Varien_Db_Ddl_Table::TYPE_CLOB, 0,array(
'nullable' => false,
),'Name');
$installer->getConnection()->createTable($table);
$installer->endSetup();
|
Now, the first time you clear your cache and Magento sees your module at version 1.0.0, it will execute the above sql script and create your foo_bar_baz table.
Now that our foo_bar_baz table has been created, we'll create a base model, then a resource and tie it into our collection.
app/code/community/Foo/Bar/Model/Baz.php
1
2
3
4
5
6
7
8
|
<?php
classFoo_Bar_Model_BazextendsMage_Core_Model_Abstract
{
protectedfunction_construct()
{
$this->_init('foo_bar/baz');
}
}
|
app/code/community/Foo/Bar/Model/Mysql4/Baz.php
1
2
3
4
5
6
7
8
|
<?php
classFoo_Bar_Model_Mysql4_BazextendsMage_Core_Model_Mysql4_Abstract
{
protectedfunction_construct()
{
$this->_init('foo_bar/baz','id');
}
}
|
app/code/community/Foo/Bar/Model/Mysql4/Baz/Collection.php
1
2
3
4
5
6
7
8
|
<?php
classFoo_Bar_Model_Mysql4_Baz_CollectionextendsMage_Core_Model_Mysql4_Collection_Abstract
{
protectedfunction_construct()
{
$this->_init('foo_bar/baz');
}
}
|
That's all she wrote for the XML of Adminhtml Grids, so now we are onto some PHP. Let's move on to creating our grid block and controller.
First, we'll setup our block grid container, which will kick off our grid rendering within Magento. Remembering how Magento works,when we call our block as foo_bar/adminhtml_baz, it will automatically be looking for a file at Foo/Bar/Block/Adminhtml/Baz. We will also extend Magento's block widget grid container.
app/code/community/Foo/Bar/Block/Adminhtml/Baz.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
<?php
classFoo_Bar_Block_Adminhtml_BazextendsMage_Adminhtml_Block_Widget_Grid_Container
{
publicfunction__construct()
{
// The blockGroup must match the first half of how we call the block, and controller matches the second half
// ie. foo_bar/adminhtml_baz
$this->_blockGroup ='foo_bar';
$this->_controller ='adminhtml_baz';
$this->_headerText =$this->__('Baz');
parent::__construct();
}
}
|
After the grid container is setup, we create the class housing our actual grid code, which is almost at the same place as the container, just a directory deeper, and with the file named Grid.php, and extending Mage_Admihtml_Block_Widget_Grid.
app/code/community/Foo/Bar/Block/Adminhtml/Baz/Grid.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
|
<?php
classFoo_Bar_Block_Adminhtml_Baz_GridextendsMage_Adminhtml_Block_Widget_Grid
{
publicfunction__construct()
{
parent::__construct();
// Set some defaults for our grid
$this->setDefaultSort('id');
$this->setId('foo_bar_baz_grid');
$this->setDefaultDir('asc');
$this->setSaveParametersInSession(true);
}
protectedfunction_getCollectionClass()
{
// This is the model we are using for the grid
return'foo_bar/baz_collection';
}
protectedfunction_prepareCollection()
{
// Get and set our collection for the grid
$collection= Mage::getResourceModel($this->_getCollectionClass());
$this->setCollection($collection);
returnparent::_prepareCollection();
}
protectedfunction_prepareColumns()
{
// Add the columns that should appear in the grid
$this->addColumn('id',
array(
'header'=>$this->__('ID'),
'align'=>'right',
'width'=>'50px',
'index'=>'id'
)
);
$this->addColumn('name',
array(
'header'=>$this->__('Name'),
'index'=>'name'
)
);
returnparent::_prepareColumns();
}
publicfunctiongetRowUrl($row)
{
// This is where our row data will link to
return$this->getUrl('*/*/edit',array('id'=>$row->getId()));
}
}
|
And also create an Edit.php file along with an Edit/Form.php file, which both control how the edit form loads and is displayed.</p>
app/code/community/Foo/Bar/Block/Adminhtml/Baz/Edit.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
<?php
classFoo_Bar_Block_Adminhtml_Baz_EditextendsMage_Adminhtml_Block_Widget_Form_Container
{
/**
* Init class
*/
publicfunction__construct()
{
$this->_blockGroup ='foo_bar';
$this->_controller ='adminhtml_baz';
parent::__construct();
$this->_updateButton('save','label',$this->__('Save Baz'));
$this->_updateButton('delete','label',$this->__('Delete Baz'));
}
/**
* Get Header text
*
* @return string
*/
publicfunctiongetHeaderText()
{
if(Mage::registry('foo_bar')->getId()) {
return$this->__('Edit Baz');
}
else{
return$this->__('New Baz');
}
}
}
|
app/code/community/Foo/Bar/Block/Adminhtml/Baz/Edit/Form.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
|
<?php
classFoo_Bar_Block_Adminhtml_Baz_Edit_FormextendsMage_Adminhtml_Block_Widget_Form
{
/**
* Init class
*/
publicfunction__construct()
{
parent::__construct();
$this->setId('foo_bar_baz_form');
$this->setTitle($this->__('Baz Information'));
}
/**
* Setup form fields for inserts/updates
*
* return Mage_Adminhtml_Block_Widget_Form
*/
protectedfunction_prepareForm()
{
$model= Mage::registry('foo_bar');
$form=newVarien_Data_Form(array(
'id' =>'edit_form',
'action' =>$this->getUrl('*/*/save',array('id'=>$this->getRequest()->getParam('id'))),
'method' =>'post'
));
$fieldset=$form->addFieldset('base_fieldset',array(
'legend' => Mage::helper('checkout')->__('Baz Information'),
'class' =>'fieldset-wide',
));
if($model->getId()) {
$fieldset->addField('id','hidden',array(
'name'=>'id',
));
}
$fieldset->addField('name','text',array(
'name' =>'name',
'label' => Mage::helper('checkout')->__('Name'),
'title' => Mage::helper('checkout')->__('Name'),
'required' => true,
));
$form->setValues($model->getData());
$form->setUseContainer(true);
$this->setForm($form);
returnparent::_prepareForm();
}
}
|
Now, our admin routing was setup before, which told Magento to look in Packagename/Modulename/controllers/Adminhtml for our controller. So, we just create a controller just as we would for the frontend, just in this directory instead, and also extend Mage_Adminhtml_Controller_Action instead of Mage_Core_Controller_Front_Action.
app/code/community/Foo/Bar/controllers/Adminhtml/BazController.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
|
<?php
classFoo_Bar_Adminhtml_BazControllerextendsMage_Adminhtml_Controller_Action
{
publicfunctionindexAction()
{
// Let's call our initAction method which will set some basic params for each action
$this->_initAction()
->renderLayout();
}
publicfunctionnewAction()
{
// We just forward the new action to a blank edit form
$this->_forward('edit');
}
publicfunctioneditAction()
{
$this->_initAction();
// Get id if available
$id =$this->getRequest()->getParam('id');
$model= Mage::getModel('foo_bar/baz');
if($id) {
// Load record
$model->load($id);
// Check if record is loaded
if(!$model->getId()) {
Mage::getSingleton('adminhtml/session')->addError($this->__('This baz no longer exists.'));
$this->_redirect('*/*/');
return;
}
}
$this->_title($model->getId() ?$model->getName() :$this->__('New Baz'));
$data= Mage::getSingleton('adminhtml/session')->getBazData(true);
if(!empty($data)) {
$model->setData($data);
}
Mage::register('foo_bar',$model);
$this->_initAction()
->_addBreadcrumb($id?$this->__('Edit Baz') :$this->__('New Baz'),$id?$this->__('Edit Baz') :$this->__('New Baz'))
->_addContent($this->getLayout()->createBlock('foo_bar/adminhtml_baz_edit')->setData('action',$this->getUrl('*/*/save')))
->renderLayout();
}
publicfunctionsaveAction()
{
if($postData=$this->getRequest()->getPost()) {
$model= Mage::getSingleton('foo_bar/baz');
$model->setData($postData);
try{
$model->save();
Mage::getSingleton('adminhtml/session')->addSuccess($this->__('The baz has been saved.'));
$this->_redirect('*/*/');
return;
}
catch(Mage_Core_Exception$e) {
Mage::getSingleton('adminhtml/session')->addError($e->getMessage());
}
catch(Exception$e) {
Mage::getSingleton('adminhtml/session')->addError($this->__('An error occurred while saving this baz.'));
}
Mage::getSingleton('adminhtml/session')->setBazData($postData);
$this->_redirectReferer();
}
}
publicfunctionmessageAction()
{
$data= Mage::getModel('foo_bar/baz')->load($this->getRequest()->getParam('id'));
echo$data->getContent();
}
/**
* Initialize action
*
* Here, we set the breadcrumbsandthe active menu
*
* @returnMage_Adminhtml_Controller_Action
*/
protectedfunction_initAction()
{
$this->loadLayout()
// Make the active menu match the menu config nodes (without 'children' inbetween)
->_setActiveMenu('sales/foo_bar_baz')
->_title($this->__('Sales'))->_title($this->__('Baz'))
->_addBreadcrumb($this->__('Sales'),$this->__('Sales'))
->_addBreadcrumb($this->__('Baz'),$this->__('Baz'));
return$this;
}
/**
* Check currently called action by permissions for current user
*
* @return bool
*/
protectedfunction_isAllowed()
{
returnMage::getSingleton('admin/session')->isAllowed('sales/foo_bar_baz');
}
}
|
Yes thank you...It's not easy to find current information on this...
I've read a few tutorials about this and it's very easy to get lost. This is the best I've seen so far and will definitely be giving it a go.
Yeah, big thanks for this ! Really appreciate you making this public for fellow-magentoers to see.
Hi, thanks for the post and I am able to create a admin html menu form with single sub menu but what should i do to include more than one sub menu.
It's not work for me.
I'm certainly doing something wrong... but I don't know "where".
Can you share an .rar file of the extension?
Thanks!
"Fatal error: Class 'Foo_Bar_Helper_Data' not found "... :(
Thanks you, it's work know :)
Almost... I can see the grid on the admin section (add : OK). It's not very important because it's not the goal of the extension. I know now, thanks to you, how to create a magento admin section with the current "admin" controller.
I can't seem to have my Grids showing on my page. I think something's wrong with pointing it into a database table. Ive done exactly how you explained in models folder. I also have created the database table in my localhost. The only thing that is displayed is the headertext. below that is blank. The Grids aren't showing. HEEEEEEEEEEELP :(
Help please :( I really get confused with the files...
The grids aren't showing up.. Only the headers...
Block\
--Adminhtml\
----Manage.php
etc\
--config.xml
Model\
--Manage\
----Manage\
------Collection.php
----Manage.php
--Manage.php
BLOCK FILE
==========
class Mage_Affiliate_Block_Adminhtml_Manage extends Mage_Adminhtml_Block_Widget_Grid_Container
{
public function __construct()
{
$this->_controller = 'adminhtml_manage';
$this->_blockGroup = 'affiliate';
$this->_headerText = Mage::helper('affiliate')->__('Manage Users');
parent::__construct();
}
}
1. etc\config.xml
Mage_Affiliate_Model
affiliate_manage
Mage_Affiliate_Model_Manage
2. Model\Manage.php
class Mage_Affiliate_Model_Manage extends Mage_Core_Model_Abstract
{
public function _construct()
{
parent::_construct();
$this->_init('affiliate/manage');
}
}
3. Model\Manage\Manage.php
class Mage_Affiliate_Model_Manage_Manage extends Mage_Core_Model_Resource_Db_Abstract{
protected function _construct()
{
$this->_init('affiliate/manage', 'affiliatemanage_id'); //affiliatemanage_id is my PK in my affiliatemanage database table, declared in config.xml
}
}
4. Model\Manage\Manage\Collection.php
class Mage_Affiliate_Model_Manage_Manage_Collection extends Mage_Core_Model_Mysql4_Collection_Abstract
{
public function _construct()
{
parent::_construct();
$this->_init('affiliate/manage');
}
}
** assuming parentheses are greater than and less thans..
1. etc\config.xml
(global)
(models)
(affiliate)
(class)Mage_Affiliate_Model(/class)
(resourceModel)affiliate_manage(/resourceModel)
(/affiliate)
(affiliate_manage)
(class)Mage_Affiliate_Model_Manage(/class)
(entities)
(manage)
(table)affiliatemanage(/table)
(/manage)
(/entities)
(/affiliate_manage)
(/models)
(/global)
var\log\system.txt
2012-08-01T15:08:25+00:00 ERR (3): Warning: include() [function.include]: Failed opening 'Mage\Affiliate\Model\Resource\Manage\Collection.php' for inclusion (include_path='C:\xampp\htdocs\svn_files\trunk\app\code\local;C:\xampp\htdocs\svn_files\trunk\app\code\community;C:\xampp\htdocs\svn_files\trunk\app\code\core;C:\xampp\htdocs\svn_files\trunk\lib;.;C:\xampp\php\PEAR') in C:\xampp\htdocs\svn_files\trunk\lib\Varien\Autoload.php on line 93
Please take your time and re-read the article again. You are missing some things, notably app/code/community/Foo/Bar/Model/Mysql4/Baz/Collection.php (note the Mysql4 folder). The post was just for reference and is confirmed working, if something isn't working you need to just re-read the article because you are doing something wrong. Errors? You're a dev, figure it out.
There is an error in Grid.php... this is correct...
protected function _getCollectionClass()
{
// This is the model we are using for the grid
return 'foo_bar/baz_collection';
}
Dear markoshust
Thank you for writing this example, I can now work. But in fact, the example there are two problems, one is the Xanti resolved _getCollectionClass of (), the other is the file name of the class Foo_Bar_Block_Adminhtml_Baz_Edit_Form incorrect (although you have suggested in paragraph), want to be able to update
Thank you
Thank you for this great tutorial, but i want to point out this:foo_bar_mysql4 is depracated, use instead foo_bar_resource(folder resource instead of folder mysql4)
Any chance of getting a zip added to the article for downloading these files?
It would make bootstrapping into learning this a lot simpler! :-)
Working copy of this articles module:
http://redesigned.com/misc/foobarbaz_module_example.zip
(note i did not clean up the formatting any, this is exactly as it appears here with the one change the the module is in the local instead of the community folder, which is more standard for manually installed modules such as this.)
Hi,
I copied every file verbatium into a clean install of Magento 1.7.0.2
The menu appears, but when i click on the Baz menu item, I get an admin page (header, nav, footer) with no content.
Any ideas what is wrong?
Thanks!
-Josh