Blog

Riordinare le voci nell’area clienti ed i js e css: the right way

Lettura 6 minuti

Prima di sviluppare qualsiasi funzionalità è bene verificare che qualcuno non ci abbia già pensato, un dogma piuttosto consolidato per chi si occupa di sviluppo software, noto come “non reinventare la ruota”.

La competenza è quindi, spesso, discernere tra le soluzioni trovate, quelle ritenute più corrette.

Nei casi più sfortunati non ne troviamo nessuna che ci convinca o che ci fornisca anche solo una traccia da cui partire.

È il caso dell’ordinamento delle voci di menù nell’account cliente e dell’ordine di caricamento dei javascript e css nei layout xml.

Qualora volessi aggiungere un mio javascript e accertarmi che venga eseguito prima di altri script di sistema oppure volessi mettere una voce subito dopo Account nell’area cliente, la soluzione che si trova in rete è la seguente ed è comune ai 2 scenari:

  1. effettuare una serie di action remove di tutti gli elementi che devono venire dopo quello che voglio aggiungere
  2. una action addLink con il link/item che voglio aggiungere
  3. una nuova action di aggiunta per tutti gli elementi che avevo precedentemente rimosso

Una soluzione sicuramente poco elegante.

L’alternativa

Di seguito i file relativi a un modulo che propone un metodo alternativo con relativa spiegazione dei passaggi:

/app/etc/modules/Bitbull_SimpleOrder.xml

<?xml version="1.0"?>
<config>
    <modules>
        <Bitbull_SimpleOrder>
            <active>true</active>
            <codePool>local</codePool>
            <depends>
                <Mage_Customer/>
                <Mage_Page/>
            </depends>
        </Bitbull_SimpleOrder>
    </modules>
</config>

Essenzialmente dichiariamo l’esistenza del modulo con le dipendenze dei moduli che andremo ad estendere

app/code/local/Bitbull/SimpleOrder/etc/config.xml

<?xml version="1.0"?>
<config>
    <modules>
        <Bitbull_SimpleOrder>
            <version>0.1.0</version>
        </Bitbull_SimpleOrder>
    </modules>
    <global>
        <blocks>
            <bitbull_simpleorder>
                <class>Bitbull_SimpleOrder_Block</class>
            </bitbull_simpleorder>
            <customer>
                <rewrite>
<account_navigation>Bitbull_SimpleOrder_Block_Customer_Account_Navigation</account_navigation></rewrite>
            </customer>
            <page>
                <rewrite>
                    <html_head>Bitbull_SimpleOrder_Block_Page_Html_Head</html_head>
                </rewrite>
            </page>
        </blocks>
        <helpers>
            <bitbull_simpleorder>
                <class>Bitbull_SimpleOrder_Helper</class>
            </bitbull_simpleorder>
        </helpers>
<events>
            <controller_action_postdispatch>
                <observers>
                    <bitbull_simpleorder>
                            <type>singleton</type>
                            <class>bitbull_simpleorder/observer</class>
                            <method>checkForLayoutDisplayRequest</method>
                        </bitbull_simpleorder>
                    </observers>
                    </controller_action_postdispatch>
            </events>
    </global>
    <frontend>
        <layout>
            <updates>
                <bitbull_simpleorder>
                    <file>bitbull_simpleorder.xml</file>
                </bitbull_simpleorder>
            </updates>
        </layout>
    </frontend>
</config>

Non essendoci observer adatti per il comportamento che dobbiamo sovrascrivere vengono effettuati direttamente il rewrite dei moduli base. L’observer dichiarato è stato aggiunto a seguito della scrittura dell’articolo, e ne spiego il funzionamento in coda all’articolo.

app/code/local/Bitbull/SimpleOrder/Block/Customer/Account/Navigation.php

<?php
 
class Bitbull_SimpleOrder_Block_Customer_Account_Navigation extends Mage_Customer_Block_Account_Navigation {
 
    protected $_currentPosition = 0;
 
 
    public function getLinks()
    {
        if(count($this->_links)){
            $this->_links = Mage::helper('bitbull_simpleorder')->sortElementsByPosition($this->_links);
        }
 
        return parent::getLinks();
    }
 
