Egyedi Magento admin gridek, formok, tabok létrehozása

Tudni szeretnéd, hogyan hozhatsz létre saját webáruházadban egy külön admin menüpontot és abban egy saját lista/form párost? Az is hasznos lenne, ha vásárlóidat egy általad adminisztrált kategória struktúrába szervezhetnéd, melyekkel később külön akciókat tudsz majd végrehajtani? A Magento webáruház keretrendszerben ezeket az igényeket tudjuk egyedi admin menüpontokkal, listákkal (grid) és form-okkal kielégíteni, melyeket az adott megvalósításhoz szabhatunk.

Ebből a cikkből megtudhatod, hogy:

  • Hogyan hozz létre új Magento admin menüpontot.
  • Egy menüponthoz hogyan valósítható meg a lista.
  • Miként lehet űrlapokat létrehozni, azon belül tetszőleges input-ot.
  • A tab-ok hogyan működnek az űrlapokon.

A megoldást két úton közelítettük meg:

  1. Az elsőben létre kell hozni egy új admin menüpontot a kategóriák listázására, ill. szerkesztésére, majd kiegészíteni a vásárló szerkesztést egy új tab-bal, amiben azokat a kategóriákat lehet megadni, melyekben ő szerepel.
  2. A másik megoldás pedig az, hogy a kategória szerkesztést kiegészítjük a felhasználók listájával, ahol magához a kategóriához lehet a vásárlókat kijelölni. A végleges verzióban a második lett megvalósítva, mert így egy helyen lehet kezelni ezeket a beállításokat.

A megvalósítás tervezett lépései:

  1. Adatbázis új elemeinek megtervezése (az adatbázis módosítások megvalósítása nem ennek a cikknek képezik a részét)
  2.  Új modul létrehozása (Customercategory)
    • Könyvtár struktúra létrehozása
    • magento customer category directory structure
    • A szükséges model-, resource model-, helper-, installer létrehozása
    • App/etc/modules/Aion_Customercategory.xml
    • Config.xml létrehozása a modul etc könyvtárába
    • Controller-ek létrehozása
    • Block-ok létrehozása
    • Layout létrehozása
    • Nyelvi file-ok létrehozása (csv)

A Magento modul létrehozása

A Magento-ban egy új modul létrehozásakor a kiválasztott pool-ban (jelen esetben a local) a namespace alá (aion) létre kell hozni a modul nevének szánt könyvtárat (Customercategory), és ezzel együtt az app/etc/modules alá pedig a [modul neve].xml-t a következő tartalommal:


<?xml version="1.0"?>
 <config>
     <modules>
         <!-- namespace_modulename -->
         <Aion_Customercategory>
             <active>true</active>
             <!-- pool -->
             <codePool>local</codePool>
         </Aion_Customercategory>
     </modules>
 </config>

A modul könyvtárstuktúrája:

  • Block – ide kerülnek be a megjelenítéshez szánt form-ok, grid-ek és hasonló osztályok
  • controllers – Ez tartalmazza a controllereket
  • etc – A használt xml file-ok helye (config.xml, adminhtml.xml, …)
  • Helper – a helper file-ok helye
  • Model – A model file-ok helye
  • sql – Az adatbázis installer/update-er file-ok helye

A modulunk etc könyvtárában lévő config.xml-ben be kell állítanunk az alapokat ahhoz, hogy a modulunk működőképes legyen:


<?xml version="1.0"?>
 <config>
     <modules>
         <Aion_Customercategory>
             <version>0.1.0</version>
         </Aion_Customercategory>
     </modules>
     <global>
         <models>
             <aion_customercategory>
                 <class>Aion_Customercategory_Model</class>
                 <resourceModel>aion_customercategory_resource</resourceModel>
             </aion_customercategory>
             <aion_customercategory_resource>
                 <class>Aion_Customercategory_Model_Resource</class>
                 <entities>
                     <category>
                         <table>aion_customercategory_category</table>
                     </category>
                     <customer>
                         <table>aion_customercategory_customer</table>
                     </customer>
                 </entities>
             </aion_customercategory_resource>
         </models>
         <blocks>
             <aion_customercategory>
                 <class>Aion_Customercategory_Block</class>
             </aion_customercategory>
         </blocks>
         <helpers>
             <aion_customercategory>
                 <class>Aion_Customercategory_Helper</class>
             </aion_customercategory>
         </helpers>
         <resources>
             <aion_customercategory_setup>
                 <setup>
                     <class>Mage_Core_Model_Resource_Setup</class>
                     <module>Aion_Customercategory</module>
                 </setup>
             </aion_customercategory_setup>
         </resources>
     </global>
 </config>

