8

Not sure why this is so difficult. If I understand THIS correctly, I should be able to swiftly accomplish my goals... But no joy.

So - I'm building my first theme, and still getting my head around layout...

I'm working specifically on the Catalog Product View page, and am converting this page from a right column layout to a left column layout. I simply want to move the blocks from the right into the left.

In the default catalog.xml, product_list_related is defined:

     </catalog_product_view> 
     //...

     <reference name="right">
            <block type="catalog/product_list_related" name="catalog.product.related" before="-" template="catalog/product/list/related.phtml"/>
        </reference>
    </catalog_product_view>

In my local.xml, I'm simply trying to move this block:

    <layout>

    // bunch other page directives

   <catalog_product_view>
        <reference name="root">
            <action method="setTemplate"><template>page/2columns-left.phtml</template></action>
        </reference>

        <reference name="right">
            <action method="unsetChild"><name>catalog.product.related</name></action>
        </reference>

        <reference name="left">
            <action method="insert"><blockName>catalog.product.related</blockName></action>

            // note that that "catalog.leftnav" gets inserted as expected
            <block type="catalog/layer_view" name="catalog.leftnav" after="-" template="catalog/layer/view.phtml"/>
        </reference>

    </catalog_product_view>

    </layout>

As noted - inserting catalog.leftnav works as expected, so I'm assuming everything else is set up correctly. The target block renders as expected if I leave the template and other directives unchanged, which tells me that the block should render once its been properly unset and inserted...

This is driving me nuts... but what else is new with Magento.

Cheers -

b[]x

UPDATE

Because I simply can not get a local.xml override to work, I'm just falling back on a modified catalog.xml. I'm a reasonably smart guy... it worries me that I can't get this to work (and that magento just silently fails, whatever the case ) - but I can't waste anymore time dicking around with this stupid issue.

Moving on.

UPDATE, again. 

Spent some time, now, working in magento and getting more familiar with its complexities. I came back to this issue today as I need to get my local.xml working right.

I really don't know what I had wrong, but this set of directives finally worked.

        <reference name="right">
                <action method="unsetChild">
                    <alias>catalog.product.related</alias>
                </action>
        </reference>

        <reference name="left">
                <action method="insert">
                    <block>catalog.product.related</block>
                </action>
        </reference>

A key point I'll make note of for others dealing with this:

Layout xml directives call available methods within magento classes. In this case, Page.xmls "Left" block is of type Mage_Core_Block_Text, which inherits from Mage_Core_Block_Abstract which contains the methods unsetChild and insert.

from Mage_Core_Block_Abstract :

/**
     * Unset child block
     *
     * @param  string $alias
     * @return Mage_Core_Block_Abstract
     */
    public function unsetChild($alias)
    {
        if (isset($this->_children[$alias])) {
            unset($this->_children[$alias]);
        }

        if (!empty($this->_sortedChildren)) {
            $key = array_search($alias, $this->_sortedChildren);
            if ($key !== false) {
                unset($this->_sortedChildren[$key]);
            }
        }

        return $this;
    }

and

  /**
     * Insert child block
     *
     * @param   Mage_Core_Block_Abstract|string $block
     * @param   string $siblingName
     * @param   boolean $after
     * @param   string $alias
     * @return  object $this
     */
    public function insert($block, $siblingName = '', $after = false, $alias = '')
    {
        if (is_string($block)) {
            $block = $this->getLayout()->getBlock($block);
        }
        if (!$block) {
            /*
             * if we don't have block - don't throw exception because
             * block can simply removed using layout method remove
             */
            //Mage::throwException(Mage::helper('core')->__('Invalid block name to set child %s: %s', $alias, $block));
            return $this;
        }
        if ($block->getIsAnonymous()) {
            $this->setChild('', $block);
            $name = $block->getNameInLayout();
        } elseif ('' != $alias) {
            $this->setChild($alias, $block);
            $name = $block->getNameInLayout();
        } else {
            $name = $block->getNameInLayout();
            $this->setChild($name, $block);
        }

        if ($siblingName === '') {
            if ($after) {
                array_push($this->_sortedChildren, $name);
            } else {
                array_unshift($this->_sortedChildren, $name);
            }
        } else {
            $key = array_search($siblingName, $this->_sortedChildren);
            if (false !== $key) {
                if ($after) {
                    $key++;
                }
                array_splice($this->_sortedChildren, $key, 0, $name);
            } else {
                if ($after) {
                    array_push($this->_sortedChildren, $name);
                } else {
                    array_unshift($this->_sortedChildren, $name);
                }
            }

            $this->_sortInstructions[$name] = array($siblingName, (bool)$after, false !== $key);
        }

        return $this;
    }

