Лучший способ добавить динамическую сетку в качестве входных данных в пользовательское расширение Adminhtml
Я создал пользовательское расширение для поиска магазинов с собственной сеткой и редактировал страницы в Adminhtml, и все отлично работает. Для часов работы магазинов я хотел бы реализовать динамическую сетку, например, для параметров атрибутов.
Теперь я нашел решение, но я надеюсь, что есть лучший или, по крайней мере, более чистый способ.
Что у меня есть до сих пор, так это добавление средства визуализации в поле в форме fieldset
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);
[...]
}
}
И класс блоков для визуализация
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;
}
}
Что дает мне следующий результат
Теперь это в основном работает, но получение текущих значений будет довольно запутанным, и в целом я не слишком горжусь написанным мной кодом (как вы можете себе представить).
Я поискал в Гугле несколько решений, но все они, как правило, используют этот подход. Кто-нибудь знает более чистый способ сделать это?
2 answers
Мне потребовалось много времени, чтобы понять, что цены на уровне выглядят так, как я хочу. Поэтому, изучив, как Magento делает это с помощью ценового уровня, я в итоге сделал следующее. Заранее прошу прощения за огромные блоки кода, но я подумал, что это может быть интересно для дальнейшего использования.
В моем классе формы 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')
);
[...]
}
}
Теперь для класса блока рабочего времени 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();
}
}
И файл template.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>
И результат:
Дорогое будущее Гуглеры, к тому времени, как вы прочтете это, Magento 2.x будет выпущен. Будем надеяться, что Magento немного упростил такие вещи. :)
Я привожу некоторые из моих кодов, написанных на основе шаблонов Magento. Может быть, это будет полезно.
Интерфейс некоторых вкладок:
<?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;
}
}
?>
И беременность/список/параметры. шаблон 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>