Miután az alapokkal megvagyunk, létre kell hoznunk az adminban egy menüpontot, az elemek kezelésére. Mi ezt a Customer főmenüpont alá hozzuk létre, ezért a következőket kell a config.xml-be beágyaznunk:

 </global>

     <adminhtml>
         <menu>
             <customer>
                 <children>
                     <aion_customercategory>
                         <title>Customer Categories</title>
                         <sort_order>9999</sort_order>
                         <action>adminhtml/customercategory/index</action>
                     </aion_customercategory>
                 </children>
             </customer>
         </menu>
     </adminhtml>
 </config>

Látható, hogy úgy kell az XML struktúrát megadni, hogy az adminban (<adminhtml>) szeretnénk egy menüpontot definiálni (<menu>), azon belül a customer alá (<customer>) egy almenüt (<children>), ahol megadjuk a beállításainkat:

  • title – mi legyen a menüpont neve
  • sort order – a menüpont hányadik legyen a sorban
  • action – Melyik controller melyik metódusa hivódjon meg a menüpontra való kattintáskor

Most ha megnézzük az admin felületünket, akkor a következő fogad (Nagyon fontos, hogy minden xml-ben történő módosítás után ürítenünk kell a Magento cache-t, hogy újra beolvassa):

Magento admin grid customization

Ezek után meg kell írnunk a controller-t, hogy le is kezeljük a hívást:

Létre kell hoznunk az xml-ben action-ként megadott osztály/metódus párost a controllers könyvtár alá (minden controller végződése triviálisan Controller, és a metódusok pedig Action végződést kell, hogy kapjanak). Mivel ez egy admin controller lesz, ezért a Mage_Adminhtml_Controller_Action ősosztályból kell származnia

controllers/Adminhtml/CustomercategoryController.php

class Aion_Customercategory_Adminhtml_CustomercategoryController
    extends Mage_Adminhtml_Controller_Action
 {
     public function indexAction()
     {
         die('ok');
     }
 }


Ahhoz, hogy a Magento tudja, hogy az általunk megírt controllereket is használja, a config.xml-be a következő elemet kell beírnunk:

</adminhtml>
<admin>  
    <routers> 
        <adminhtml> 
            <args>  
                <modules> 
                    <Aion_Customercategory before="Mage_Adminhtml">Aion_Customercategory_Adminhtml</Aion_Customercategory>  
                </modules>     
            </args>    
        </adminhtml> 
    </routers>
</admin>

Vagyis az általunk megadott útvonalakat a Magento-s router elött kezelje. Meg lehet adni after-rel is, ami ebben az esetben irreleváns, viszont ha egy system url-t akarunk mi magunk lekezelni, akkor a before a megfelelő property, hogy a default elött fusson le a mi controller/action párosunk (természetesen a módosítás után szintén cache-t kell ürítenünk, hogy a változásokat beolvassa a Magento). Most már lefut az általunk megírt metódus, de definiálni kell a layout-ot, hogy az milyen formában renderelődjön ki. Ezt szintén xml formátumban kell megadnunk, viszont pár dolog kell hozzá:

  • meg kell adnunk a config.xml-ben az adminhtml tag alatt, hogy melyek azok a layout.xml-ek, amiket szeretnénk használni:

...
</menu>
     <layout>
         <updates>
             <aion_customercategory>
                 <file>aion/customercategory.xml</file>
             </aion_customercategory>
         </updates>
     </layout>
 </adminhtml>
...

– Magát az indexAction-ünkben be kell tölteni a layout-tot, majd kirenderelni:

class Aion_Customercategory_Adminhtml_CustomercategoryController
    extends Mage_Adminhtml_Controller_Action
 {
     public function indexAction()
     {
         $this->loadLayout();
         $this->renderLayout();
     }
 }

  • létre kell hoznunk ezeket az app/design/adminhtml/default/default/layout könyvtár alá (vagyis a modulunk layout xml leírója a /app/design/adminhtml/default/default/layout/aion/customercategory.xml file lesz).
    
    

<?xml version="1.0"?>
 <layout version="0.1.0">
     <adminhtml_customercategory_index>
         <reference name="menu">
             <action method="setActive">
                 <menupath>customer/customercategory</menupath>
             </action>
         </reference>
         <reference name="content">
             <block
                type="aion_customercategory/adminhtml_category"
                name="category_index"
            />
         </reference>
     </adminhtml_customercategory_index>
 </layout>

