Mittwoch, 25. Juli 2012

Magento: Konfigurierbare Produkte, Direktauswahl der Attribute auf der Kategorieseite/Artikelübersicht

Will man auf der Kategorieseite/Product List/Artikelübersicht bei konfigurierbaren Artikeln/Produkten in Magento eine Direktauswahl der Attribute haben, um das aus der Übersicht in den Warenkorb zu bekommen funktioniert das direkt leider so nicht. Ich habe viel recherchiert und da ich nicht der PHP-Code-Checker bin eine Lösung gesucht, die mir weiterhilft. Hier habe ich eine Step-by-Step-Anleitung gefunden, die mir mit etwas Mühe und ein paar Stunden Arbeit es ermöglicht hat das zu lösen:

http://www.catgento.com/adding-configurable-product-options-to-category-list-in-magento/

Jetzt wollte ich das Ganze noch mal in deutsch schreiben und die Feinheiten noch beschreiben, an denen ich gehängen bin bis es funktioniert hat. Wir brauchen hierzu verschiedene Dateien, um Magento quasi dazu zu zwingen unseren Wunsch umzusetzen:

Schritt 1:

Erstelle ein neues Template unter dem Namen configurable_category.phtml und speichere es unter folgendem Pfad in deinem verwendeten Theme: app/design/frontend/default/MY-THEME/template/catalog/product/view/type. Gibt es in deinem Theme diese Ordnerstruktur nicht, dann erstelle sie. Die Datei enthält diesen Code:


<?php
$_product    = $this->getProduct();
$_attributes = Mage::helper('core')->decorateArray($this->getAllowAttributes());
?>

<?php if ($_product->isSaleable() && count($_attributes)):?>
<dl>
    <?php foreach($_attributes as $_attribute): ?>
    <dt><label><em>*</em><?php echo $_attribute->getLabel() ?></label></dt>
    <dd<?php if ($_attribute->decoratedIsLast){?><?php }?>>
        <div>
        <select name="super_attribute[<?php echo $_attribute->getAttributeId() ?>]" id="attribute<?php echo $_attribute->getAttributeId() ?>" class="required-entry super-attribute-select_<?php echo $_product->getId()?>">
        <option><?php echo $this->__('Choose an Option...') ?></option>
        </select>
        </div>
    </dd>
    <?php endforeach; ?>
</dl>

<script type="text/javascript">
    var spConfig_<?php echo $_product->getId()?> = new Inchoo_Product.Config(<?php echo $this->getJsonConfig() ?>);
</script>
<?php endif;?>


Schritt 2:

Nun erstellen wir eine Datei mit dem Namen configurable_list.js und speichern diese direkt in den js-Ordner der Magento-Installation. Hierin verwenden wir den folgenden Code:

if(typeof Inchoo_Product =='undefined') {
    var Inchoo_Product  = {};
}
 
