1

I bought an order attributes extension and as with all things Magento, I've been tweaking it to my tastes. They add in their fieldset of the new form elements into an existing checkout step. Their backend lets you pick what step and dynamically declare the extra attributes, too. I've already successfully changed three of the elements from simple text inputs to instead be <SELECT> dropdowns of attributes from our catalog.

Now I want to re-style the html so they go left to right and not in the generic order and layout of all Magento/Varien forms. I've done this dozens of times in various template .phtml files, but this case is defying my attempts.

My problem is that if I alter the template fields.phtml with my form (my usual practice), then the new order attributes show up on every checkout step, not just the one I want.

So I next think, ok, let's alter _toHtml() in fields.php. Now I have a chicken and egg problem in that this function starts off with calling $html = parent::_toHtml(); which goes through the entire HTML building process.

The template file adds a bunch of stuff above my form, so it's not "easy" to just change the text string $html.

Any ideas about getting hold of the <SELECT> html at some point in the chain?

Their code flow is like this: Company/Extension/Block/Fields.php _toHtml() calls getFormElements(), which instantiates a new Varien_Data_Form() and then creates the fieldset adding their attributes to the form with
$fieldset->addField($attribute->getAttributeCode(), $fieldType, $elementOptions) ->setEntityAttribute($attribute);

At no point is there any raw HTML in their code for making these input fields.

_toHtml() then executes:
$html = parent::_toHtml();

which goes to: app/code/core/Mage/Core/Block/Template.php which executes this series of calls:

_toHtml() calls renderView()
renderView() calls $this->fetchView($this->getTemplateFile())
fetchView() simply does an include() of the template file, fields.phtml
▄ The template file, fields.phtml then does foreach ($this->getFormElements() as $element) ... and then does echo $element->toHtml(); but, there's only one "element", which is the fieldset itself.
▄ Next stop I think is lib/Varien/Data/Form/Element/Abstract.php where its toHtml() calls getHtml()
getHtml() then calls getDefaultHtml()
getDefaultHtml() in file lib/Varien/Data/Form/Element/Fieldset.php actually generates the HTML itself, adding in the <div><h4 class="icon-head head-edit-form fieldset-legend"></h4> I'm seeing just above the fieldset itself in the page source.
▄ The fieldset is rendered via $this->getElementHtml() which in Fieldset.php, then does the and tags and for the actual elements in the fieldset, it runs $html.= $this->getChildrenHtml();

1

I ended up scraping the $html text via php's DOM classes. It's crude but it handily accomplished what I was looking for, which is getting form elements after the initial textarea to NOT each have their own <SPAN>, which made the form too bulky for my tastes.

In case someone Google's their way here, the working code is below. Tomorrow, I should post another answer with how I did this same feat for ADMINHTML form that was equally obtuse.

//  This gets the entirety of the HTML sent to the browser from 
//  the template file and this extension.
$html = parent::_toHtml();

$dom = new DOMDocument();
//  found via http://stackoverflow.com/questions/2778110/change-innerhtml-of-a-php-domelement
//  http://www.keyvan.net/2010/07/javascript-like-innerhtml-access-in-php
//  I added after $this class definition
$dom->registerNodeClass('DOMElement', 'JSLikeHTMLElement');

$dom->loadHTML($html);

$selects = '';

// get the fieldset node..
$fsNode=$dom->getElementsByTagName('fieldset')->item(0);
$spans = $fsNode->getElementsByTagName('span');

$idx=1; // skip 1st span child...


for ( ; $idx < $spans->length; $idx++ ) {
    $selects .= '<div class="left" style="padding:2px">';
    $span = $spans->item($idx);
    $els = $span->childNodes;

    foreach( $els as $el )
        $selects .= $this->getInnerHtml($el);
    $selects .= "</div>\n";
}

//  MUST step backwards to remove spans, or $spans will reset
//  This for loop came from comments at php.net
//  http://php.net/manual/en/class.domnodelist.php#83390
for ($idx = $spans->length; --$idx >= 0; ) {
    $span = $spans->item($idx);
    if( $idx > 1 )
        $span->parentNode->removeChild($span);
    if( $idx == 1 ) {
        // Remove this span's children
        // http://stackoverflow.com/questions/15272726/how-to-delete-element-with-domdocument
        $els = $span->childNodes;
        for( $jdx=$els->length; --$jdx >=0;  ) {
            $item = $els->item($jdx);
            $item->parentNode->removeChild($item);
        }
        $span->innerHTML=$selects;

        break;
    }
}
$newHtml = '';
//  get rid of top level tags: <HEAD> etc. This exact code chosen
//  by observing the output of $dom->saveHTML();
foreach( $dom->getElementsByTagName('head')->item(0)->childNodes as $node )
    $newHtml .= $this->getInnerHTML($node);

$html = $newHtml;

//  Probably from php.net:
function getInnerHTML($Node) {
    $Document = new DOMDocument();
    $Document->appendChild($Document->importNode($Node,true));
    return $Document->saveHTML();
}
| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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