A fenti példában definiáljuk az adminhtml/customercategory/index (<adminhtml_customercategory_index>) útvonal layout-ját, ahol megadjuk, hogy a kiválasztott menü a customer/customercategory, és a content-je pedig egy általunk létrehozandó adminhtml/category block lesz.

Index block létrehozása

Triviálisan, amikor a felhasználó a menüpontunkra kattint, akkor egy listát szeretne látni azokkal az elemekkel, amiket meg felvitt, illetve szeretne szerkeszteni/létrehozni/törölni elemeket. A fenti layoutban megadtuk, hogy a block-unk az adminhtml_category lesz, ezért létre kell hoznunk az app/code/local/Aion/Customercategory/Block/Adminhtml/Category.php-t, ami kezeli a layout-ban megadott content-tet:

class Aion_Customercategory_Block_Adminhtml_Category

extends Mage_Adminhtml_Block_Widget_Grid_Container
{

public function __construct()
{
$this->_blockGroup = 'aion_customercategory';
$this->_controller = 'adminhtml_category';

parent::__construct();

$this->_headerText = $this->__('Customer Categories');
$this->_addButtonLabel = $this->__('Add New');

}
}

  Látható, hogy az osztályunk a Mage_Adminhtml_Block_Widget_Grid_Container leszármazottja, mert itt egy grid-et szeretnénk majd megjeleníttetni. A konstruktorban meg kell adnunk mindenképpen a _blockGroup-ot és a _controller-t, amivel megmondjuk a magento-nak, hogy a grid block-kunkat az Aion_Customercategory_Block_Adminhtml_Category_Grid-ből kell, hogy létrehozza. Amikor az ősosztály kirendereli a layout-ot, a következő függvény fut le:

$this
->getLayout()
->createBlock(
$this->_blockGroup.'/' . $this->_controller . '_grid',
$this->_controller . '.grid'
)

Grid block

Létre kell hoznunk a fent megadott grid osztályunkat az app/code/local/Aion/Customercategory/Block/Adminhtml/Category/Grid.php alatt:

class Aion_Customercategory_Block_Adminhtml_Category_Grid
extends Mage_Adminhtml_Block_Widget_Grid
 {
public function __construct()
 {
 parent::__construct();
 }
protected function _prepareCollection()
 {
 return parent::_prepareCollection();
 }
protected function _prepareColumns()
 {
 return parent::_prepareColumns();
 }
 }

Az osztályunk a Mage_Adminhtml_Block_Widget_Grid-ből származik, viszont 3 metódust mindenképpen felül kell írnunk, hogy működni tudjon a saját modulunkban:

  • __construct – A példányosításkor bizonyos alapbeállításokat eszközölnünk kell
  • _prepareCollection – Honnan szedje a collection-t
  • _prepareColumns – Milyen oszlopok legyenek a grid-ben

Kiegészítve a mi megoldásainkkal így néz ki az osztály:

class Aion_Customercategory_Block_Adminhtml_Category_Grid extends Mage_Adminhtml_Block_Widget_Grid
 {
public function __construct()
 {
 parent::__construct();
 $this->setId('category_id');
 $this->setDefaultSort('category_id');
 $this->setDefaultDir('asc');
 $this->setSaveParametersInSession(true);
 }
protected function _prepareCollection()
 {
 $collection = Mage::getModel('aion_customercategory/category')
->getCollection();
 $this->setCollection($collection);
 return parent::_prepareCollection();
 }
protected function _prepareColumns()
 {
 $this->addColumn(
 'category_id',
 array(
 'header' => $this->__('ID'),
 'align'  => 'right',
 'width'  => '50px',
 'type'   => 'number',
 'index'  => 'category_id',
 )
 );
$this->addColumn(
 'name',
 array(
 'header' => $this->__('Name'),
 'index'  => 'name',
 )
 );
$this->addColumn(
 'active',
 array(
 'header'   => $this->__('Active'),
 'index'    => 'active',
 'type'     => 'options',
 'options'  => array(
 1 => Mage::helper('adminhtml')->__('Yes'),
 0 => Mage::helper('adminhtml')->__('No')
 ),
 )
 );
return parent::_prepareColumns();
 }
 }

Ha ezeket megvalósítottuk, akkor már az oldalon is megjelenik valami:

Magento admin grid modification

