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.

 

318 válaszok
  1. best website hosting says:

    I think this is one of the most important information for me.
    And i’m glad reading your article. But wanna remark on few general things,
    The site style is wonderful, the articles is really nice : D.
    Good job, cheers

  2. best web hosting 2020 says:

    I’m curious to find out what blog platform you happen to be utilizing?
    I’m experiencing some minor security problems with my latest website and I’d like to find something more safeguarded.
    Do you have any suggestions?

  3. cheap flights says:

    Your mode of telling all in this post is truly good, every one be able to
    effortlessly understand it, Thanks a lot. cheap flights 34pIoq5

  4. cheap flights says:

    Hi! I know this is sort of off-topic but I needed to ask. Does
    building a well-established blog such as yours take a large amount of work?
    I’m completely new to running a blog however I
    do write in my journal daily. I’d like to start a
    blog so I can share my personal experience and thoughts online.
    Please let me know if you have any kind of suggestions or tips for new aspiring blog owners.
    Appreciate it! cheap flights 31muvXS

  5. cheap flights says:

    Hey would you mind letting me know which web host you’re utilizing?
    I’ve loaded your blog in 3 completely different internet browsers and I must say this blog
    loads a lot faster then most. Can you recommend a good
    web hosting provider at a reasonable price?
    Many thanks, I appreciate it! cheap flights 2CSYEon

  6. cheap flights says:

    Woah! I’m really digging the template/theme of this
    blog. It’s simple, yet effective. A lot of times it’s very
    hard to get that “perfect balance” between usability and appearance.
    I must say you have done a excellent job with this.
    In addition, the blog loads super quick for me on Safari.
    Outstanding Blog! cheap flights 31muvXS

  7. cheap flights says:

    Hello to every body, it’s my first pay a quick visit of
    this blog; this webpage carries awesome and truly good data designed for readers.

  8. black mass says:

    I visit each day a few web sites and sites to read articles or
    reviews, except this blog provides feature based articles.

  9. 24 hour plumber near me here says:

    I’m impressed, I must say. Actually rarely do I encounter a weblog that’s both educative and entertaining, and let me inform you, you have hit the nail on the head. Your idea is excellent; the problem is something that not sufficient individuals are speaking intelligently about. I am very pleased that I stumbled across this in my seek for one thing regarding this.

  10. Continued electrician san diego says:

    Hello There. I found your blog using msn. This is a very well written article. I will be sure to bookmark it and come back to read more of your useful information. Thanks for the post. I will certainly return.

  11. local plumbers Full Report says:

    Can I just say what a reduction to seek out somebody who actually knows what theyre speaking about on the internet. You undoubtedly know methods to carry a problem to gentle and make it important. Extra people need to read this and understand this aspect of the story. I cant imagine youre no more fashionable because you undoubtedly have the gift.

  12. visit site electrical contractor says:

    Appreciating the dedication you put into your blog and in depth information you offer. It’s great to come across a blog every once in a while that isn’t the same unwanted rehashed material. Great read! I’ve bookmarked your site and I’m adding your RSS feeds to my Google account.

  13. you can try this out 24 hr electrician says:

    Do you mind if I quote a couple of your articles as long as I provide credit and sources back to your blog? My blog is in the very same area of interest as yours and my users would truly benefit from some of the information you present here. Please let me know if this okay with you. Many thanks!

  14. hey says:

    Taxi moto line
    128 Rue la Boétie
    75008 Paris
    +33 6 51 612 712  

    Taxi moto paris

    You should be a part of a contest for one of the finest websites online.
    I am going to recommend this blog!

  15. www.eastwestlaw.com says:

    I do not even know how I ended up here, but I thought this post was great.
    I do not know who you are but certainly you are going
    to a famous blogger if you aren’t already ;) Cheers!

  16. http://spua.org/ says:

    I don’t even know how I ended up here, but I thought this post was good.

    I don’t know who you are but certainly you’re going to a famous blogger if you are not already ;) Cheers!

  17. the feed says:

    Its such as you read my thoughts! You appear to know a lot about this, like you wrote the e book in it or something. I feel that you could do with a few p.c. to pressure the message home a little bit, however instead of that, this is excellent blog. An excellent read. I’ll definitely be back.|

  18. www.wikidot.com says:

    Unquestionably believe that that you stated. Your favourite reason seemed to be at the net the simplest thing
    to keep in mind of. I say to you, I certainly get irked
    whilst other folks think about concerns that they plainly don’t recognise about.
    You controlled to hit the nail upon the highest as well as defined out the entire thing with no need
    side-effects , folks can take a signal. Will likely be back to get more.

    Thanks

  19. vlpacific.ru says:

    Do you mind if I quote a couple of your posts
    as long as I provide credit and sources back to your website?
    My blog site is in the exact same niche as yours and my
    users would really benefit from a lot of the information you provide here.

    Please let me know if this alright with you. Appreciate
    it!

  20. thefeed says:

    I visited multiple web pages but the audio feature for audio songs existing at this website is truly superb.|

  21. Nickslema says:

    [url=http://upsildenafil.com/]sildenafil 25 mg coupon[/url] [url=http://cialisdrug.com/]cost of cialis prescription[/url] [url=http://ordviagra.com/]can you buy viagra over the counter canada[/url] [url=http://xtadalafil.com/]buy cialis price[/url] [url=http://fildenasildenafil.com/]sildenafil 100mg price canada[/url] [url=http://levothyroxinesynthroid.com/]synthroid 150 mcg price[/url] [url=http://tetracyclineonline.com/]where can i buy terramycin eye ointment for my cat[/url] [url=http://lasixfurosemidepill.com/]furosemide 40 mg tablet cost[/url]

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é.