I'd like to add the categories description into the topmenu navigation on Magento.
I've tried a hack using CMS block but it didn't work properly (it shows category but outside of topmenu)
Do you have any any clue about how doing this easily?
Thanks for your help.
Quick summary: (Magento 1.8.2.0 and higher) (see last paragraph for earlier versions)
Add a child block under the top.menu block in your theme's local.xml and create (or copy from RWD theme) a custom renderer.phtml file to generate your custom menu HTML that includeds category descriptions.
The recommended path for renderer.phtml is app/design/frontend/yourpackage/default/template/page/html/topmenu/renderer.phtml.
Detail:
If you have Magento 1.8.2.0 or above you should seek to implement a menu renderer.phtml file due to this code:
file: app/Mage/Page/Block/Html/Topmenu.php
class: Mage_Page_Block_Html_Topmenu extends Mage_Core_Block_Template
function: getHtml()
public function getHtml($outermostClass = '', $childrenWrapClass = '')
{
//...
if ($renderer = $this->getChild('catalog.topnav.renderer')) {
$renderer->setMenuTree($this->_menu)->setChildrenWrapClass($childrenWrapClass);
$html = $renderer->toHtml();
} else {
$html = $this->_getHtml($this->_menu, $childrenWrapClass);
}
//...
}
You can see here that if there is a child block named catalog.topnav.renderer then Magento will use it, otherwise it falls back gracefully to use $this->_getHtml() where $this is Mage_Page_Block_Html_Topmenu.
Unfortunately the Magento default theme does not use the new renderer feature so there is not an example in the base theme. However the most excellent RWD theme which comes as standard with Magento does use the menu renderer and I highly recommend you study the RWD theme code to learn how to use a menu renderer phtml file.
Specifically you should create an additional entry in your local.xml to define your menu renderer:
file: app/design/frontend/yourpackage/default/layout/local.xml
<block type="page/html_header" name="header" as="header">
<block type="core/text_list" name="top.menu" as="topMenu" translate="label">
<label>Navigation Bar</label>
<block type="page/html_topmenu" name="catalog.topnav" template="page/html/topmenu.phtml">
<block type="page/html_topmenu_renderer" name="catalog.topnav.renderer" template="page/html/topmenu/renderer.phtml"/>
</block>
</block>
</block>
Or something like that to suit your theme layout. Noting specifically the all-important hard coded child block name name="catalog.topnav.renderer"
And then I would start with a copy of the RWD renderer.phml file copied into your theme path page/html/topmenu/renderer.phtml
file: app/design/frontend/rwd/default/template/page/html/topmenu/renderer.phtml
<?php
/** #var Mage_Page_Block_Html_Topmenu_Renderer $this */
/** #var Varien_Data_Tree_Node $menuTree */
/** #var string $childrenWrapClass */
$html = '';
$children = $menuTree->getChildren();
$parentLevel = $menuTree->getLevel();
$childLevel = is_null($parentLevel) ? 0 : $parentLevel + 1;
$counter = 1;
$childrenCount = $children->count();
$parentPositionClass = $menuTree->getPositionClass();
$itemPositionClassPrefix = $parentPositionClass ? $parentPositionClass . '-' : 'nav-';
foreach ($children as $child) {
$child->setLevel($childLevel);
$child->setIsFirst($counter == 1);
$child->setIsLast($counter == $childrenCount);
$child->setPositionClass($itemPositionClassPrefix . $counter);
$outermostClassCode = 'level'. $childLevel;
$_hasChildren = ($child->hasChildren()) ? 'has-children' : '';
$html .= '<li '. $this->_getRenderedMenuItemAttributes($child) .'>';
$html .= ''. $this->escapeHtml($this->__($child->getName())) .'';
if (!empty($childrenWrapClass)) {
$html .= '<div class="'. $childrenWrapClass .'">';
}
$nextChildLevel = $childLevel + 1;
if (!empty($_hasChildren)) {
$html .= '<ul class="level'. $childLevel .'">';
$html .= '<li class="level'. $nextChildLevel .' view-all">';
$html .= '<a class="level'. $nextChildLevel .'" href="'. $child->getUrl() .'">';
$html .= $this->__('View All') . ' ' . $this->escapeHtml($this->__($child->getName()));
$html .= '</a>';
$html .= '</li>';
$html .= $this->render($child, $childrenWrapClass); //THIS IS THE RECURSION
$html .= '</ul>';
}
if (!empty($childrenWrapClass)) {
$html .= '</div>';
}
$html .= '</li>';
$counter++;
}
return $html;
And by studying that file you can start to see where and how various modifications affect the menu html.
Please note the code that Mage_Page_Block_Html_Topmenu_Renderer::render() uses to handle your rendrerer.phtml file: unusually for Magento it is a direct include $this->_templateFile and either returns the string or the ob_get_cleaned buffer:
file: /app/code/core/Mage/Page/Block/Html/Topmenu/Renderer.php
class: Mage_Page_Block_Html_Topmenu_Renderer
function: render()
public function render(Varien_Data_Tree_Node $menuTree, $childrenWrapClass)
{
ob_start();
$html = include $this->_templateFile;
$directOutput = ob_get_clean();
if (is_string($html)) {
return $html;
} else {
return $directOutput;
}
}
If you are using a version prioir to 1.8.2.0 you will need to rewrite the class Mage_Page_Block_Html_Topmenu and override its function _getHtml() to inject your extra HTML into the menu. The main disadvantage being you need to recompile every time the menu layout changes.
Thank you for this answer.
Now It's Ok I display the topmenu from my template.
The thing that I don't really understand is how to get the description value.
Does the
$children = $menuTree->getChildren();
object contain the description ?
I've tried to call it with $child->getDescription() but it did not work.
Related
I want to show filter of category on submenu, my code works!!
My problem is that if page are already filtered, my code does not return the options
I believe it has to do something in the code that bypasses the filter page and again bring the options in the submenu even if already have the filter on page
HTML of submenu:
{{block type="core/template" category="3" template="page/html/icons_submenu.phtml"}}
Content of page icons_submenu.phtml:
<?php
$layer = Mage::getModel("catalog/layer");
$category = Mage::getModel('catalog/category')->load($this->getCategory());
$layer->setCurrentCategory($category);
$attributes = $layer->getFilterableAttributes();
foreach ($attributes as $attribute) {
if ($attribute->getAttributeCode() == 'color') {
$filterBlockName = 'catalog/layer_filter_attribute';
$result = Mage::app()->getLayout()->createBlock($filterBlockName)->setLayer($layer)->setAttributeModel($attribute)->init();
echo '<strong>Color:</strong><br />';
foreach($result->getItems() as $option) {
echo ' ' . $option->getValue() . ' - ' . $option->getLabel() . '<br />';
}
}
}
?>
Example:
I would really suggest you to actually move all that logic into a proper module, a proper block and a proper model and not in a template like you are doing right now.
If you actually want further help for that, feel free to ask, making something according to the coding guide lines of Magento would make you even happier of your job, I can assure you.
This being said, what you actually want is a current filter model based on the current category and a specify attribute.
You don't need to go by the block catalog/layer_filter_attribute in a way to do this, you can directly go by the model based on the layer you already load.
So, this way of doing it should work, although it should not be in a template or view, once again :
<?php
$category = Mage::getModel('catalog/category')
->load($this->getCategory());
$layer = Mage::getModel('catalog/layer')
->setCurrentCategory($category);
$attributes = $layer->getFilterableAttributes();
foreach ($attributes as $attribute) {
if ($attribute->getAttributeCode() == 'color') {
// $filterBlockName = 'catalog/layer_filter_attribute';
/** This is actually your only problem in your code **/
// $result = Mage::app()->getLayout()->createBlock($filterBlockName)->setLayer($layer)->setAttributeModel($attribute)->init();
/** But would work with this line **/
$result = Mage::getModel('catalog/layer_filter_attribute')
->setLayer($layer)
->setAttributeModel($attribute);
echo '<strong>Color:</strong><br />';
foreach($result->getItems() as $option) {
echo ' ' . $option->getValue() . ' - ' . $option->getLabel() . '<br />';
}
}
}
?>
Then you can see it still works based on only the colours I have in the current category
But also when the category is already filtered on a specific colour
How can i display uploaded file name or link after successfully uploaded file at magento custom module form.I had attached screenshot for clear understanding.Please help
In order to do this, you need a custom renderer for the file input in your form.
For this create the following class:
<?php
class {{Namespace}}_{{Module}}_Block_Adminhtml_{{Entity}}_Helper_File extends Varien_Data_Form_Element_Abstract{
public function __construct($data){
parent::__construct($data);
$this->setType('file');
}
public function getElementHtml(){
$html = '';
$this->addClass('input-file');
$html.= parent::getElementHtml();
if ($this->getValue()) {
$url = $this->_getUrl();
if( !preg_match("/^http\:\/\/|https\:\/\//", $url) ) {
$url = Mage::getBaseUrl('media').'{{entity}}'.'/'.'file' . $url; //replace this with the path to the file if you upload it somewhere else
}
$html .= '<br />'.$this->_getUrl().' ';
}
$html.= $this->_getDeleteCheckbox();
return $html;
}
protected function _getDeleteCheckbox(){
$html = '';
if ($this->getValue()) {
$label = Mage::helper('{{module}}')->__('Delete File');
$html .= '<span class="delete-image">';
$html .= '<input type="checkbox" name="'.parent::getName().'[delete]" value="1" class="checkbox" id="'.$this->getHtmlId().'_delete"'.($this->getDisabled() ? ' disabled="disabled"': '').'/>';
$html .= '<label for="'.$this->getHtmlId().'_delete"'.($this->getDisabled() ? ' class="disabled"' : '').'> '.$label.'</label>';
$html .= $this->_getHiddenInput();
$html .= '</span>';
}
return $html;
}
protected function _getHiddenInput(){
return '<input type="hidden" name="'.parent::getName().'[value]" value="'.$this->getValue().'" />';
}
protected function _getUrl(){
return $this->getValue();
}
public function getName(){
return $this->getData('name');
}
}
Then you need to tell your for to use this for the file inputs. So in your edit form tab, add this right after defining the fiedlset:
$fieldset->addType('file', Mage::getConfig()->getBlockClassName('{{module}}/adminhtml_{{entity}}_helper_file'));
Replace {{Namespace}}, {{Module}} and {{Entity}} with the appropriate values keeping the case.
Namespace is the namespace of your module (D'uh), Module is the name of your module (D'uh again), and Entity is what you are managing. Can be Article, News, Files....
[EDIT]
You can build your module using this module creator. It takes care of these kind of issues.
Note: I hope this is not considered self promotion. The extension is free and I get no financial benefits out of it.
Ok I found answer myself...
just create one file and extended it with magento core abstract class that is Namespace_ModuleName_Block_Adminhtml_Modulename_Helper_File extends Varien_Data_Form_Element_Abstract
and added below code to admin form
$fieldset = $form->addFieldset('Module_form', array('legend'=>Mage::helper('Module')->__('Video information')));
$fieldset->addType('file', Mage::getConfig()->getBlockClassName('Module/adminhtml_Module_helper_file')); /* line added */
i am trying to built category tree for the categories and sub categories in custom admin module, if possible to override the default category tree present in edit tab of product.
Below is the code which i am working, it is able to build category tree but it lack the checkbox ability. any sugestion would be appreciated
<?php
$rootcatId= Mage::app()->getStore()->getRootCategoryId();
$categories = Mage::getModel('catalog/category')->getCategories($rootcatId);
function get_categories($categories) {
$array= '<ul>';
foreach($categories as $category) {
$cat = Mage::getModel('catalog/category')->load($category->getId());
$count = $cat->getProductCount();
$array .= '<li>'.
'<a href="' . Mage::getUrl($cat->getUrlPath()). '">' .
$category->getName() . "(".$count.")</a>\n";
if($category->hasChildren()) {
$children = Mage::getModel('catalog/category')->getCategories($category->getId());
$array .= get_categories($children);
}
$array .= '</li>';
}
return $array . '</ul>';
}
echo get_categories($categories); ?>
Please clarify your question as it's bad idea to override core functionality because same function is used by different modules instead you can check functionality of these
app/design/adminhtml/default/default/template/catalog/product/edit/categories.phtml app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit/Tab/Categories.php
and then reflect these to your template files
I have built a custom module to enable changing the Topmenu link text based on a custom attribute in the backend.
The module was tested on Magento CE 1.7.02 and works 100%.
Now I am testing on Magento EE 1.12.02 and the menu is not recognizing the rewritten Topmenu class (As-in, I can remove everything from the file, and/or mis-spell the class name in the XML and there are no errors, and the site loads fine).
Something tells me that Enterprise Edition pulls this menu from a different location than Community Edition, but I cannot find the place.
Here is the relevant portion of my config XML:
<blocks>
<page>
<rewrite>
<html_topmenu>WorldSynergy_Seoadditions_Block_Html_Topmenu</html_topmenu>
</rewrite>
</page>
</blocks>
And here is the Topmenu.php class:
class WorldSynergy_Seoadditions_Block_Html_Topmenu extends Mage_Page_Block_Html_Topmenu
{
/**
* Recursively generates top menu html from data that is specified in $menuTree
*
* #param Varien_Data_Tree_Node $menuTree
* #param string $childrenWrapClass
* #return string
*/
protected function _getHtml(Varien_Data_Tree_Node $menuTree, $childrenWrapClass)
{
$html = '';
$children = $menuTree->getChildren();
$parentLevel = $menuTree->getLevel();
$childLevel = is_null($parentLevel) ? 0 : $parentLevel + 1;
$counter = 1;
$childrenCount = $children->count();
$parentPositionClass = $menuTree->getPositionClass();
$itemPositionClassPrefix = $parentPositionClass ? $parentPositionClass . '-' : 'nav-';
foreach ($children as $child) {
$child->setLevel($childLevel);
$child->setIsFirst($counter == 1);
$child->setIsLast($counter == $childrenCount);
$child->setPositionClass($itemPositionClassPrefix . $counter);
$outermostClassCode = '';
$outermostClass = $menuTree->getOutermostClass();
if ($childLevel == 0 && $outermostClass) {
$outermostClassCode = ' class="' . $outermostClass . '" ';
$child->setClass($outermostClass);
}
$childId = explode( "-" , $child->getId() );
$childId = $childId[2];
$attrs = Mage::getModel("catalog/category")->getAttributes();
$altName = $attrs['ws_menutitle']->getFrontEnd()->getValue( Mage::getModel("catalog/category")->load( $childId ) );
if( empty($altName) ){ $altName = $child->getName(); }
$html .= '<li ' . $this->_getRenderedMenuItemAttributes($child) . '>';
$html .= '<a href="' . $child->getUrl() . '" ' . $outermostClassCode . '><span>ABC'
. $this->escapeHtml($altName) . '</span></a>';
if ($child->hasChildren()) {
if (!empty($childrenWrapClass)) {
$html .= '<div class="' . $childrenWrapClass . '">';
}
$html .= '<ul class="level' . $childLevel . '">';
$html .= $this->_getHtml($child, $childrenWrapClass);
$html .= '</ul>';
if (!empty($childrenWrapClass)) {
$html .= '</div>';
}
}
$html .= '</li>';
$counter++;
}
return $html;
}
}
I'm in 1.11.1.0, but I believe the same exact thing is happening.
If you look at app/design/frontend/base/default/layout/page.xml, you'll see that the top.menu block is missing the page/html_topmenu block that CE has.
<block type="core/text_list" name="top.menu" as="topMenu" translate="label">
<label>Navigation Bar</label>
</block>
Then, in app/design/frontend/base/default/layout/catalog.xml, they're inserting the catalog navigation block into the top.menu block:
<reference name="top.menu">
<block type="catalog/navigation" name="catalog.topnav" template="catalog/navigation/top.phtml"/>
</reference>
Strange that they did all of this in app/design/frontend/base, and not app/design/frontend/enterprise.
I have a custom module with optional text fields (shown via the standard text field option in system.xml). What I'd like to do is to show 1 mandatory text field and have a button that says something like [+ Add Field]. When that button is pressed, another text field is added. I'd like to do this for up to 10 total text fields (max). Can someone help me to accomplish this or point me to a nice tutorial on how to do this?
EDIT 7/10/12 # 11:30AM
I've been working on this and so far I can get the text fields to show up. However I have a few issues...
When pressing the "Save Config" button, none of the values entered in these dynamically created textfields actually save.
Is there a way to limit how many text fields actually save?
I'm unsure of the correct way to retrieve the text field(s) data. This is mostly due to the fact that I cannot save the values...(I will add another update after #1 is fixed if I need help with this...)
My System.xml file has this in it (directly related to this)..
<labels translate="label">
<label>This is some label</label>
<comment>This is some comment.</comment>
<tooltip><![CDATA[This is some tooltip]]></tooltip>
<frontend_type>text</frontend_type>
<frontend_model>Company_Namespace/adminhtml_textfields</frontend_model>
<backend_model>adminhtml/system_config_backend_serialized</backend_model>
<sort_order>50</sort_order>
<show_in_default>1</show_in_default>
<show_in_website>1</show_in_website>
<show_in_store>0</show_in_store>
</labels>
My custom Textfields.php (frontend_model) file contents:
<?php
class Company_Namespace_Block_Adminhtml_Textfields extends Mage_Adminhtml_Block_System_Config_Form_Field
{
protected $_addRowButtonHtml = array();
protected $_removeRowButtonHtml = array();
protected function _getElementHtml(Varien_Data_Form_Element_Abstract $element)
{
$this->setElement($element);
$html = '<div id="code_label_textfields_template" style="display:none">';
$html .= $this->_getRowTemplateHtml();
$html .= '</div>';
$html .= '<ul id="code_label_textfields_container">';
if ($this->_getValue('method')) {
foreach ($this->_getValue('method') as $i=>$f) {
if ($i) {
$html .= $this->_getRowTemplateHtml($i);
}
}
}
$html .= '</ul>';
$html .= $this->_getAddRowButtonHtml('code_label_textfields_container', 'code_label_textfields_template', $this->__('Button Label Here'));
return $html;
}
protected function _getRowTemplateHtml()
{
$html = '<li>';
$html .= '<div style="margin:5px 0 10px;">';
$html .= '<input class="input-text" name="'.$this->getElement()->getName().'" value="'.$this->_getValue('price/'.$i).'" '.$this->_getDisabled().'/> ';
$html .= $this->_getRemoveRowButtonHtml();
$html .= '</div>';
$html .= '</li>';
return $html;
}
protected function _getDisabled()
{
return $this->getElement()->getDisabled() ? ' disabled' : '';
}
protected function _getValue($key)
{
return $this->getElement()->getData('value/'.$key);
}
protected function _getSelected($key, $value)
{
return $this->getElement()->getData('value/'.$key)==$value ? 'selected="selected"' : '';
}
protected function _getAddRowButtonHtml($container, $template, $title='Add')
{
if (!isset($this->_addRowButtonHtml[$container])) {
$this->_addRowButtonHtml[$container] = $this->getLayout()->createBlock('adminhtml/widget_button')
->setType('button')
->setClass('add '.$this->_getDisabled())
->setLabel($this->__($title))
//$this->__('Add')
->setOnClick("Element.insert($('".$container."'), {bottom: $('".$template."').innerHTML})")
->setDisabled($this->_getDisabled())
->toHtml();
}
return $this->_addRowButtonHtml[$container];
}
protected function _getRemoveRowButtonHtml($selector='li', $title='Remove')
{
if (!$this->_removeRowButtonHtml) {
$this->_removeRowButtonHtml = $this->getLayout()->createBlock('adminhtml/widget_button')
->setType('button')
->setClass('delete v-middle '.$this->_getDisabled())
->setLabel($this->__($title))
//$this->__('Remove')
->setOnClick("Element.remove($(this).up('".$selector."'))")
->setDisabled($this->_getDisabled())
->toHtml();
}
return $this->_removeRowButtonHtml;
}
}
What am I missing, especially for saving values???
Refer to the system.xml and the Mage_GoogleCheckout_Block_Adminhtml_Shipping_Merchant block for an example. It's a little convoluted, but it works. You can see this in the Merchant Calculated settings in Google Checkout.