Látható, hogy már van egy grid-ünk, amit még ki kell egészíteni.

Magento Admin Form megvalósítása

Az általános tervezési/megvalósítási követelményeknek megfelelően célszerű az új létrehozása és meglévő elem módosítása form-okat egy közös osztállyal lekezelni. Ennek megfelelően a controllerben a következő módosításokat kell eszközölni:

class Aion_Customercategory_Adminhtml_CustomercategoryController
extends Mage_Adminhtml_Controller_Action
 {
...
 public function editAction()
 {
 $id    = $this->getRequest()->getParam('id');
 $model = Mage::getModel('aion_customercategory/category');
if ($id) {
 $model->load($id);
 if (!$model->getId()) {
 $this->_getSession()->addError(
 $this->__('This Category no longer exists.')
 );
 $this->_redirect('*/*/');
 return;
 }
 }
$data = $this->_getSession()->getFormData(true);
 if (!empty($data)) {
 $model->setData($data);
 }
Mage::register('current_model', $model);
$this
 ->loadLayout()
 ->renderLayout();
 }
 public function newAction()
 {
 $this->_forward('edit');
 }
...
 }

Vagyis az új létrehozásakor irányítson át a szerkesztésre, és ott van levizsgálva, hogy ha van ID paraméter, akkor betölti a szükséges adatokat. Viszont ahogy az az indexAction esetében is kellett, itt is szükség van a layout xml-ben definiálni, hogy hogyan épül fel az oldal:

<?xml version="1.0"?>
 <layout version="0.1.0">
...
<adminhtml_customercategory_edit>
 <update handle="editor"/>
 <reference name="menu">
 <action method="setActive">
 <menupath>customer/customercategory</menupath>
 </action>
 </reference>
 <reference name="content">
 <block
type="aion_customercategory/adminhtml_category_edit"
name="category_edit"
/>
 </reference>
 <reference name="left">
 <block
type="aion_customercategory/adminhtml_category_edit_tabs"
name="category_tabs"
/>
 </reference>
 </adminhtml_customercategory_edit>
...
 </layout>

A szerkesztő felületen a tartalom az a kategória szerkesztő lesz (reference name=”content”), a baloldalon pedig a tab-ok (reference name=”left”). Ahhoz, hogy valami megjelenjen az oldalon, ezt a két blockot meg kell valósítanunk. Az első block az adminhtml_category_edit, amit definiálnunk kell az app/code/local/Aion/Customercategory/Block/Adminhtml/Category/Edit.php alatt:

class Aion_Customercategory_Block_Adminhtml_Category_Edit
extends Mage_Adminhtml_Block_Widget_Form_Container
 {
 protected function _getHelper()
 {
 return Mage::helper('aion_customercategory');
 }
public function __construct()
 {
 parent::__construct();
 $this->_blockGroup = 'aion_customercategory';
 $this->_controller = 'adminhtml_category';
 $this->_mode       = 'edit';
 $this->_updateButton(
'save',
'label',
$this->_getHelper()->__('Save Category')
);
 $this->_addButton(
'saveandcontinue',
array(
 'label'   => $this->_getHelper()->__(
'Save and Continue Edit'
),
 'onclick' => 'saveAndContinueEdit()',
 'class'   => 'save',
 ), -100);
$this->_formScripts[] = "
 function saveAndContinueEdit(){
 editForm.submit($('edit_form').action+'back/edit/');
 }
 ";
 }
 }

Ez egy Mage_Adminhtml_Block_Widget_Form_Container, és magát a form-ot a _blockGroup, _controller, _mode alapján az Aion_Customercategory_Adminhtml_Category_Edit_Form osztályból tölti be. Létrehozzuk ezt az osztályt az app/code/local/Aion/Customercategory/Block/Adminhtml/Category/Edit/Form.php file-ban:

class Aion_Customercategory_Block_Adminhtml_Category_Edit_Form
extends Mage_Adminhtml_Block_Widget_Form
 {
 protected function _prepareForm()
 {
 $form = new Varien_Data_Form(array(
 'id'      => 'edit_form',
 'method'  => 'post',
 'enctype' => 'multipart/form-data',
 'action'  => $this->getUrl('*/*/save'),
 )
 );
 $form->setUseContainer(true);
 $this->setForm($form);
 return parent::_prepareForm();
 }
 }