Local xml parameters are important, then, not in name (specifically), but in order ie:

<reference name="left">
                <action method="insert">
                    <block>catalog.product.related</block>
                    <siblingName>catalog.leftnav</siblingName>
                    <after>1</after>
                    <alias>catalog_product_related</alias>
                </action>
        </reference>

Ultimately, this makes local.xml a really powerful method of manipulating the system, but if you are unfamiliar with it and the magento system, get ready for weeks or months of work to really get your head around it.

Cheers


Yet another update

I've run into the problem several times now, where a block I want to move has been removed. This is a problem, as any block that has been removed from layout is nuked forever.

However, with Alan Storm's very handy Unremove Plugin you can undo whats been done:

<checkout_onepage_index>
    <x-unremove name="left" />
    <reference name="right">
        <action method="unsetChild">
            <alias>checkout.progress.wrapper</alias>
        </action>
    </reference>
    <reference name="left">
        <action method="insert">
            <block>checkout.progress.wrapper</block>
        </action>
    </reference>
</checkout_onepage_index>

It manages this feat by watching the layout object, and building a list of removed blocks, which can be later referenced.

Nice!

Community
  • 1
  • 1
Bosworth99
  • 4,206
  • 5
  • 37
  • 52
  • If you leave out the `catalog.leftna`v declaration, does the `catalog.product.related` block then render? – benmarks Mar 15 '12 at 20:04
  • `catalog.product.related` just refuses to render in `left` regardless of `leftnav`. – Bosworth99 Mar 15 '12 at 20:06
  • Its the Professional Edition. Most files are in `base/default`. The pro additions are amended to `pro/default`. Following the magento design guide, I copied the `pro/default` into `my_package/default` and made yet another copy `my_package/varient` - a varient which is my working file set. – Bosworth99 Mar 15 '12 at 23:35
  • Do a search for `catalog.product.related` and make sure that it's not being munged in the pro default theme. – benmarks Mar 15 '12 at 23:55
  • 1
    +1 Thanks for editing! unsetChild and insert are the key! – Dustin Graham Jul 11 '12 at 15:35

7 Answers7

19

This is a dusty thread now, but for the record, this is the final answer I went with.

<reference name="right">
    <action method="unsetChild">
        <alias>checkout.progress.wrapper</alias>
    </action>
</reference>
<reference name="left">
    <action method="insert">
        <block>checkout.progress.wrapper</block>
    </action>
</reference>

Magento recommends including this in your local.xml, and it has proved an effective technique.

Bosworth99
  • 4,206
  • 5
  • 37
  • 52
  • 1
    For teh Win! Thank you for beating it into the ground and delivering its bloody carcase to be used in tasty dishes. Used it to move right navigation to left navigation in the customer accounts since **that** was the only major difference between the customer.xml template for the new version of Magento and the old version we're running. Stock upgradable templates with 12 lines of code in the local.xml to shift it was the delivered goods with no further diff'ing the old version vs the new version. – Fiasco Labs Mar 26 '13 at 19:00
  • Glad this was of use to you. Its a very hairy issue. – Bosworth99 Mar 27 '13 at 19:46
2

If you are just looking to change the output from 2col-right, to 2col-left, it would have been much, much easier to just change

<reference name="right" ...

to

<reference name="left" ...

There is no need to unset or insert any children or re-declare anything. You are overcomplicating things and duplicating code needlessly.

If you are making your own very besoke design too, I would suggest starting copying the entire ./app/design/frontend/base directory to ./app/design/frontend/mypackage - then work from the mypackage/default directory to re-skin your site. Its cleaner and easier. Others may comment on the potential issues of upgrade-ability if you copy every file (rather than just the ones you intend to modify), but it is by far a better practice and much less prone to errors, difficulties and general maintenance.

Edit: To follow up on this in more detail - have a look https://magento.stackexchange.com/a/3794/361

Community
  • 1
  • 1
