5

I need to insert a grid inside the admin form having two columns and each row should be having a delete button. Is there a way to achieve this enter image description here

2 Answers 2

4

You need a couple of things. First of all next to the normal table you'll have created for your module that stores the data of the adminhtml form you need a second table that stores the URLs data with the fields

  1. entity_id
  2. parent_id
  3. label
  4. url
  5. sortorder

Create the resource models accordingly.

Rendering in the Adminhtml

Adding a new renderer to the form generator. I've just added the field that will have the renderer

app/code/[codepool]/Namespace/Module/BLock/Adminhtml/[Module]/Edit/Tab/General.php

class [Namespace]_[Module]_Block_Adminhtml_[Module]_Edit_Tab_General extends Mage_Adminhtml_Block_Widget_Form
{
    protected function _prepareForm()
    {
        $form = new Varien_Data_Form();
        $this->setForm($form);
        $fieldset = $form->addFieldset('[module]_form', array('legend'=>Mage::helper('[namespace]_[module]')->__('General')));

        [...]       

        $urlsField = $fieldset->addField('urls', 'text', array(
            'name'      => 'urls',
            'label'     => Mage::helper('[namespace]_[module]')->__('URLS'),
            'required'  => false,
        ));

    $urlsField = $form->getElement('urls');

    $urlsField->setRenderer(
        $this->getLayout()->createBlock('[namespace]_[module]/adminhtml_[module]_edit_renderer_urls')
    );

    [...]
    }
}

The renderer class that adds the phtml template

app/code/[codepool]/Namespace/Module/BLock/Adminhtml/[Module]/Edit/Renderer/Urls.php

class [Namespace]_[Module]_Block_Adminhtml_[Module]_Edit_Renderer_Urls
 extends Mage_Adminhtml_Block_Widget
 implements Varien_Data_Form_Element_Renderer_Interface
{

    /**
     * Initialize block
     */
    public function __construct()
    {
        $this->setTemplate('[namespace]_[module]/urls.phtml');
    }

    /**
     * Render HTML
     *
     * @param Varien_Data_Form_Element_Abstract $element
     * @return string
     */
    public function render(Varien_Data_Form_Element_Abstract $element)
    {
        $this->setElement($element);
        return $this->toHtml();
    }

}

And the template that renders the filled in options and adds a javascript template for the other rows.

app/design/adminhtml/default/default/template/[namespace]_[module]/urls.phtml

$_htmlId      = $this->getElement()->getHtmlId();
$_htmlClass   = $this->getElement()->getClass();
$_htmlName    = $this->getElement()->getName();
$_readonly    = $this->getElement()->getReadonly();

$collection = Mage::registry('[namespace]_[module]_data')->getUrls(); // this gets the URLs from the model that is loaded for this item, so the class [Namespace]_[Module]_Model_[Object] needs a method `getUrls`

$_counter = 0;
?>
<tr>
    <td class="label"><?php echo $this->getElement()->getLabel() ?></td>
    <td colspan="10" class="grid hours">
        <table id="attribute-options-table" class="data border [module]-urls" cellspacing="0" cellpadding="0"><tbody>
            <tr class="headings">
                <th><?php echo $this->__('Label') ?></th>
                <th><?php echo $this->__('Url') ?></th>
                <th class="last"><button id="add_new_option_button" title="Add Option" type="button" class="scalable add"><span><span><span><?php echo $this->__('Add Option') ?></span></span></span></button></th>
            </tr>