Már van egy üres form-unk, meg kell valósítani a tab-ok kezelésére szánt osztályt. A block-nak az adminhtml_category_edit_tabs-ot adtuk meg a layout.xml-ben, ezért az osztály neve Aion_Customercategory_Block_Adminhtml_Category_Edit_Tabs kell, hogy legyen. Ebből következik, hogy a file az app/code/local/Aion/Customercategory/Block/Adminhtml/Category/Edit/Tabs.php kell, hogy legyen:

class Aion_Customercategory_Block_Adminhtml_Category_Edit_Tabs
extends Mage_Adminhtml_Block_Widget_Tabs
 {
 public function __construct()
 {
 parent::__construct();
 $this->setId('category_tabs');
 $this->setDestElementId('edit_form');
 $this->setTitle(
Mage::helper('aion_customercategory')->__('Details')
);
 }
protected function _beforeToHtml()
 {
 $this->addTab(
 'category_section',
 array(
 'label' => Mage::helper('aion_customercategory')
->__('Category'),
 'title' => Mage::helper('aion_customercategory')
->__('Category'),
 'content' => $this->getLayout()
->createBlock(
'aion_customercategory/adminhtml_category_edit_category_form'
)->toHtml(),
 )
 );
 return parent::_beforeToHtml();
 }
 }

Látható, hogy a __consturctor-nál lehet megadni azt, hogy mi az azonosítója, váltáskor melyik form-ba töltse az elemeket, mi legyen a cím a tab-ok felett. A _beforeToHtml-ben adjuk meg a tab-okat dinamikusan, vagyis amennyi addTab() található benne, annyi lesz a felsorolásban. Most létre kell hoznunk az adminhtml_category_edit_category_form block-ot, ami a Aion_Customercategory_Block_Adminhtml_Category_Edit_Category_Form osztályt jelenti, ami pedig az app/code/local/Aion/Customercategory/Block/Adminhtml/Category/Edit/Category/Form.php file-ban lesz megtalálható:

class Aion_Customercategory_Block_Adminhtml_Category_Edit_Category_Form
extends Mage_Adminhtml_Block_Widget_Form
 {
 protected function _getModel()
 {
 return Mage::registry('current_model');
 }
protected function _getHelper()
 {
 return Mage::helper('aion_customercategory');
 }
protected function _prepareForm()
 {
 $model = $this->_getModel();
 $form  = new Varien_Data_Form(array(
 'id'     => 'edit_form',
 'action' => $this->getUrl('*/*/save'),
 'method' => 'post'
 ));
$fieldset = $form->addFieldset('base_fieldset', array(
 'legend' => $this->_getHelper()->__('Category Information'),
 'class'  => 'fieldset-wide',
 ));
if ($model && $model->getId()) {
 $modelPk = $model->getResource()->getIdFieldName();
 $fieldset->addField($modelPk, 'hidden', array(
 'name' => $modelPk,
 ));
 }
$fieldset->addField('name', 'text', array(
 'name'     => 'name', 'required' => true,
 'label'    => $this->_getHelper()->__('Name'),
 'title'    => $this->_getHelper()->__('Name'),
 )
 );
$fieldset->addField('active', 'select', array(
 'name'     => 'active', 'required' => true,
 'label'    => $this->_getHelper()->__('Active'),
 'title'    => $this->_getHelper()->__('Active'),
 'options'  => array(
 '1' => Mage::helper('adminhtml')->__('Yes'),
 '0' => Mage::helper('adminhtml')->__('No'),
 ),
 )
 );
if ($model) {
 $form->setValues($model->getData());
 }
 $this->setForm($form);
return parent::_prepareForm();
 }
 }

A segéd metódusok mellett a _prepareForm() függvényben adjuk a felülethez a field-eket, amelyekre szükségünk van. Most már egy látható felületünk is van, ha megtekintjük az oldalt:

Magento admin custom form development

Ezzel megoldottuk az új létrehozása, elem módosítása felületet, viszont tudnunk kell elmenteni is a form-on felvitt adatokat. Nincs másra szükségünk, mint a controller-ben (CustomercategoryController) egy saveAction() metódust definiálni, ami ezt meg is teszi:

...
public function saveAction()
 {
 $redirectBack = $this->getRequest()->getParam('back', false);
 if ($data = $this->getRequest()->getPost()) {
 $id    = $this->getRequest()->getParam('id');
 $model = Mage::getModel('aion_customercategory/category');
 if ($id) {
 $model->load($id);
 if (!$model->getId()) {
 $this->_getSession()->addError(
 $this->__('This Category no longer exists.')
 );
 $this->_redirect('*/*/index');
 return;
 }
 }
try {
 $model->addData($data);
 $this->_getSession()->setFormData($data);
 $model->save();
 $this->_getSession()->setFormData(false);
 $this->_getSession()->addSuccess(
 $this->__('The Category has been saved.')
 );
 } catch (Mage_Core_Exception $e) {
 $this->_getSession()->addError($e->getMessage());
 $redirectBack = true;
 } catch (Exception $e) {
 $this->_getSession()->addError(
$this->__('Unable to save the Category.')
);
 $redirectBack = true;
 Mage::logException($e);
 }
if ($redirectBack) {
 $this->_redirect('*/*/edit', array('id' => $model->getId()));
 return;
 }
 }
 $this->_redirect('*/*/index');
 }
...

A mentés után már a listában is megtalálhatóak azok az elemek, amiket felvittünk:

Magento custom admin form save data

Az általános grid funkciók a magento-ból származtatott osztály miatt már így is működnek:

  • szűrés
  • sorrendezés
  • lapozás

Meg kell adnunk, hogy ha egy-egy sorra kattintunk, akkor milyen url-re dobjon az oldal. Ezt az Aion_Customercategory_Block_Adminhtml_Category_Grid osztályban kell megtennünk a következő metódus segítségével:

public function getRowUrl($row)
 {
 return $this->getUrl('*/*/edit', array('id' => $row->getId()));
 }

Ahhoz, hogy a teljes funkcionalitást megvalósítsuk, a törlésre is szükség van, amit a szerkesztő form-ról érhetünk el, és automatikusan egy megerősítő popup után átirányít a deleteAction() függvényre, vagyis a controllerben ezt is meg kell valósítanunk:

public function deleteAction()
 {
 if ($id = $this->getRequest()->getParam('id')) {
 try {
 $model = Mage::getModel('aion_customercategory/category');
 $model->load($id);
 if (!$model->getId()) {
 Mage::throwException($this->__('Unable to find a Category to delete.'));
 }
 $model->delete();
 $this->_getSession()->addSuccess(
 $this->__('The Category has been deleted.')
 );
 $this->_redirect('*/*/index');
 return;
 } catch (Mage_Core_Exception $e) {
 $this->_getSession()->addError($e->getMessage());
 } catch (Exception $e) {
 $this->_getSession()->addError(
 $this->__('An error occurred while deleting Category data. Please review log and try again.')
 );
 Mage::logException($e);
 }
 $this->_redirect('*/*/edit', array('id' => $id));
 return;
 }
 $this->_getSession()->addError(
 $this->__('Unable to find a Category to delete.')
 );
 $this->_redirect('*/*/index');
 }

Ezzel már szerkeszteni tudjuk a kategóriákat, ha módosítani kell a szerkezetet, akkor elég csak a form-ban elvenni vagy hozzáadni field-eket egyszerűen kitörölve, vagy az addField() metódust használni. Ahhoz, hogy vásárlókat tudjunk a kategóriához adni a tervezési fázisban az ajax-os tab megoldás mellett döntöttünk. Egy új tab-ot kell felvenni a Aion_Customercategory_Block_Adminhtml_Category_Edit_Tabs osztályban, ami ajaxosan tölti be a customer grid-et:

protected function _beforeToHtml()
 {
 ...
 $this->addTab('customers', array(
 'label'     => $this->__('Customers'),
 'title'     => $this->__('Customers'),
 'url'       => $this->getUrl(
'*/*/customerstab',
array('_current' => true)
),
 'class'     => 'ajax'
 ));
return parent::_beforeToHtml();
 }

Az url kulcs alatt látható, hogy a már megvalósított controller-ünk customerstabAction metódusát hívja meg, amiben elvégezzük a szükséges műveleteket:

public function customerstabAction()
 {
 $saved_customer_ids = array();
 //Your load logic
 $this
 ->loadLayout()
 ->getLayout()
 ->getBlock('category.customer.tab')
 ->setSelectedCustomers($saved_customer_ids);
$this->renderLayout();
 }

Viszont fontos, hogy mint minden admin elemnek, ennek is kell a layout.xml-ben egy leíró blokk, hogy miket tartalmazzon:

...
<adminhtml_customercategory_customerstab>
 <block type="core/text_list" name="root" output="toHtml">
 <block type="aion_customercategory/adminhtml_category_edit_customer_grid" name="category.customer.tab"/>
</block>
 </adminhtml_customercategory_customerstab>
...