/**************************** CONFIGURABLE PRODUCT **************************/
Inchoo_Product.Config = Class.create();
Inchoo_Product.Config.prototype = {
    initialize: function(config){
        this.config     = config;
        this.taxConfig  = this.config.taxConfig;
        var settingsClassToSelect = '.super-attribute-select_'+this.config.productId;
        this.settings   = $$(settingsClassToSelect);
        this.state      = new Hash();
        this.priceTemplate = new Template(this.config.template);
        this.prices     = config.prices;
 
        this.settings.each(function(element){
            Event.observe(element, 'change', this.configure.bind(this))
        }.bind(this));
 
        // fill state
        this.settings.each(function(element){
            var attributeId = element.id.replace(/[a-z]*/, '');
            attributeId = attributeId.replace(/_.*/, '');
            if(attributeId && this.config.attributes[attributeId]) {
                element.config = this.config.attributes[attributeId];
                element.attributeId = attributeId;
                this.state[attributeId] = false;
            }
        }.bind(this))
 
        // Init settings dropdown
        var childSettings = [];
        for(var i=this.settings.length-1;i>=0;i--){
            var prevSetting = this.settings[i-1] ? this.settings[i-1] : false;
            var nextSetting = this.settings[i+1] ? this.settings[i+1] : false;
            if(i==0){
                this.fillSelect(this.settings[i])
            }
            else {
                this.settings[i].disabled=true;
            }
            $(this.settings[i]).childSettings = childSettings.clone();
            $(this.settings[i]).prevSetting   = prevSetting;
            $(this.settings[i]).nextSetting   = nextSetting;
            childSettings.push(this.settings[i]);
        }
 
        // Set default values - from config and overwrite them by url values
        if (config.defaultValues) {
            this.values = config.defaultValues;
        }
 
        var separatorIndex = window.location.href.indexOf('#');
        if (separatorIndex != -1) {
            var paramsStr = window.location.href.substr(separatorIndex+1);
            var urlValues = paramsStr.toQueryParams();
            if (!this.values) {
                this.values = {};
            }
            for (var i in urlValues) {
                this.values[i] = urlValues[i];
            }
        }
 
        this.configureForValues();
        document.observe("dom:loaded", this.configureForValues.bind(this));
    },
 
    configureForValues: function () {
        if (this.values) {
            this.settings.each(function(element){
                var attributeId = element.attributeId;
                element.value = (typeof(this.values[attributeId]) == 'undefined')? '' : this.values[attributeId];
                this.configureElement(element);
            }.bind(this));
        }
    },
 
    configure: function(event){
        var element = Event.element(event);
        this.configureElement(element);
    },
 
    configureElement : function(element) {
        this.reloadOptionLabels(element);
        if(element.value){
            this.state[element.config.id] = element.value;
            if(element.nextSetting){
                element.nextSetting.disabled = false;
                this.fillSelect(element.nextSetting);
                this.resetChildren(element.nextSetting);
            }
        }
        else {
            this.resetChildren(element);
        }
        //this.reloadPrice();
//      Calculator.updatePrice();
    },
 
    reloadOptionLabels: function(element){
        var selectedPrice;
        if(element.options[element.selectedIndex].config){
            selectedPrice = parseFloat(element.options[element.selectedIndex].config.price)
        }
        else{
            selectedPrice = 0;
        }
        for(var i=0;i<element.options.length;i++){
            if(element.options[i].config){
                element.options[i].text = this.getOptionLabel(element.options[i].config, element.options[i].config.price-selectedPrice);
            }
        }
    },
 
    resetChildren : function(element){
        if(element.childSettings) {
            for(var i=0;i<element.childSettings.length;i++){
                element.childSettings[i].selectedIndex = 0;
                element.childSettings[i].disabled = true;
                if(element.config){
                    this.state[element.config.id] = false;
                }
            }
        }
    },
 
    fillSelect: function(element){
        var attributeId = element.id.replace(/[a-z]*/, '');
        attributeId = attributeId.replace(/_.*/, '');
        var options = this.getAttributeOptions(attributeId);
        this.clearSelect(element);
        element.options[0] = new Option(this.config.chooseText, '');
 
        var prevConfig = false;
        if(element.prevSetting){
            prevConfig = element.prevSetting.options[element.prevSetting.selectedIndex];
        }
 
        if(options) {
            var index = 1;
            for(var i=0;i<options.length;i++){
                var allowedProducts = [];
                if(prevConfig) {
                    for(var j=0;j<options[i].products.length;j++){
                        if(prevConfig.config.allowedProducts
                            && prevConfig.config.allowedProducts.indexOf(options[i].products[j])>-1){
                            allowedProducts.push(options[i].products[j]);
                        }
                    }
                } else {
                    allowedProducts = options[i].products.clone();
                }
 
                if(allowedProducts.size()>0){
                    options[i].allowedProducts = allowedProducts;
                    element.options[index] = new Option(this.getOptionLabel(options[i], options[i].price), options[i].id);
                    element.options[index].config = options[i];
                    index++;
                }
            }
        }
    },
 
    getOptionLabel: function(option, price){
        var price = parseFloat(price);
        if (this.taxConfig.includeTax) {
            var tax = price / (100 + this.taxConfig.defaultTax) * this.taxConfig.defaultTax;
            var excl = price - tax;
            var incl = excl*(1+(this.taxConfig.currentTax/100));
        } else {
            var tax = price * (this.taxConfig.currentTax / 100);
            var excl = price;
            var incl = excl + tax;
        }
 
        if (this.taxConfig.showIncludeTax || this.taxConfig.showBothPrices) {
            price = incl;
        } else {
            price = excl;
        }
 
        var str = option.label;
        if(price){
            if (this.taxConfig.showBothPrices) {
                str+= ' ' + this.formatPrice(excl, true) + ' (' + this.formatPrice(price, true) + ' ' + this.taxConfig.inclTaxTitle + ')';
            } else {
                str+= ' ' + this.formatPrice(price, true);
            }
        }
        return str;
    },
 
    formatPrice: function(price, showSign){
        var str = '';
        price = parseFloat(price);
        if(showSign){
            if(price<0){
                str+= '-';
                price = -price;
            }
            else{
                str+= '+';
            }
        }
 
        var roundedPrice = (Math.round(price*100)/100).toString();
 
        if (this.prices && this.prices[roundedPrice]) {
            str+= this.prices[roundedPrice];
        }
        else {
            str+= this.priceTemplate.evaluate({price:price.toFixed(2)});
        }
        return str;
    },
 
    clearSelect: function(element){
        for(var i=element.options.length-1;i>=0;i--){
            element.remove(i);
        }
    },
 
    getAttributeOptions: function(attributeId){
        if(this.config.attributes[attributeId]){
            return this.config.attributes[attributeId].options;
        }
    },
 
    reloadPrice: function(){
        var price    = 0;
        var oldPrice = 0;
        for(var i=this.settings.length-1;i>=0;i--){
            var selected = this.settings[i].options[this.settings[i].selectedIndex];
            if(selected.config){
                price    += parseFloat(selected.config.price);
                oldPrice += parseFloat(selected.config.oldPrice);
            }
        }
 
        optionsPrice.changePrice('config', {'price': price, 'oldPrice': oldPrice});
        optionsPrice.reload();
 
        return price;
 
        if($('product-price-'+this.config.productId)){
            $('product-price-'+this.config.productId).innerHTML = price;
        }
        this.reloadOldPrice();
    },
 
    reloadOldPrice: function(){
        if ($('old-price-'+this.config.productId)) {
 
            var price = parseFloat(this.config.oldPrice);
            for(var i=this.settings.length-1;i>=0;i--){
                var selected = this.settings[i].options[this.settings[i].selectedIndex];
                if(selected.config){
                    price+= parseFloat(selected.config.price);
                }
            }
            if (price < 0)
                price = 0;
            price = this.formatPrice(price);
 
            if($('old-price-'+this.config.productId)){
                $('old-price-'+this.config.productId).innerHTML = price;
            }
 
        }
    }
}

