Najlepszy sposób na dodanie dynamicznej siatki jako danych wejściowych w niestandardowym rozszerzeniu Adminhtml

15

Zbudowałem niestandardowe rozszerzenie lokalizatora sklepu z jego własną siatką i edytuję strony w Adminhtml i wszystko działa świetnie. W przypadku godzin otwarcia sklepów chciałbym wdrożyć dynamiczną siatkę, tak jak w przypadku opcji atrybutów.

wprowadź opis zdjęcia tutaj

Teraz znalazłem rozwiązanie, ale mam nadzieję, że istnieje lepszy, a przynajmniej czystszy sposób. Do tej pory dodałem renderer do pola w formularzufieldset

class Redkiwi_Rkstorelocator_Block_Adminhtml_Rkstorelocator_Edit_Tab_General extends Mage_Adminhtml_Block_Widget_Form
{
    protected function _prepareForm()
    {
        $form = new Varien_Data_Form();
        $this->setForm($form);
        $fieldset = $form->addFieldset('rkstorelocator_form', array('legend'=>Mage::helper('rkstorelocator')->__('Store information')));

        [...]

        $officehours_field = $fieldset->addField('office_hours', 'editor', array(
            'name'      => 'office_hours',
            'label'     => Mage::helper('rkstorelocator')->__('Office hours'),
            'required'  => false,
        ));

        $officehours_block = $this->getLayout()
                                ->createBlock('rkstorelocator/adminhtml_rkstorelocator_edit_renderer_officehours')
                                ->setData(array(
                                    'name'      => 'office_hours',
                                    'label'     => Mage::helper('rkstorelocator')->__('Office hours'),
                                    'required'  => false,
                                ));

        $officehours_field->setRenderer($officehours_block);

        [...]
    }
}

I klasa bloków do renderowania

class Redkiwi_Rkstorelocator_Block_Adminhtml_Rkstorelocator_Edit_Renderer_Officehours
 extends Mage_Adminhtml_Block_Abstract 
 implements Varien_Data_Form_Element_Renderer_Interface 
{

    public function render(Varien_Data_Form_Element_Abstract $element) 
    {
        $required_indicator = $this->getData('required') ? '<span class="required">*</span>' : '' ;

        $html = '
<table id="attribute-options-table" class="dynamic-grid rkstorelocator-officehours" cellspacing="0" cellpadding="0"><tbody>
    <tr>
        <th>Day indicator</th>
        <th>Opening hour</th>
        <th>Closing hour</th>
        <th>
            <button id="add_new_option_button" title="Add Option" type="button" class="scalable add"><span><span><span>Add Option</span></span></span></button>
        </th>
    </tr>
</tbody></table>

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

var _form_html_row = \'<tr class="option-row rkstorelocator-officehours-dayrow" id="hour-row-{{id}}"><td><input name="'.$this->getData('name').'[value][option_{{id}}][0]" value="" class="input-text required-option" type="text"></td><td><input name="'.$this->getData('name').'[value][option_{{id}}][2]" value="" class="input-text required-option" type="text"></td><td><input name="'.$this->getData('name').'[value][option_{{id}}][2]" value="" class="input-text required-option" type="text"></td><td class="a-left" id="delete_button_container_option_{{id}}"><input type="hidden" class="delete-flag" name="'.$this->getData('name').'[delete][option_{{id}}]" value=""/><button onclick="$(\\\'hour-row-{{id}}\\\').remove();" title="Delete" type="button" class="scalable delete delete-option"><span><span><span>Delete</span></span></span></button></td></tr>\';

var _rkstorelocator_counter = 0;

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

//]]></script>
        ';
        return $html;
    }

}

Co daje mi następujący wynik wprowadź opis zdjęcia tutaj

Teraz to w zasadzie działa, ale uzyskanie obecnych wartości będzie dość niechlujne i w sumie nie jestem zbyt dumny z kodu, który napisałem (jak można sobie wyobrazić).

Poszukałem kilku rozwiązań, ale ogólnie wszystkie przyjmują takie podejście. Czy ktoś zna czystszy sposób to zrobić?

Sander Mangel
źródło
Czy mogę mieć pełny kod każdego pliku używanego dla tego niestandardowego modułu?
UdayPavan Reddy,

Odpowiedzi:

10

Długo mi zajęło uświadomienie sobie, że ceny Tier wyglądają tak, jak chcę. Więc po sprawdzeniu, jak Magento robi to z wyceną Tier, skończyłem, wykonując następujące czynności. Z góry przepraszam za ogromne bloki kodu, ale pomyślałem, że może być interesujący w przyszłości.

W mojej formie klasy Redkiwi_Rkstorelocator_Block_Adminhtml_Rkstorelocator_Edit_Tab_General

class Redkiwi_Rkstorelocator_Block_Adminhtml_Rkstorelocator_Edit_Tab_General extends Mage_Adminhtml_Block_Widget_Form
{
    protected function _prepareForm()
    {
        $form = new Varien_Data_Form();
        $this->setForm($form);
        $fieldset = $form->addFieldset('rkstorelocator_form', array('legend'=>Mage::helper('rkstorelocator')->__('Store information')));

        [...]

        $officehours_field = $fieldset->addField('office_hours', 'text', array(
            'name'      => 'office_hours',
            'label'     => Mage::helper('rkstorelocator')->__('Office hours'),
            'required'  => false,
        ));

        $office_hours = $form->getElement('office_hours');

        $office_hours->setRenderer(
            $this->getLayout()->createBlock('rkstorelocator/adminhtml_rkstorelocator_edit_renderer_officehours')
        );


        [...]
    }
}

Teraz klasa godzin blokowych Office Redkiwi_Rkstorelocator_Block_Adminhtml_Rkstorelocator_Edit_Renderer_Officehours.

