I have created a Custom Grid. I have one table with grid generated. I
successfully added the tab in my custom grid.But I am a little bit confused on how to add Custom Tab in "Product Edit Page"
and in Custom Tab how to display Custom Model Grid.Please guide me.
Magento – How to add Custom Model Grid Into Custom Tab in Product Edit page in admin panel in magento
admin-panelgridmagento-1.8producttabs
Related Solutions
Assuming you already have your entity and a grid for it in the backend here is what you need.
In my example, my entity is called article
and the module is Easylife_Press
. Adjust the names to fit your module.
First you need a new table to remember the relation between products and your entities. For this add in your config.xml
the table declaration inside the resource model tag. (models->press->press_resource->entities
)
<article_product><!-- relation table -->
<table>press_article_product</table>
</article_product>
Now you need to create the table. In one of your upgrade scripts add this:
$this->run("
CREATE TABLE {$this->getTable('press/article_product')} (
`rel_id` int(11) unsigned NOT NULL auto_increment,
`article_id` int(11) unsigned NOT NULL,
`product_id` int(11) unsigned NOT NULL,
`position` int(11) unsigned NOT NULL default '0',
PRIMARY KEY (`rel_id`),
UNIQUE KEY `UNIQUE_ARTICLE_PRODUCT` (`article_id`,`product_id`),
CONSTRAINT `PRESS_ARTICLE_ARTICLE_PRODUCT` FOREIGN KEY (`article_id`) REFERENCES {$this->getTable('press/article')} (`entity_id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `PRESS_ARTICLE_PRODUCT_ARTICLE` FOREIGN KEY (`product_id`) REFERENCES {$this->getTable('catalog_product_entity')} (`entity_id`) ON DELETE CASCADE ON UPDATE CASCADE
)
ENGINE=InnoDB DEFAULT CHARSET=utf8;
");
If the PK of your entity table is not entity_id
change it. Sorry that I don't have the DDL install script. It's easier to understand this way. If you want I will write one.
Now you need a model to handle this table.
app/code/local/Easylife/Press/Model/Article/Product.php
<?php
class Easylife_Press_Model_Article_Product extends Mage_Core_Model_Abstract{
protected function _construct(){
$this->_init('press/article_product');
}
/**
* Save data for article-product relation
* @access public
* @param Easylife_Press_Model_Article $article
* @return Easylife_Press_Model_Article_Product
*
*/
public function saveArticleRelation($article){
$data = $article->getProductsData();
if (!is_null($data)) {
$this->_getResource()->saveArticleRelation($article, $data);
}
return $this;
}
/**
* get products for article
* @access public
* @param Easylife_Press_Model_Article $article
* @return Easylife_Press_Model_Resource_Article_Product_Collection
*
*/
public function getProductCollection($article){
$collection = Mage::getResourceModel('press/article_product_collection')
->addArticleFilter($article);
return $collection;
}
}
And a model that maps on a table requires a resource model.
app/code/local/Easylife/Press/Model/Resource/Article/Product.php
<?php
class Easylife_Press_Model_Resource_Article_Product extends Mage_Core_Model_Resource_Db_Abstract{
/**
* initialize resource model
* @access protected
* @return void
* @see Mage_Core_Model_Resource_Abstract::_construct()
*
*/
protected function _construct(){
$this->_init('press/article_product', 'rel_id');
}
/**
* Save article - product relations
* @access public
* @param Easylife_Press_Model_Article $article
* @param array $data
* @return Easylife_Press_Model_Resource_Article_Product
*
*/
public function saveArticleRelation($article, $data){
if (!is_array($data)) {
$data = array();
}
$deleteCondition = $this->_getWriteAdapter()->quoteInto('article_id=?', $article->getId());
$this->_getWriteAdapter()->delete($this->getMainTable(), $deleteCondition);
foreach ($data as $productId => $info) {
$this->_getWriteAdapter()->insert($this->getMainTable(), array(
'article_id' => $article->getId(),
'product_id' => $productId,
'position' => @$info['position']
));
}
return $this;
}
/**
* Save product - article relations
* @access public
* @param Mage_Catalog_Model_Product $prooduct
* @param array $data
* @return Easylife_Press_Model_Resource_Article_Product
* @
*/
public function saveProductRelation($product, $data){
if (!is_array($data)) {
$data = array();
}
$deleteCondition = $this->_getWriteAdapter()->quoteInto('product_id=?', $product->getId());
$this->_getWriteAdapter()->delete($this->getMainTable(), $deleteCondition);
foreach ($data as $articleId => $info) {
$this->_getWriteAdapter()->insert($this->getMainTable(), array(
'article_id' => $articleId,
'product_id' => $product->getId(),
'position' => @$info['position']
));
}
return $this;
}
}
...and a collection resource model:
app/code/local/Easylife/Press/Model/Resource/Article/Product/Collection.php
<?php
class Easylife_Press_Model_Resource_Article_Product_Collection extends Mage_Catalog_Model_Resource_Product_Collection{
/**
* remember if fields have been joined
* @var bool
*/
protected $_joinedFields = false;
/**
* join the link table
* @access public
* @return Easylife_Press_Model_Resource_Article_Product_Collection
*
*/
public function joinFields(){
if (!$this->_joinedFields){
$this->getSelect()->join(
array('related' => $this->getTable('press/article_product')),
'related.product_id = e.entity_id',
array('position')
);
$this->_joinedFields = true;
}
return $this;
}
/**
* add article filter
* @access public
* @param Easylife_Press_Model_Article | int $article
* @return Easylife_Press_Model_Resource_Article_Product_Collection
*
*/
public function addArticleFilter($article){
if ($article instanceof Easylife_Press_Model_Article){
$article = $article->getId();
}
if (!$this->_joinedFields){
$this->joinFields();
}
$this->getSelect()->where('related.article_id = ?', $article);
return $this;
}
}
Then you need to add a new tab in the product add/edit form and save the values from it. For this you need to observe the event core_block_abstract_prepare_layout_after
. Add the following to your config.xml
file inside the <adminhtml>
tag.
<events>
<core_block_abstract_prepare_layout_after> <!-- this will add the tab-->
<observers>
<article>
<type>singleton</type>
<class>press/adminhtml_observer</class>
<method>addArticleBlock</method>
</article>
</observers>
</core_block_abstract_prepare_layout_after>
<catalog_product_save_after><!-- this will save the relations -->
<observers>
<article>
<type>singleton</type>
<class>press/adminhtml_observer</class>
<method>saveArticleData</method>
</article>
</observers>
</catalog_product_save_after>
</events>
Now the observer that adds the tab and saves the relations
app/code/local/Easylife/Press/Model/Adminhtml/Observer.php
<?php
class Easylife_Press_Model_Adminhtml_Observer{
/**
* check if tab can be added
* @access protected
* @param Mage_Catalog_Model_Product $product
* @return bool
*
*/
protected function _canAddTab($product){
if ($product->getId()){
return true;
}
if (!$product->getAttributeSetId()){
return false;
}
$request = Mage::app()->getRequest();
if ($request->getParam('type') == 'configurable'){
if ($request->getParam('attribtues')){
return true;
}
}
return false;
}
/**
* add the article tab to products
* @access public
* @param Varien_Event_Observer $observer
* @return Easylife_Press_Model_Adminhtml_Observer
*
*/
public function addArticleBlock($observer){
$block = $observer->getEvent()->getBlock();
$product = Mage::registry('product');
if ($block instanceof Mage_Adminhtml_Block_Catalog_Product_Edit_Tabs && $this->_canAddTab($product)){
$block->addTab('articles', array(
'label' => Mage::helper('press')->__('Articles'),
'url' => Mage::helper('adminhtml')->getUrl('adminhtml/press_article_catalog_product/articles', array('_current' => true)),
'class' => 'ajax',
));
}
return $this;
}
/**
* save article - product relation
* @access public
* @param Varien_Event_Observer $observer
* @return Easylife_Press_Model_Adminhtml_Observer
*
*/
public function saveArticleData($observer){
$post = Mage::app()->getRequest()->getPost('articles', -1);
if ($post != '-1') {
$post = Mage::helper('adminhtml/js')->decodeGridSerializedInput($post);
$product = Mage::registry('product');
$articleProduct = Mage::getResourceSingleton('press/article_product')->saveProductRelation($product, $post);
}
return $this;
}
}
One helper to make it easier to get the related entities
app/code/local/Easylife/Press/Helper/Product.php
:
<?php
class Easylife_Press_Helper_Product extends Easylife_Press_Helper_Data{
/**
* get the selected articles for a product
* @access public
* @param Mage_Catalog_Model_Product $product
* @return array()
*
*/
public function getSelectedArticles(Mage_Catalog_Model_Product $product){
if (!$product->hasSelectedArticles()) {
$articles = array();
foreach ($this->getSelectedArticlesCollection($product) as $article) {
$articles[] = $article;
}
$product->setSelectedArticles($articles);
}
return $product->getData('selected_articles');
}
/**
* get article collection for a product
* @access public
* @param Mage_Catalog_Model_Product $product
* @return Easylife_Press_Model_Resource_Article_Collection
*/
public function getSelectedArticlesCollection(Mage_Catalog_Model_Product $product){
$collection = Mage::getResourceSingleton('press/article_collection')
->addProductFilter($product);
return $collection;
}
}
Now the actual grid block.
app/code/local/Easylife/Press/Block/Adminhtml/Catalog/Product/Edit/Tab/Article.php
<?php
class Easylife_Press_Block_Adminhtml_Catalog_Product_Edit_Tab_Article extends Mage_Adminhtml_Block_Widget_Grid {
/**
* Set grid params
* @access protected
* @return void
*
*/
public function __construct(){
parent::__construct();
$this->setId('article_grid');
$this->setDefaultSort('position');
$this->setDefaultDir('ASC');
$this->setUseAjax(true);
if ($this->getProduct()->getId()) {
$this->setDefaultFilter(array('in_articles'=>1));
}
}
/**
* prepare the article collection
* @access protected
* @return Easylife_Press_Block_Adminhtml_Catalog_Product_Edit_Tab_Article
*
*/
protected function _prepareCollection() {
$collection = Mage::getResourceModel('press/article_collection');
if ($this->getProduct()->getId()){
$constraint = 'related.product_id='.$this->getProduct()->getId();
}
else{
$constraint = 'related.product_id=0';
}
$collection->getSelect()->joinLeft(
array('related'=>$collection->getTable('press/article_product')),
'related.article_id=main_table.entity_id AND '.$constraint,
array('position')
);
$this->setCollection($collection);
parent::_prepareCollection();
return $this;
}
/**
* prepare mass action grid
* @access protected
* @return Easylife_Press_Block_Adminhtml_Catalog_Product_Edit_Tab_Article
*
*/
protected function _prepareMassaction(){
return $this;
}
/**
* prepare the grid columns
* @access protected
* @return Easylife_Press_Block_Adminhtml_Catalog_Product_Edit_Tab_Article
*
*/
protected function _prepareColumns(){
$this->addColumn('in_articles', array(
'header_css_class' => 'a-center',
'type' => 'checkbox',
'name' => 'in_articles',
'values'=> $this->_getSelectedArticles(),
'align' => 'center',
'index' => 'entity_id'
));
$this->addColumn('title', array(
'header'=> Mage::helper('press')->__('Title'),
'align' => 'left',
'index' => 'title',
));
$this->addColumn('position', array(
'header' => Mage::helper('press')->__('Position'),
'name' => 'position',
'width' => 60,
'type' => 'number',
'validate_class'=> 'validate-number',
'index' => 'position',
'editable' => true,
));
}
/**
* Retrieve selected articles
* @access protected
* @return array
*
*/
protected function _getSelectedArticles(){
$articles = $this->getProductArticles();
if (!is_array($articles)) {
$articles = array_keys($this->getSelectedArticles());
}
return $articles;
}
/**
* Retrieve selected articles
* @access protected
* @return array
*
*/
public function getSelectedArticles() {
$articles = array();
//used helper here in order not to override the product model
$selected = Mage::helper('press/product')->getSelectedArticles(Mage::registry('current_product'));
if (!is_array($selected)){
$selected = array();
}
foreach ($selected as $article) {
$articles[$article->getId()] = array('position' => $article->getPosition());
}
return $articles;
}
/**
* get row url
* @access public
* @return string
*
*/
public function getRowUrl($item){
return '#';
}
/**
* get grid url
* @access public
* @return string
*
*/
public function getGridUrl(){
return $this->getUrl('*/*/articlesGrid', array(
'id'=>$this->getProduct()->getId()
));
}
/**
* get the current product
* @access public
* @return Mage_Catalog_Model_Product
*
*/
public function getProduct(){
return Mage::registry('current_product');
}
/**
* Add filter
* @access protected
* @param object $column
* @return Easylife_Press_Block_Adminhtml_Catalog_Product_Edit_Tab_Article
*
*/
protected function _addColumnFilterToCollection($column){
if ($column->getId() == 'in_articles') {
$articleIds = $this->_getSelectedArticles();
if (empty($articleIds)) {
$articleIds = 0;
}
if ($column->getFilter()->getValue()) {
$this->getCollection()->addFieldToFilter('entity_id', array('in'=>$articleIds));
}
else {
if($articleIds) {
$this->getCollection()->addFieldToFilter('entity_id', array('nin'=>$articleIds));
}
}
}
else {
parent::_addColumnFilterToCollection($column);
}
return $this;
}
}
And last, a controller to handle the ajax requests for your custom grid.
app/code/local/Easylife/Press/controllers/Adminhtml/Press/Article/Catalog/ProductController.php
<?php
require_once ("Mage/Adminhtml/controllers/Catalog/ProductController.php");
class Easylife_Press_Adminhtml_Press_Article_Catalog_ProductController extends Mage_Adminhtml_Catalog_ProductController{
/**
* construct
* @access protected
* @return void
*
*/
protected function _construct(){
// Define module dependent translate
$this->setUsedModuleName('Easylife_Press');
}
/**
* articles in the catalog page
* @access public
* @return void
*
*/
public function articlesAction(){
$this->_initProduct();
$this->loadLayout();
$this->getLayout()->getBlock('product.edit.tab.article')
->setProductArticles($this->getRequest()->getPost('product_articles', null));
$this->renderLayout();
}
/**
* articles grid in the catalog page
* @access public
* @return void
*
*/
public function articlesGridAction(){
$this->_initProduct();
$this->loadLayout();
$this->getLayout()->getBlock('product.edit.tab.article')
->setProductArticles($this->getRequest()->getPost('product_articles', null));
$this->renderLayout();
}
}
Just kidding, that wasn't the last thing. You still need to define the layouts for the controller actions. In one of the admin layout files add this:
<adminhtml_press_article_catalog_product_articles>
<block type="core/text_list" name="root" output="toHtml">
<block type="press/adminhtml_catalog_product_edit_tab_article" name="product.edit.tab.article"/>
<block type="adminhtml/widget_grid_serializer" name="article_grid_serializer">
<reference name="article_grid_serializer">
<action method="initSerializerBlock">
<grid_block_name>product.edit.tab.article</grid_block_name>
<data_callback>getSelectedArticles</data_callback>
<hidden_input_name>articles</hidden_input_name>
<reload_param_name>product_articles</reload_param_name>
</action>
<action method="addColumnInputName">
<input_name>position</input_name>
</action>
</reference>
</block>
</block>
</adminhtml_press_article_catalog_product_articles>
<adminhtml_press_article_catalog_product_articlesgrid>
<block type="core/text_list" name="root" output="toHtml">
<block type="press/adminhtml_catalog_product_edit_tab_article" name="product.edit.tab.article"/>
</block>
</adminhtml_press_article_catalog_product_articlesgrid>
I hope I didn't forget any files. Clear the cache and give it a try.
Seams complicated. Well it is. But it can be easy. The code above was auto-generated by the Ultimate Module Creator extension. All you need to do with it is to configure the fields of your entity and say that you want a many to may relation with the products. I hope this is not considered self promotion, because the extension is free, you can get it directly from Magento connect, and it really solves this kind of problems.
Following I give some example which I have done before. Mine contains one edit form and one ajax grid, but with this logic, you can add any number of tabs and grids in this form. File structuire:
1./app/code/local/Ssd/Experts/Block/Adminhtml/Experts.php
/**
* Experts management Grid container, row click opents expert edit page
*/
class Ssd_Experts_Block_Adminhtml_Experts extends Mage_Adminhtml_Block_Widget_Grid_Container
{
public function _construct()
{
$this->_controller = 'adminhtml_experts';
$this->_blockGroup = 'experts';
$this->_headerText = Mage::helper('experts')->__('Manage experts');
$this->_addButtonLabel = Mage::helper('brands')->__('Add Expert');
parent::_construct();
}
}
2./app/code/local/Ssd/Experts/Block/Adminhtml/Experts/Grid.php
class Ssd_Experts_Block_Adminhtml_Experts_Grid extends Mage_Adminhtml_Block_Widget_Grid
{
public function __construct()
{
parent::__construct();
$this->setId('expertsGrid');
$this->setDefaultSort('name');
$this->setDefaultDir('ASC');
$this->setSaveParametersInSession(true);
}
protected function _prepareCollection()
{
$collection = Mage::getModel('experts/experts')->getCollection()->addCategoryData();
$this->setCollection($collection);
return parent::_prepareCollection();
}
protected function _prepareColumns()
{
$this->addColumn('expert_name', array(
'header' => Mage::helper('experts')->__('Name'),
'align' => 'left',
'index' => 'expert_name',
'width' => '250',
));
/**
* another columns here
*/
return parent::_prepareColumns();
}
}
3./app/code/local/Ssd/Experts/Block/Adminhtml/Experts/Edit.php
/**
* Expert edit form container
*/
class Ssd_Experts_Block_Adminhtml_Experts_Edit extends Mage_Adminhtml_Block_Widget_Form_Container
{
public function __construct()
{
parent::__construct();
$this->_objectId = 'id';
$this->_blockGroup = "experts";
$this->_controller = "adminhtml_experts";
$this->updateButton('delete', 'label', Mage::helper('experts')->__('Delete Expert'));
$this->updateButton('save', 'label', Mage::helper('experts')->__('Save Expert'));
}
}
4./app/code/local/Ssd/Experts/Block/Adminhtml/Experts/Edit/Tabs.php
class Ssd_Experts_Block_Adminhtml_Experts_Edit_Tabs extends Mage_Adminhtml_Block_Widget_Tabs
{
public function __construct()
{
parent::__construct();
$this->setId('experts_tab');
$this->setDestElementId('edit_form');
}
protected function _beforeToHtml()
{
//edit form tab
$this->addTab('expert_form_edit', array(
'label' => Mage::helper('brands')->__('Edit Expert'),
'title' => Mage::helper('brands')->__('Edit Expert'),
'content' => $this->getLayout()->createBlock('experts/adminhtml_experts_edit_tab_form')->toHtml(),
'active' => true
));
if (Mage::registry('experts_data') && Mage::registry('experts_data')->getId()) {
//grid tab
$this->addTab('expert_advices', array(
'label' => Mage::helper('experts')->__('Expert Advices'),
'title' => Mage::helper('experts')->__('Expert Advices'),
'content' => $this->getLayout()->createBlock('experts/adminhtml_experts_edit_tab_advices_grid')->toHtml(),
));
}
//also you can add another grid tab
/*$this->addTab('expert_advices', array(
'label' => Mage::helper('experts')->__('Another Grid'),
'title' => Mage::helper('experts')->__('Another Grid'),
'content' => $this->getLayout()->createBlock('experts/adminhtml_experts_edit_tab_another_grid')->toHtml(),
));*/
return parent::_beforeToHtml();
}
}
5./app/code/local/Ssd/Experts/Block/Adminhtml/Experts/Edit/Form.php
class Ssd_Experts_Block_Adminhtml_Experts_Edit_Form extends Mage_Adminhtml_Block_Widget_Form
{
protected function _prepareForm()
{
$form = new Varien_Data_Form(array(
'id' => 'edit_form',
'action' => $this->getUrl('*/*/save', array('id' => $this->getRequest()->getParam('id'))),
'method' => 'post',
'enctype' => 'multipart/form-data',
));
$form->setUseContainer(true);
$this->setForm($form);
return parent::_prepareForm();
}
}
6./app/code/local/Ssd/Experts/Block/Adminhtml/Experts/Edit/Tab/Form.php
class Ssd_Experts_Block_Adminhtml_Experts_Edit_Tab_Form extends Mage_Adminhtml_Block_Widget_Form
{
protected function _prepareForm()
{
$form = new Varien_Data_Form();
$fieldset = $form->addFieldset('personal_information', array(
'legend' => Mage::helper('experts')->__('Personal Information'),
'class' => 'fieldset-wide',
));
$fieldset->addField('expert_name', 'text', array(
'label' => Mage::helper('experts')->__('Expert Name'),
'required' => true,
'name' => 'expert_name',
));
/**
* another elements here
* this is typical edit form
*/
return parent::_prepareForm();
}
}
7./app/code/local/Ssd/Experts/Block/Adminhtml/Experts/Edit/Tab/Advices/Grid.php
/**
* this is typical grid, but it also impelents tab interface
*/
class Ssd_Experts_Block_Adminhtml_Experts_Edit_Tab_Advices_Grid extends Mage_Adminhtml_Block_Widget_Grid
implements Mage_Adminhtml_Block_Widget_Tab_Interface
{
public function _construct()
{
parent::_construct();
$this->setId('expert_advice_grid');
$this->setDefaultSort('page_title');
$this->setDefaultDir('ASC');
//enable ajax grid
$this->setUseAjax(true);
$this->setSaveParametersInSession(true);
}
protected function _prepareCollection()
{
$collection = Mage::getModel('experts/advices')
->getCollection()
->addExpertData()
->addFieldToFilter('main_table.expert_id', Mage::app()->getRequest()->getParam('id'));
$this->setCollection($collection);
return parent::_prepareCollection();
}
protected function _prepareColumns()
{
$this->addColumn('advice_title', array(
'header' => Mage::helper('experts')->__('Title'),
'align' => 'left',
'index' => 'advice_title',
));
/**
* add another columns here
*/
return parent::_prepareColumns();
}
//this method is reuired if you want ajax grid
public function getGridUrl()
{
return $this->getUrl('*/*/grid', array('_current' => true));
}
public function canShowTab()
{
return true;
}
public function isHidden()
{
return false;
}
public function getTabLabel()
{
return $this->__('Settings');
}
public function getTabTitle()
{
return $this->__('Expert Advices');
}
}
8./app/code/local/Ssd/Experts/Block/Adminhtml/Experts/Edit/Tab/Another/Grid.php
class Ssd_Experts_Block_Adminhtml_Experts_Edit_Tab_Another_Grid extends Mage_Adminhtml_Block_Widget_Grid
implements Mage_Adminhtml_Block_Widget_Tab_Interface
{
public function _construct()
{
parent::_construct();
$this->setId('expert_another_grid');
//enable ajax grid
$this->setUseAjax(true);
}
protected function _prepareCollection()
{
/**
* prepare collection what you want
*/
return parent::_prepareCollection();
}
protected function _prepareColumns()
{
/**
* add another columns here
*/
return parent::_prepareColumns();
}
//this method is reuired if you want ajax grid
public function getGridUrl()
{
return $this->getUrl('*/*/anothergrid', array('_current' => true));
}
/**
* implement tab interface methods
*/
}
9./app/code/local/Ssd/Experts/controllers/Adminhtml/ExpertsController.php
class Ssd_Experts_Adminhtml_ExpertsController extends Mage_Adminhtml_Controller_Action
{
//this is ajax grid request action
public function gridAction()
{
echo $this->getLayout()->createBlock('experts/adminhtml_experts_edit_tab_advices_grid')->toHtml();
}
public function editAction()
{
$expertModel = Mage::getModel('experts/experts')->load($this->getRequest()->getParam('id'));
if ($expertModel->getId()) {
Mage::register('experts_data', $expertModel);
$this->loadLayout();
//attention here, what blocks are creating, left is tab, content is edit container
$this->_addLeft($this->getLayout()->createBlock('experts/adminhtml_experts_edit_tabs'));
$this->_addContent($this->getLayout()->createBlock('experts/adminhtml_experts_edit'));
$this->renderLayout();
} else {
Mage::getSingleton('adminhtml/session')->addError(Mage::helper('experts')->__('Expert does not exist'));
$this->_redirect('*/*/');
}
}
/**
* another actions here: index, delete, mass....
*/
}
I hope you can understand file structure using class names. some codes are cutted out. My experts edit form contains edit form container, tabs, end each tab content is can be form controls, grid or any content what you want. Additional comments are in the code. Hope this example helps you.
Best Answer
So my understanding is you want to add a grid to the product edit/ new product page.
First thing you will need to do is add a adminhtml XML file to you modules config.xml if it does not allready have one.
in etc/config.xml
The in that layout file add to the reference "product_tabs" we need to call the addTab function as pass in our tabs name and the Block:
Note I don't know if there is a handle for new and edit products so I just created a custom handle and created it for both. If you wanted to just have this on all simple products you could have used
adminhtml_catalog_product_simple
For our new block to show it needs to implement Mage_Adminhtml_Block_Widget_Tab_Interface and have the functions getTabLabel,getTabTitle,canShowTab,isHidden
For simplicty I have just used the sitemap collection from Magento and copied some of that default code, this is similar to the default grid apart from it implements Mage_Adminhtml_Block_Widget_Tab_Interface.
You will then see in this case the sitemap in a grid on your product edit page or in your case your custom grid.