    public function addLink($name, $path, $label, $urlParams = array(), $position = null)
    {
        if (!$position) {
            $this->_currentPosition += 10 + $this->_currentPosition;
            $position = $this->_currentPosition;
        }
        $this->_links[$name] = new Varien_Object(array(
            'name' => $name,
            'path' => $path,
            'label' => $label,
            'url' => $this->getUrl($path, $urlParams),
            'position' => $position
        ));
        return $this;
    }
}

Ed eccoci alla parte interessante del modulo. L’idea prende spunto dalla dichiarazione e gestione dell’ordinamento delle configurazioni del control panel (il sort_order nei system.xml).

Il metodo getLinks non fa altro che richiamare un metodo dell’helper che si occupa di riordinare gli elementi in base all’attributo o chiave “position”.

Il metodo addLink come avrete già dedotto si occupa di aggiungere ai links l’attributo position che dichiareremo nell’xml per il nostro link. Ma non solo: gli elementi già esistenti non hanno una position quindi non funzionerebbe, per questo il primo if serve ad assegnargliene una se non viene passata tramite il parametro position (ovvero tutti gli elementi tranne quelli che dichiareremo noi o sovrascriveremo).

app/code/local/Bitbull/SimpleOrder/Helper/Data.php

class Bitbull_SimpleOrder_Helper_Data extends Mage_Core_Helper_Abstract {
 
    public function sortElementsByPosition($elements){
        if( is_array(reset($elements))){
            usort($elements, array($this, 'cmpArrayPositions'));
        }elseif(is_object(reset($elements))){
            usort($elements, array($this, 'cmpObjectPositions'));
        }
        return $elements;
    }
 
    private function cmpArrayPositions($a,$b){
        return $a['position'] > $b['position'];
    }
 
    private function cmpObjectPositions($a,$b){
        return $a->position > $b->position;
    }
}

l’helper essenzialmente si occupa di ordinare tramite una callback interna allo stesso gli elementi che siano array o oggetti per “position”.

app/code/local/Bitbull/SimpleOrder/Block/Page/Html/Head.php

<?php
class Bitbull_SimpleOrder_Block_Page_Html_Head extends Mage_Page_Block_Html_Head {
     
    protected $_currentPosition = 0;
     
 
    public function addItem($type, $name, $params=null, $if=null, $cond=null,$position = null)
    {
        if(empty($if)){
            $if = null;
        }
        if(empty($cond)){
            $cond = null;
        }
 
        if (!$position) {
            $this->_currentPosition = 10 + $this->_currentPosition;
            $position = $this->_currentPosition;
        }
        if ($type==='skin_css' && empty($params)) {
            $params = 'media="all"';
        }
        $this->_data['items'][$type.'/'.$name] = array(
            'type'     => $type,
            'name'     => $name,
            'params'   => $params,
            'if'       => $if,
            'cond'     => $cond,
            'position' => $position
       );
        return $this;
    }
 
    public function getCssJsHtml(){
        $this->_data['items']= Mage::helper('bitbull_simpleorder')->sortElementsByPosition($this->_data['items']);
        return parent::getCssJsHtml();
    }
}

In realtà il modulo è già praticamente finito, in quanto qui viene proposto il blocco con la medesima logica per ordinare js e css al momento del load, mentre prima erano le voci relative all’account del customer.

Unica nota rilevante per quanto riguarda gli items rispetto ai links è che Magento li dividerà comunque per tipo secondo queste tipologie, che si basano su dove vengono trovati i file:

  • ‘js’: // js/*.js
  • ‘skin_js’: // skin/*/*.js
  • ‘js_css’: // js/*.css
  • ‘skin_css’: // skin/*/*.css

Di fatto quinti potrete ordinare il caricamento di file solo tra loro per tipologia.

l’xml che manca infatti altro non è che la messa in pratica della logica qui implementata

/var/www/html/magento/app/design/frontend/{TEMA}/default/layout/bitbull_simpleorder.xml