class Redkiwi_Rkstorelocator_Block_Adminhtml_Rkstorelocator_Edit_Renderer_Officehours
 extends Mage_Adminhtml_Block_Widget
 implements Varien_Data_Form_Element_Renderer_Interface
{

    /**
     * Initialize block
     */
    public function __construct()
    {
        $this->setTemplate('rkstorelocator/officehours.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();
    }

}

I szablon pliku .phtml adminhtml/default/default/template/rkstorelocator/officehours.phtml

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

$collection = Mage::registry('rkstorelocator_data')
                ->getOpeningHours()
                ->setOrder('sortorder', 'ASC');

$_counter = 0;
?>
<tr>
    <td class="label"><?php echo $this->getElement()->getLabel() ?></td>
    <td colspan="10" class="grid hours">
        <table id="attribute-options-table" class="dynamic-grid rkstorelocator-officehours" cellspacing="0" cellpadding="0"><tbody>
            <tr>
                <th><?php echo $this->__('Day label') ?></th><th><?php echo $this->__('Opening hour') ?></th><th><?php echo $this->__('Closing hour') ?></th><th><?php echo $this->__('Sortorder') ?></th>
                <th><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 rkstorelocator-officehours-dayrow" id="hour-row-<?php echo $_counter?>">
    <td><input name="<?php echo $_htmlName; ?>[value][option_<?php echo $_counter ?>][dayindicator]" value="<?php echo $_item->getDayindicator() ?>" class="input-text" type="text"></td>
    <td><input name="<?php echo $_htmlName; ?>[value][option_<?php echo $_counter ?>][openinghour]" value="<?php echo $_item->getOpeninghour() ?>" class="input-text" type="text"></td>
    <td><input name="<?php echo $_htmlName; ?>[value][option_<?php echo $_counter ?>][closinghour]" value="<?php echo $_item->getClosinghour() ?>" class="input-text" type="text"></td>
    <td><input name="<?php echo $_htmlName; ?>[value][option_<?php echo $_counter ?>][sortorder]" value="<?php echo $_item->getSortorder() ?>" 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 rkstorelocator-officehours-dayrow" id="hour-row-{{id}}"><td><input name="<?php echo $_htmlName; ?>[value][option_{{id}}][dayindicator]" value="" class="input-text" type="text"></td><td><input name="<?php echo $_htmlName; ?>[value][option_{{id}}][openinghour]" value="" class="input-text" type="text"></td><td><input name="<?php echo $_htmlName; ?>[value][option_{{id}}][closinghour]" value="" class="input-text" type="text"></td><td><input name="<?php echo $_htmlName; ?>[value][option_{{id}}][sortorder]" 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="$(\'hour-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 _rkstorelocator_counter = <?php echo $_counter?>;

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

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

A wynik: wprowadź opis zdjęcia tutaj

Drodzy przyszli pracownicy Google, do czasu przeczytania tego Magento 2.x zostanie wydane. Miejmy nadzieję, że Magento sprawił, że tego rodzaju rzeczy są trochę łatwiejsze. :)

Sander Mangel
źródło
Jak przechowujesz je w DB? Jako ciąg zserializowany?
Siergiej Guk
Nie, mam drugi stolik na godziny pracy. Wydawało się lepsze niż serializacja
Sander Mangel
uzgodnione, wystarczy dodać to samo dla produktów, a będzie około 10 par nazwa-wartość. Zastanów się więc, czy przechowywać je jako ciągi szeregowe, czy tworzyć tabele z 1 parą na wiersz na produkt. W każdym razie dziękuję za post!
Siergiej Guk,
Powodem, dla którego nie chciałem używać danych zserializowanych, jest to, że nie można nic z tym zrobić w bazie danych. W następnej wersji tego rozszerzenia chcę wyszukiwać lokalizacje na podstawie godzin otwarcia. Byłoby to niemożliwe w przypadku danych szeregowych. Teraz mam obiekt kolekcji, którego mogę użyć. Dodatkowe dane spowodowane drugą tabelą stanowiły niewielki kompromis
Sander Mangel
Uratowałem mój dzień! Dziękuję za to. Przypuszczam, $ this-> setTemplate ('rkstorelocator / officehours.phtml'); była sztuczka. Szkoda, że ​​nie było lepszego podejścia do bitu javascript.
Xelber,
3

Podaję niektóre z moich kodów napisanych na podstawie szablonów Magento. Może się przyda.
Niektóre interfejsy kart:

<?php
class Ssd_Shower_Block_Adminhtml_Shower_Edit_Tab_Options
    extends Mage_Adminhtml_Block_Template
    implements Mage_Adminhtml_Block_Widget_Tab_Interface
{

    /** set own teplate */
    public function __construct()
    {
        $this->setTemplate('pregnancy/list/options.phtml');
    }

    /** here some implementation of tab interfeys */


    /** options for every row, they will be rendered as dynamic row with inputs */
    public function getOptionValues()
    {
        $period=$this->getData('period');
        $optionsArr = Mage::helper('shower')->getTipList($period);

        $values = array();
        foreach ($optionsArr as $option) {

            $value = array();
            $value['id'] = $option->getId();
            $value['period_id'] = $period->getId();
            $value['tip_content'] = $option->getTip_content();
            $value['sort_order'] = $option->getSort_order();
            $value['update'] = 1;

            $values[] = new Varien_Object($value);
        }

        return $values;
    }

}

?>

I szablon ciąża / lista / opcje.phtml:

<div class="entity-edit" id="manage-options-panel">
    <div class="entry-edit-head">
        <h4 class="icon-head head-edit-form fieldset-legend">Some title</h4>
    </div>
    <div class="box">
        <div class="hor-scroll">
            <table class="dynamic-grid" cellspacing="0" cellpadding="0" width="100%">
                <tr id="grid_head">
                    <th style="width:90%!important"><?php echo Mage::helper('pregnancy')->__('Checklist Items') ?></th>
                    <th class="w-150"><?php echo Mage::helper('pregnancy')->__('Position') ?></th>
                    <th class="w-150">
                        <button id="add_new_option_button" class="scalable add" style="" onclick="" type="button">
                            <span><?php echo Mage::helper('pregnancy')->__('Add Checklist Item') ?></span>
                        </button>
                    </th>
                </tr>
                <tr id="attribute-options-table">
                </tr>
                <tr class="no-display template" id="row-template">
                    <td><input name="tip[{{id}}][tip_content]"
                               value="{{tip_content}}"
                               class="input-text required-option full"
                               type="text" disabled="disabled"/></td>

                    <td class="a-center"><input class="input-text" type="text" name="tip[{{id}}][sort_order]"
                                                value="{{sort_order}}"/></td>
                    <td class="a-left">
                        <input type="hidden" class="delete-flag" name="tip[{{id}}][delete]" value=""/>
                        <input type="hidden" class="update-flag" name="tip[{{id}}][update]" value="{{update}}"/>
                        <button class="scalable delete delete-option" type="button"><span>Delete</span></button>
                    </td>
                </tr>
            </table>
        </div>
        <input type="hidden" id="option-count-check" value=""/>
    </div>
</div>

<script type="text/javascript">
    //<![CDATA[
    var optionDefaultInputType = 'text';
    //template for dynamic row
    var templateText =
            '<tr class="option-row">' +
                    '<td><input name="tip[{{id}}][tip_content]" value="{{tip_content}}" class="input-text required-option full" type="text"/><\/td>' +
                    '<td><input class="input-text" type="text" name="tip[{{id}}][sort_order]" value="{{sort_order}}"/><\/td>' +
                    '<td class="a-left">' +
                    '<input type="hidden" class="delete-flag" name="tip[{{id}}][delete]" value="" />' +
                    '<input type="hidden" class="update-flag" name="tip[{{id}}][update]" value="{{update}}"/>' +
                    '<button class="scalable delete delete-option" type="button"><span><?=$this->__("Delete")?></span></button>' +
                    '<\/td>' +
                    '<\/tr>';

    var attributeOption = {
        table : $('attribute-options-table'),
        templateSyntax : /(^|.|\r|\n)({{(\w+)}})/,
        templateText : templateText,
        itemCount : 0,
        totalItems : 0,
        //add dynamic row function
        add : function(data) {
            this.template = new Template(this.templateText, this.templateSyntax);
            if (!data.id) {
                data = {};
                data.id = 'option_' + this.itemCount;
            }
            if (!data.intype)
                data.intype = optionDefaultInputType;

            Element.insert(this.table, {before: this.template.evaluate(data)});
            this.bindRemoveButtons();
            this.itemCount++;
            this.totalItems++;
            this.updateItemsCountField();
        },
        //remove dynamic row function
        remove : function(event) {
            if (confirm('<?php echo $this->__("Do you really delete this tip?");?>')) {
                var element = $(Event.findElement(event, 'tr'));
                element.ancestors().each(function(parentItem) {
                    if (parentItem.hasClassName('option-row')) {
                        element = parentItem;
                        throw $break;
                    } else if (parentItem.hasClassName('box')) {
                        throw $break;
                    }
                });

                if (element) {
                    var elementFlags = element.getElementsByClassName('delete-flag');
                    if (elementFlags[0]) {
                        elementFlags[0].value = 1;
                    }

                    element.addClassName('no-display');
                    element.addClassName('template');
                    element.hide();
                    this.totalItems--;
                    this.updateItemsCountField();
                }
            }
        },
        updateItemsCountField: function() {
            if (this.totalItems > 0) {
                $('option-count-check').value = '1';
            } else {
                $('option-count-check').value = '';
            }
        },
        bindRemoveButtons : function() {
            var buttons = $$('.delete-option');
            for (var i = 0; i < buttons.length; i++) {
                if (!$(buttons[i]).binded) {
                    $(buttons[i]).binded = true;
                    Event.observe(buttons[i], 'click', this.remove.bind(this));
                }
            }
        }

    }
    if ($('row-template')) {
        $('row-template').remove();
    }
    attributeOption.bindRemoveButtons();

    if ($('add_new_option_button')) {
        Event.observe('add_new_option_button', 'click', attributeOption.add.bind(attributeOption));
    }
    Validation.addAllThese([
        ['required-option', '<?php echo Mage::helper('pregnancy')->__('Failed') ?>', function(v) {
            return !Validation.get('IsEmpty').test(v);
        }]
    ]);
    Validation.addAllThese([
        ['required-options-count', '<?php echo Mage::helper('pregnancy')->__('Options is required') ?>', function(v) {
            return !Validation.get('IsEmpty').test(v);
        }]
    ]);
<?php
    /** pulling data from Ssd_Shower_Block_Adminhtml_Shower_Edit_Tab_Options **/
    if ($options = $this->getOptionValues()) {
        foreach ($options as $_value): ?>
        attributeOption.add(<?php echo $_value->toJson() ?>);
    <?php endforeach; } ?>
    //]]>
</script>
mageUz
źródło
Dzięki za odpowiedź, jest to nieco zgodne z tym, jak to rozwiązałem. Niektóre części twojego phtml, których chciałbym użyć :-)
Sander Mangel