Viszont fontos, hogy mint minden admin elemnek, ennek is kell a layout.xml-ben egy leíró blokk, hogy miket tartalmazzon:

...
<adminhtml_customercategory_customerstab>
 <block type="core/text_list" name="root" output="toHtml">
 <block type="aion_customercategory/adminhtml_category_edit_customer_grid" name="category.customer.tab"/>
</block>
 </adminhtml_customercategory_customerstab>
...

Létre kell hoznunk a szükséges block-ot, ami ebben a példában az adminhtml_category_edit_customer_grid -> Aion_Customercategory_Block_Adminhtml_Category_Edit_Customer_Grid -> app/code/local/Aion/Customercategory/Block/Adminhtml/Category/Edit/Customer/Grid.php Ahogy azt már az előző grid-ünknél is megtettük, a __construct(), _prepareCollection(), _prepareColumns() metódusokat meg kell adni:

class Aion_Customercategory_Block_Adminhtml_Category_Edit_Customer_Grid extends Mage_Adminhtml_Block_Widget_Grid
 {
 public function __construct()
 {
 parent::__construct();
 $this->setId('customerGrid');
 $this->setUseAjax(true);
 $this->setSaveParametersInSession(true);
 }
protected function _prepareCollection()
 {
 $collection = Mage::getResourceModel('customer/customer_collection')
 ->addNameToSelect();
$this->setCollection($collection);
 return parent::_prepareCollection();
 }
protected function _prepareColumns()
 {
 $this->addColumn('selected_customers', array(
 'header'    => $this->__('Select'),
 'type'      => 'checkbox',
 'index'     => 'entity_id',
 'align'     => 'center',
 'field_name'=> 'selected_customers[]',
 'values'    => $this->getSelectedCustomers(),
 ));
$this->addColumn('customer_name', array(
 'header'    => $this->__('Name'),
 'index'     => 'name',
 'align'     => 'left',
 ));
$this->addColumn('email', array(
 'header'    => $this->__('E-mail'),
 'index'     => 'email',
 'align'     => 'left',
 ));
return parent::_prepareColumns();
 }
 }

A constructorban ha setUseAjax(true), akkor értelemszerűen ajax-osan próbálja majd lekérni a grid-et, viszont ehhez meg kell adni még ebben az osztályban a gridUrl-t is:

public function getGridUrl()
 {
 return $this->getUrl('*/*/customersgrid', array('_current' => true));
 }
Azonban triviálisan ez egy controller/action hívás, ezért azt is meg kell valósítani a controllerben:
public function customersgridAction()
 {
 $this
 ->loadLayout()
 ->getLayout()
 ->getBlock('category.customer.tab')
 ->setSelectedCustomers(
$this->getRequest()->getPost('customers', null)
);
$this->renderLayout();
 }

Ahhoz, hogy ez az ajax-os oldal is kirenderelődjön, és ne kapjunk hibát, természetesen a layout.xml-ben meg kell adni a szükséges adatokat:

...
<adminhtml_customercategory_customersgrid>
<block type="core/text_list" name="root" output="toHtml">
<block type="aion_customercategory/adminhtml_category_edit_customer_grid" name="category.customer.tab"/>
</block>
</adminhtml_customercategory_customersgrid>
...

  Ha ezeket megvalósítottuk, akkor már láthatjuk az ajax-osan betöltött customer grid-ünket:

 

magento admin customer grid

 

A következő lépés, hogy a kiválasztott customer entitásokat el tudjuk tárolni a kategóriához, vagyis egy hidden inputba be kell az ID-kat rakni, hogy a mentéskor ezeket eltárolhassuk. Ez azért kell, mert ha csak a grid-ben kijelölt checkbox-okat vizsgálnánk, akkor a lapozáskor vagy szűréskor a már kiválasztott checkbox lehet, hogy nem jelenik meg, így nem tudnánk elküldeni a mentési folyamat részeként. Szerencsére a Magento rendelkezik megoldással erre a problémára, a tab-unkat ki kell egészíteni egy új block-kal (widget_grid_serializer), ami ezt lekezeli, és csak a legfontosabb beállításokat/módosításokat kell a kódunkban elvégezni:

...
<adminhtml_customercategory_customerstab>
<block type="core/text_list" name="root" output="toHtml">
<block type="aion_customercategory/adminhtml_category_edit_customer_grid" name="category.customer.tab"/>
<block type="adminhtml/widget_grid_serializer" name="category.customer.serializer">
<action method="initSerializerBlock">
<grid_block_name>category.customer.tab</grid_block_name>
<data_callback>getSelectedCustomerIds</data_callback>
<hidden_input_name>customer_ids</hidden_input_name>
<reload_param_name>customers</reload_param_name>
</action>
</block>
    </block>