<?php foreach ($collection as $_item): ?>
<tr class="option-row [module]-urls-row" id="urls-row-<?php echo $_counter?>">
    <td><input name="<?php echo $_htmlName; ?>[value][option_<?php echo $_counter ?>][label]" value="<?php echo $_item->getLabel() ?>" class="input-text" type="text"></td>
    <td><input name="<?php echo $_htmlName; ?>[value][option_<?php echo $_counter ?>][url]" value="<?php echo $_item->getUrl() ?>" class="input-text" type="text"></td>
    <td class="a-left" id="delete_button_container_option_<?php echo $_counter ?>'">
        <input name="<?php echo $_htmlName; ?>[value][option_<?php echo $_counter ?>][id]" value="<?php echo $_item->getId() ?>" type="hidden">
        <input id="delete-row-<?php echo $_counter ?>" type="hidden" class="delete-flag" name="<?php echo $_htmlName; ?>[delete][option_<?php echo $_counter ?>]" value=""/>
        <button onclick="$('hour-row-<?php echo $_counter ?>').style.display='none'; $('delete-row-<?php echo $_counter ?>').setValue(1);" title="Delete" type="button" class="scalable delete delete-option"><span><span><span>Delete</span></span></span></button>
    </td>
</tr>
<?php
        $_counter++;
    endforeach;
?>
</tbody></table>

<script type="text/javascript">//<![CDATA[

var _form_html_row = '<tr class="option-row [module]-urls-row" id="urls-row-{{id}}"><td><input name="<?php echo $_htmlName; ?>[value][option_{{id}}][label]" value="" class="input-text" type="text"></td><td><input name="<?php echo $_htmlName; ?>[value][option_{{id}}][url]" value="" class="input-text" type="text"></td><td class="a-left" id="delete_button_container_option_{{id}}"><input name="<?php echo $_htmlName; ?>[value][option_{{id}}][id]" value="" type="hidden"><input id="delete-row-{{id}}" type="hidden" class="delete-flag" name="<?php echo $_htmlName; ?>[delete][option_{{id}}]" value=""/><button onclick="$(\'urls-row-{{id}}\').style.display=\'none\'; $(\'delete-row-{{id}}\').setValue(1);" title="Delete" type="button" class="scalable delete delete-option"><span><span><span>Delete</span></span></span></button></td></tr>';

var _urls_counter = <?php echo $_counter?>;

$('add_new_option_button').observe('click', function(){
    $('attribute-options-table').insert(_form_html_row.replace(/\{\{id\}\}/ig, _urls_counter));
    _urls_counter++;
});

//]]></script>
    </td>
</tr>

Storing the data on save

app/code/[codepool]/[Namespace]/[Module]/controllers/Adminhtml/[Module]Controller.php

class [Namespace]_[Module]_Adminhtml_[Module]Controller 
 extends Mage_Adminhtml_Controller_Action
{

    public function saveAction()
    {
        if ( $this->getRequest()->getPost() ) 
        {
            try {
                $postData = $this->getRequest()->getPost();

                // Save the data
                $model = Mage::getModel('[namespace]_[module]/object');

                $urls = $postData['urls'];
                unset($postData['urls']);
                $model->setData($postData);

                $model->save();


                $parentId = $model->getId();

                // save urls
                if (!empty($urls))
                {
                    foreach ($urls['delete'] as $_key => $_row)
                    {
                        $delete = (int)$_row;
                        $urlsData = $urls['value'][$_key];

                        $_urls = Mage::getModel('[namespace]_[module]/[urls]')->load((int)$urlsData['id']); // this is the model that stores the URLs data in a second table

                        if ($delete && 0 < (int)$_urls->getId()) // exists & required to delete
                        {
                            $_urls->delete();
                            continue;
                        }

                        if (!$delete)
                        {
                            if (0 == (int)$_urls->getId()) // new item
                            {
                                Mage::getModel('[namespace]_[module]/[urls]')->setData(array(
                                    'parent_id' => $parentId,
                                    'label' => $urlsData['label'],
                                    'url'   => $urlsData['url'],
                                    'sortorder' => $urlsData['sortorder'],
                                ))->save();
                            }
                            else
                            {
                                $_urls->addData(array(
                                    'store_id'  => $parentId,
                                    'label' => $urlsData['label'],
                                    'url'   => $urlsData['url'],
                                    'sortorder' => $urlsData['sortorder'],
                                ))->save();
                            }
                        }
                    }
                }

                // And wrap up the transaction
                Mage::getSingleton('adminhtml/session')->addSuccess(Mage::helper('adminhtml')->__('Item was successfully saved'));
                [...]

                $this->_redirect('*/*/');
                return;
            } catch (Exception $e) {
                [...]
            }
        }
        $this->_redirect('*/*/');
    }

}