<?xml version="1.0"?>
<layout version="0.1.0">
    <default>
        <reference name="head">
            <action method="addItem"><type>skin_css</type><name>css/mycss.css</name><params /><if/><cond /><position>12</position></action>
        </reference>
    </default>
    <customer_account>
            <reference name="customer_account_navigation">
                <action method="addLink" translate="label">
                    <name>mymodulequotes</name>
                    <path>mymodulequotes/history/index</path>
                    <label>I miei Preventivi</label>
                    <urlParams />
                    <position>15</position>
                </action>
            </reference>
    </customer_account>
</layout>

in viene aggiunto un css che verrà inserito dopo il primo elemento tra js e css caricato da Magento

in viene aggiunta una nuova voce “I miei preventivi” anche qui verosimilmente dopo la voce account.

In particolare per il primo notiamo che nonostante non vengano valorizzati vengono dichiarati (o urlParams nel caso di customer_account) il motivo è che l’instanziazione degli argomenti di addItem e AddLink è posizionale, se venissero omessi quindi il valore di position andrebbe in params.

Finito l’articolo però, mi hanno fatto notare che non è comodo supporre quale siano le posizioni assegnate, e bisognerebbe andare “a tentativi”.

Ho realizzato quest’altra parte di codice utile più che a livello di funzionalità nell’aiuto a livello di sviluppo.

app/code/local/Bitbull/SimpleOrder/Model/Observer.php

<?php
class Bitbull_SimpleOrder_Model_Observer extends Varien_Object{
    const FLAG_SHOW_LAYOUT             = 'showLayout';
    const HTTP_HEADER_HTML            = 'Content-Type: text/html';
 
 
    private function init() {
        $this->setLayout(Mage::app()->getFrontController()->getAction()->getLayout());
        $this->setUpdate($this->getLayout()->getUpdate());
    }
 
    public function checkForLayoutDisplayRequest($observer) {
        $this->init();
        /** @var Mage_Core_Controller_Varien_Action $controllerAction */
        $controllerAction = $observer->getEvent()->getData('controller_action');
        if ($controllerAction->getRequest()->isDispatched()) {
            $is_set = array_key_exists(self::FLAG_SHOW_LAYOUT, $_GET);
            if($is_set && 'itemsOrder'    == $_GET[self::FLAG_SHOW_LAYOUT]) {
                $this->outputItemsHeadOrderLayout();
            }else if($is_set && 'linksOrder' == $_GET[self::FLAG_SHOW_LAYOUT]) {
                $this->outputLinksHeadOrderLayout();
            }
        }
    }
 
    private function outputHeaders() {
        $header        = self::HTTP_HEADER_HTML;
        header($header);
    }
 
    private function outputItemsHeadOrderLayout() {
        $layout = $this->getLayout();
        $this->outputHeaders();
        var_dump($layout->getBlock('head')->getItems());
        die();
    }
 
    private function outputLinksHeadOrderLayout() {
        $layout = $this->getLayout();
        $this->outputHeaders();
        var_dump( $layout->getBlock('customer_account_navigation')->getLinks());
        die();
    }
}

Sostanzialmente agganciandomi a controller_action_postdispatch tramite observer verifico che ci sia un parametro GET showLayout che se valorizzato a itemsOrder mi farà vedere gli items relativi all’head, nel caso invece sia valorizzato a linksOrder stamperà gli oggetti links con relativa position all’interno.

Ad esempio per vedere i links nella dashboard del customer basterà chiamare: [MYSITE]/customer/account/?showLayout=linksOrder

In questo modo posso vedere qual’è la reale position che viene assegnata e decidere quella che voglio assegnare a quell oche voglio aggiungere io di conseguenza.

Qui link di git, ho omesso la parte relativa a bitbull_simpleorder.xml perchè contestuale all’esempio.Sicuramente è migliorabile perchè ad esmepio l’ordinamento riguarda soltanto i blocchi che abbiamo esteso, ma è un buon inizio ed è facilmente customizzabile se mi avete seguito fino a qui.

In conclusione se non trovate una soluzione in rete che vi convinca può essere una buona idea, controllare se già in Magento esista qualcosa di analogo o come base da cui partire, come in questo caso. Se infine neanche qui trovate qualcosa di valido, l’ultima risorsa, e probabilmente la più preziosa di uno sviluppatore, sta nella sua creatività che nella disperazione trova, di solito, terreno fertile.

Alla prossima

Marco Greco