Ben Lessani
  • 2,141
  • 14
  • 16
  • So. I get exactly what you're saying. But I read over and over that "the right way" to theme is by doing all my layout updates in local.xml. Now... maybe that's nonsense. I really don't know because opinions vary widely. As far as overriding the whole pro package (I'm on professional version), I've just been overriding whatever is needed on a per file basis. Honestly - at this point, our plan `A` is to NOT upgrade (so maybe the whole "unobtrusive" thing is just pointless"... thanks for the reply – Bosworth99 Mar 15 '12 at 20:10
  • But this doesn't explain why my local.xml isn't working. As far as I can tell, it should... Frusty. – Bosworth99 Mar 15 '12 at 20:11
  • 2
    I strenuously disagree with modifying a default theme in general and needlessly modifying core layout files in particular. Clean and easy in mind is having a custom theme which contains **only** those files which have been modified, *relying* on a pristine default theme, whatever the source. Moreover, `base/default` is always resolved no matter the design settings, so duplicating it is really quite pointless. – benmarks Mar 15 '12 at 20:15
  • 1
    @Ben I did not say to modify any **core** files, in fact, I clearly stipulated to copy the `base/default` directory (as this is the cleanest version of the base template) to your own custom package and theme. `base/default` is the categorical fallback if all else fails, otherwise ALL of the theme should be served from your own base package, NEVER `/base` or `/default`. Changing only the files that need to be modified is the "least impact" option - but if you are building a truly bespoke store, it is by far the best method. Given your status, I'm suprised you advocate any other method. – Ben Lessani Mar 15 '12 at 20:52
  • 1
    @sonassi - My comment is based on the suggestion to duplicate an entire theme for a simple layout change. In general: editing `base/default` theme in a new pkg means that you own all files and have to merge *all* template & layout changes in an upgrade. Whereas `base/default` is the failsafe, there are *3* custom theme levels - a must for multistores and enterprise sites. The convenience and cleanliness is apparent in an upgrade; merge a custom theme with 5 or 6 files vs having to three-way diff (needlessly) dozens of version changes. This design edict comes from core team, FWIW. – benmarks Mar 15 '12 at 22:49
  • 1
    @Ben You are right if he is only modifying a few things, I've edited my comment to suit. Do you not feel the overheads of adding, removing, appending and inserting as well as cross referencing another package for its influence on *this* package makes ongoing development a tiresome chore. Given the infrequency of upgrades and the demands of regular development, I can't help but thinking the apparent time saving for upgrades comes at the cost of day-day development being much more awkward. I guess we'll have to have an in-house meeting about this ... maybe we'll have to change our tactics! – Ben Lessani Mar 15 '12 at 22:59
  • 2
    @sonassi Now that we (Blue Acorn) deeply understand layout modeling and layout update syntax we can get anything done quickly; lots of effort went into learning though. – benmarks Mar 15 '12 at 23:10
  • All that being said. I've only been working on this project for a couple weeks. I wouldn't say that we're doing a **highly** customized site - but I'm certainly making considerable changes to the presentation. (And to keep myself sane... trying to work with the default layout as much as I can.) --- The central reliance on layout modeling in magento is really what is tripping me up - and only experience can solve that. It doesn't help that the interwebs is full of partial or contrary information either. I appreciate this discussion - but I'm not sure I'm any further along tho! – Bosworth99 Mar 15 '12 at 23:43
  • @sonassi - your points are duly noted. The fact is - we are very unlikely to do too many upgrades... I'm not sure what the benefits of the local.xml method are in this case, tho I completely understand its purpose. – Bosworth99 Mar 15 '12 at 23:45
  • Calling this the answer, but with the caveat that there are many valid opinions ;D – Bosworth99 Mar 19 '12 at 16:15
  • Now I've got yet another bloody .xml file in my template I have to diff every time I do an upgrade. No thanks. I'd rather concentrate on what really changed. – Fiasco Labs Mar 26 '13 at 19:05
2

An alternative approach is to rename the "right" block to "left" rather than move blocks from right to left. However, this will not work if the "right" block is being removed in the first place as I suggested might be the case in my other answer.

<remove name="left" />
<reference name="right">
  <action method="setNameInLayout"><name>left</name></action>
</reference>
<reference name="root">
  <action method="setChild"><alias>left</alias><name>left</name></action>
  <action method="unsetChild"><alias>right</alias></action>