Now earlier I've referenced to the method getUrls in the objects model. It would look something like this.

app/code/[codepool]/[Namespace]/[Module]/Model/[Object].php

class [Namespace]_[Module]_Model_[Object] extends Mage_Core_Model_Abstract
{

    public function getOpeningHours()
    {
        return Mage::getModel('[namespace]_[module]/urls')->getCollection()
                    ->addFieldToFilter('parent_id', $this->getId());
    }
}

And that's it!

22
  • Do we need to do anything on the layout file in adminhtml?
    – Ravi Chan
    Commented Mar 13, 2015 at 12:13
  • the layout XML can just be the usual one you need Commented Mar 13, 2015 at 12:25
  • Still not getting the output rendered by urls.phtml
    – Ravi Chan
    Commented Mar 13, 2015 at 12:36
  • did you check if the block classes are loaded, if it can find the phtml file etc? Please try to pinpoint the issue Commented Mar 13, 2015 at 12:48
  • It is not able to find the phtml file I think . I am just a beginner in Magento. Maybe that's why I don't know how to bring it.
    – Ravi Chan
    Commented Mar 13, 2015 at 12:55
7

It's easier than that. I have described it in full at http://www.integer-net.com/2015/03/17/how-to-create-tables-in-magento-system-configuration/. In short:

system.xml:

<shipping_costs translate="label">
    <label>Shipping Cost based on Price</label>
    <frontend_model>namespace_module/config_shippingCosts</frontend_model>
    <backend_model>adminhtml/system_config_backend_serialized_array</backend_model>
    <sort_order>10</sort_order>
    <show_in_default>1</show_in_default>
    <show_in_website>1</show_in_website>
    <show_in_store>0</show_in_store>
</shipping_costs>

As you can see, the code only adds a frontend model and a backend model to the usual definition of new fields.

The backend model is used to modify saving the field value to and loading from the database. Always use adminhtml/system_config_backend_serialized_array here. The frontend model is in fact a block, despite its name. It is used to render the table with the defined columns. The frontend models always inherits from the class Mage_Adminhtml_Block_System_Config_Form_Field_Array_Abstract.

In my example the frontend model class looks like this:

<?php
class Namespace_Module_Block_Config_ShippingCosts 
    extends Mage_Adminhtml_Block_System_Config_Form_Field_Array_Abstract
{
    public function _prepareToRender()
    {
        $this->addColumn('from_price', array(
            'label' => Mage::helper('namespace_module')->__('From Price'),
            'style' => 'width:100px',
        ));
        $this->addColumn('cost', array(
            'label' => Mage::helper('namespace_module')->__('Shipping Cost'),
            'style' => 'width:100px',
        ));

        $this->_addAfter = false;
        $this->_addButtonLabel = Mage::helper('namespace_module')->__('Add');
    }
}

Nothing more is needed to display a table with two columns of 100px each.

If you want to use the values from the configuration somewhere, you just have to unserialize the field data:

$shippingCosts = Mage::getStoreConfig('namespace_module/general/shipping_costs');
if ($shippingCosts) {
    $shippingCosts = unserialize($shippingCosts);
    if (is_array($shippingCosts)) {
        foreach($shippingCosts as $shippingCostsRow) {
            $fromPrice = $shippingCostsRow['from_price'];
            $cost = $shippingCostsRow['cost'];
            ...
        }
    } else {
        // handle unserializing errors here
    }
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.