</adminhtml_customercategory_customerstab>
...

  Látszik, hogy meghívja az initSerializerBlock() metódust, aminek a paraméterei:

  • grid_block_name – melyik grid-re vonatkozik a szerializálás
  • data_callback – milyen általunk írt metódus adja vissza a már kiválasztott azonosítókat
  • hidden_input_name – mi legyen a neve az inputnak (később a post során ezzel hivatkozhatunk rá)
  • reload_param_name – melyik az a paraméter, ami a már tárolt elemeket tartalmazza

Most meg kell valósítanunk az Aion_Customercategory_Block_Adminhtml_Category_Edit_Customer_Grid osztályban a getSelectedCustomerIds() függvényt, ami visszaadja a kategóriához már megadott azonosítókat:

public function getSelectedCustomerIds()
 {
 $returnArray = array();
 //Your getter logic
 return $returnArray;
 }

Ezzel megvalósítottuk a betöltést, most a mentési procedúrát kell kiegészíteni a kiválasztott azonosítók letárolásával. Fontos, hogy az inputban szerializálva vannak az adatok, vagyis vissza kell fejteni, hogy azt el is tudjuk tárolni. Természetesen ezt a CustomercategoryController saveAction()-ben kell megvalósítani:

public function saveAction()
 {
 $redirectBack = $this->getRequest()->getParam('back', false);
 if ($data = $this->getRequest()->getPost()) {
 ...
if ($customersIds = $this->getRequest()->getParam('customer_ids', null)) {
 $customersIds = Mage::helper('adminhtml/js')
->decodeGridSerializedInput($customersIds);
 //Your save logic
 }
if ($redirectBack) {
 $this->_redirect('*/*/edit', array('id' => $model->getId()));
 return;
 }
 }
 ...
}

Végezetül egy dolog maradt még hátra, hogy teljes értékű felületet kapjunk, az, hogy az admin felhasználó tudjon szűrni a listában azokra a felhasználókra, akik ki vannak már jelölve, illetve akik nincsenek.

magento admin customer grid filter

 

Ezt úgy lehet megoldani,hogy kiegészítjük az  Aion_Customercategory_Block_Adminhtml_Category_Edit_Customer_Grid-et egy általunk létrehozott metódussal amit megadhatunk az oszlop hozzáadásánál, hogy mi legyen a szűrés függvénye:

$this->addColumn('selected_customers', array(
 ...
'filter_condition_callback' => array($this, '_selectedCustomerFilter'),
 ));

Vagyis a selected_customers oszlopban a szűrést a _selectedCustomerFilter() valósítsa meg:

protected function _selectedCustomerFilter($collection, $column)
 {
 $filter = $this->getRequest()->getParam('filter', null);
 $filter_data = Mage::helper('adminhtml')->prepareFilterString($filter);
 if (!$filter_data || !isset($filter_data['selected_customers'])) {
 return $this;
 }
$selectedCustomers = $this->getSelectedCustomers();
 if (!$selectedCustomers || !is_array($selectedCustomers)) {
 return $this;
 }
if ($filter_data['selected_customers'] == '1') {
 $operator = 'IN';
 } else {
 $operator = 'NOT IN';
 }
 $collection->getSelect()->where('e.entity_id ' . $operator . ' (' . implode(',', $selectedCustomers) . ')');
return $this;
 }

Összegzés

Ezzel a példával könnyedén meg tudunk valósítani olyan ügyféligényeket, amikor új menüpontot, új formot, új gridet vagy dinamukus tab-okat kell létrehoznunk. Dolgozhatunk meglévő collection-ökkel vagy custom adatbázis modellekkel is. A Magento nagyon sok segítséget nyújt a megvalósítás során, rengeteg core-szinten megvalósított osztálya van, amiket csak használnunk kell, illetve kiegészíteni. Ha úgy gondolod, ez a cikk hasznos lehet másoknak is, kérjük, oszd meg. Bármilyen kérdéssel, kéréssel fordulj hozzánk bizalommal kommentben.

 

6 válaszok

Hagyjon egy választ

Want to join the discussion?
Feel free to contribute!

Vélemény, hozzászólás?

Az email címet nem tesszük közzé.