</reference>
<!-- make sure nothing referencing "right" comes after this! -->
ColinM
  • 13,367
  • 3
  • 42
  • 49
  • This is why the 'recommended' local.xml method concerns me, it seems to require a myriad of setting, un-setting, inserting and appending and checking, and re-checking what is going on in another package influencing this theme. :( – Ben Lessani Mar 15 '12 at 22:53
  • A valid concern, but unfortunately there all sorts of "who's my parent" coupling going on, and not interfering with the "core" implementation unless *absolutely* necessary can be advised. Sure is quick and easy to just customize core layout files, though ;-) – benmarks Mar 15 '12 at 23:02
  • I wish you wouldn't imply it was a core layout change. It is a custom package/design, no 'core' changes have taken place. I think, in time, the `local.xml` advocates will suffer equally, if not greater than those who copied `base/default` when the `local.xml` file reaches several hundred lines of 'core changes'. Extensibility has its limits and having to cross reference design influencing when building a truly custom store just sounds like a awful idea. – Ben Lessani Mar 15 '12 at 23:25
  • @Colin - I'd still kind of like to go this route in local.xml - but I can not get this to work. Your code looks a little borked (`leftleft` - could you modify it to a state that "should" work? – Bosworth99 Mar 19 '12 at 18:54
  • 1
    @sonassi, we use local.xml exclusively and one is almost 600 lines long and I don't feel like we are suffering at all due to this. I prefer to have all of our layout changes in one file versus duplicating thousands of lines of core code. When upgrading we just eyeball what was changed and usually don't have to make any changes whereas merging updates from core would be a royal PITA (which is what we are forced to do for CSS). – ColinM Apr 16 '12 at 19:11
  • Bosworth99, I just fixed the problems and edited the answer with a working version. – ColinM Apr 16 '12 at 19:21
1

I would do it this way: In your local.xml under e.g.

<reference name="right"></reference>

you remove the blocks, e.g.:

<remove name="right.poll">

and then you add the blocks in

<reference name="left"></reference>
Socrates
  • 189
  • 1
  • 5
  • 19
1

The "right" block is probably being removed with a <remove name="right" /> at some point in the layout. If so, this will cause the "catalog.product.related" block to never be added to the layout in the first place so there is no block to insert into the "left" block.

I'm pretty sure this is the issue, but to confirm, add a Mage::log("Removed $blockName"); in Mage_Core_Model_Layout->generateXml() inside the for loop and check the log after loading the page.

If I'm right, simply copy the <block..> into your local.xml and remove the "unsetChild" and "insert" actions.

ColinM
  • 13,367
  • 3
  • 42
  • 49
  • This is a good direction. However:`2012-03-15T22:57:03+00:00 DEBUG (7): Removed left.newsletter 2012-03-15T22:57:03+00:00 DEBUG (7): Removed product.tierprices 2012-03-15T22:57:03+00:00 DEBUG (7): Removed reorder` is the extent of the removed blocks. – Bosworth99 Mar 15 '12 at 22:57
0

I think the best answer to modify Magento layouts is given on Classyllama.com

http://www.classyllama.com/development/magento-development/the-better-way-to-modify-magento-layout

When you use the remove tag, it removes any blocks with the specified name from the entire layout, regardless of the context. So, if I remove right.newsletter in the context and that name is used in say the context, then both blocks will be removed. Because remove operates on the global context, you can only remove an element once. Since is being called in catalogsearch.xml, we have to unset it, or else we'll get an error.

<action method="unsetChild"><name>right.newsletter</name></action>;
robert
  • 33,242
  • 8
  • 53
  • 74
hans2103
  • 497
  • 4
  • 12
0

Copying base/default to a local theme is a horrible idea. Every update to a core theme file on a Magento version upgrade leads to the same vulnerability of modifying app/code/core files -- needing you to diff the files out on upgrades.

Your proper course of action is modifying a single local.xml with your additions or overrides. If that doesn't suffice, use the proper syntax to create a module in app/code/community with your layout definitions and custom layout XML files.

Mark Shust at M.academy
  • 6,300
  • 4
  • 32
  • 50
  • We've upgraded **a lot** of Magento stores, CE and EE alike - and the issue is almost **never** the template - but rather the use of 3rd party/custom extensions. And from experience, the only template differences between major point upgrades were very subtle anyway, eg. 1.3 > 1.4 (pager syntax changed, form key was added). However, not copying the `base/default` directory - then performing an upgrade would lead to two different sets of template code in use, potentially causing more issues than using an older template with supported (but depracated/or soon to be) functions. – Ben Lessani Mar 15 '12 at 22:22
  • I think you meant *copying* the base/default directory - then performing an upgrade would lead to two different sets of template code in use. You want to use the fallback layer built into Magento. – Mark Shust at M.academy May 17 '12 at 03:09