Diese Datei ist verantwortlich für die Darstellung der einzelnen Attribute als Dropdown im Frontend.


Schritt 3:

Nun müssen wir die neue js-Datei auch laden. Das machen wir in der catalog.xml, die man unter dem folgenden Pfad app/design/frontend/default/MY-THEME/layout findet. Man fügt im Bereich "Category default layout" oder auch an anderen Stellen wo man das Ausgabe gerne noch zusätzlich hätte folgenden Code ein:

<reference name="head">
      <action method="addJs"><script>configurable_list.js</script></action>
</reference>


Schritt 4:

Jetzt benötigen wir die die Datei list.phtml, die man unter app/design/frontend/default/MY-THEME/template/catalog/product findet. Hier müssen wir zwei mal, einmal für die Grid-Darstellung und einmal für die List-Darstellung ein Stück Code ausstauschen. Am besten den kompletten Code tauschen:

<div class="actions">
    <?php if($_product->isSaleable()): ?>
    <form action="<?php echo $this->helper('checkout/cart')->getAddUrl($_product) ?>" method="post" id="product_addtocart_form" <?php if($_product->getOptions()): ?> enctype="multipart/form-data"<?php endif; ?>>
    <?php Mage::unregister('product') ?>
    <?php Mage::register('product', $_product); ?>
     <?php if ( $_product->getTypeId() == 'configurable'): ?>
     
            <?php echo $this->getLayout()->createBlock('catalog/product_view_type_configurable', '', array('template'=> 'catalog/product/view/type/configurable_category.phtml'))->toHtml(); ?>
        <?php endif; ?>
        <button type="submit" title="<?php echo $this->__('Add to Cart') ?>" class="button btn-cart"><span><span><?php echo $this->__('Add to Cart') ?></span></span></button>
    <?php else: ?>
        <?php if ($_product->getIsSalable()): ?>
            <p class="availability in-stock"><span><?php echo $this->__('In stock') ?></span></p>
        <?php else: ?>
            <p class="availability out-of-stock"><span><?php echo $this->__('Out of stock') ?></span></p>
        <?php endif; ?>
    <?php endif; ?>
        <ul class="add-to-links">
            <?php if ($this->helper('wishlist')->isAllow()) : ?>
                <li><a href="<?php echo $this->helper('wishlist')->getAddUrl($_product) ?>" class="link-wishlist"><?php echo $this->__('Add to Wishlist') ?></a></li>
            <?php endif; ?>
            <?php if($_compareUrl=$this->getAddToCompareUrl($_product)): ?>
                <li><a href="<?php echo $_compareUrl ?>" class="link-compare"><?php echo $this->__('Add to Compare') ?></a></li>
            <?php endif; ?>
        </ul>
    </form>
</div>

Wenn ich jetzt keine Fehler bei der Beschreibung gemacht habe und ihr keine beim Einbauen, sollte der Kram jetzt hinhauen. Genauer könnt ihr das alles noch mal auf englisch auf dem oben stehenden Link nachlesen. Allerdings ist hier der Step 4 und 5 etwas verwirrend und ich habe in in einen Schritt zusammengefasst. Außerdem ist in Step 4 ein "Link" im Code richtig, der dann im Step 5 aber falsch ist. In meiner Beschreibung ist das schon korrigiert. Siehe auch die Kommentare in dem englischen Artikel wenn ihr mehr wissen wollt.

3 Kommentare:

  1. Guten Tag,

    ich benötige genau diese erweiterung und bin auch schon diese schritte durchgegangen nur leider kriege ich immer wieder die fehlermeldung:

    Call to a member function getTypeInstance() on a non-object in app/code/core/Mage/Catalog/Block/Product/View/Abstract.php on line 44

    wären Sie so nett und würden mir weiterhelfen ich benötige es sehr dringen!

    Vielen Dank für Ihre mühe.

    Grüße Anton B

    AntwortenLöschen
    Antworten
    1. Hallo Anton,

      leider ist das auch schon wieder ein Jahr her als ich das gemacht habe und das nur damals 1x. Da ich codemäßig nicht der PHP-Checker bin habe ich mich auch nur an dieser Anleitung entlanggehangelt. Aber vielleicht ist die für eine aktuellere Magentoversion auch gar nicht mehr so passend. Oben ist noch ein Link zu einem englischen Post. Vielleicht hilft der. VG

      Löschen
  2. Magento und vieles Mehrjährige Erfahrungen, technische Expertisen und unsere Kreativität sind das Sprungbrett für Ihr Vorhaben.
    Wir von Thorit.net freuen uns auf eine erfolgreiche Zusammenarbeit! Magento und vieles Benutzen Sie einfach unser Kontaktformular
    oder senden Sie uns gerne auch eine direkte Email an: info@thorit.net. Mehr Infos finden Sie hier Magento und vieles

    AntwortenLöschen