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.
Related
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.
I want to disable the anchor tag of two subcategories. I used a free extension for topmenu. The code for subcategory menu is:
public function drawMenuItem($children, $level = 1)
{
$html = '<div class="itemMenu level' . $level . '">';
$keyCurrent = $this->getCurrentCategory()->getId();
foreach ($children as $child) {
if (is_object($child) && $child->getIsActive()) {
// --- class for active category ---
$active = '';
if ($this->isCategoryActive($child)) {
$active = ' actParent';
if ($child->getId() == $keyCurrent) $active = ' act';
}
// --- format category name ---
$name = $this->escapeHtml($child->getName());
if (Mage::getStoreConfig('custom_menu/general/non_breaking_space')) $name = str_replace(' ', ' ', $name);
$html.= '<a class="itemMenuName level' . $level . $active . '" href="' . $this->getCategoryUrl($child) . '"><span>' . $name . '</span></a>';
$activeChildren = $this->_getActiveChildren($child, $level);
if (count($activeChildren) > 0) {
$html.= '<div class="itemSubMenu level' . $level . '">';
$html.= $this->drawMenuItem($activeChildren, $level + 1);
$html.= '</div>';
}
}
}
$html.= '</div>';
return $html;
}
I tried to disable the two subcategories. But it didn't work. How can I give proper condition to disable particular two subcategories link?
I want my error, warning, notification messages on frontend to extend a bit. So,
I need to override
Mage_Core_Block_Messages
class's
public function getGroupedHtml()
{
$types = array(
Mage_Core_Model_Message::ERROR,
Mage_Core_Model_Message::WARNING,
Mage_Core_Model_Message::NOTICE,
Mage_Core_Model_Message::SUCCESS
);
$html = '';
foreach ($types as $type) {
if ( $messages = $this->getMessages($type) ) {
if ( !$html ) {
$html .= '<' . $this->_messagesFirstLevelTagName . ' class="messages">';
}
$html .= '<' . $this->_messagesSecondLevelTagName . ' class="' . $type . '-msg">';
$html .= '<' . $this->_messagesFirstLevelTagName . '>';
foreach ( $messages as $message ) {
$html.= '<' . $this->_messagesSecondLevelTagName . '>';
$html.= '<' . $this->_messagesContentWrapperTagName . '>';
$html.= ($this->_escapeMessageFlag) ? $this->htmlEscape($message->getText()) : $message->getText();
$html.= '</' . $this->_messagesContentWrapperTagName . '>';
$html.= '</' . $this->_messagesSecondLevelTagName . '>';
}
$html .= '</' . $this->_messagesFirstLevelTagName . '>';
$html .= '</' . $this->_messagesSecondLevelTagName . '>';
}
}
if ( $html) {
$html .= '</' . $this->_messagesFirstLevelTagName . '>';
}
return $html;
}
to extend the html and put a cross in the message box and implement Jquery. logic: on click close hide error box. So customers can upon click hide the box.
I believe this class doesn't have any template file and the html it is rendering from the this block class itself as I can see in getGroupedHtml() method.
So, I am going to override this method and add more html.
Also, I want to do this only for one theme and not in Admin
What is the better way to achieve this?
Please suggest me something. Thanks
You could accomplish this without overriding anything and by just going for JavaScript (jQuery) only.
Here is a simple script that should do the job.
var messages = jQuery("ul.messages li[class$='-msg']");
messages.each(function(){
var message = jQuery(this);
message.find('span').append('<span class="close">X</span>');
});
messages.on('click', function(){
var message = jQuery(this).closest("li[class$='-msg']");
message.hide();
});
The script adds a 'X' to the end of every message and removes (hides) the message when it's clicked.
The only thing left to do is to give the cross (span X) some styling.
You could place this in your footer template for the correct theme.
how do make it so that when I edit an entry, the correct value for my custom field type is selected? I have this so far:
class JFormFieldCustom extends JFormField {
protected $type = 'Custom';
// getLabel() left out
public function getInput() {
return '<select id="'.$this->id.'" name="'.$this->name.'">'.
'<option value="1" >1</option>'.
'<option value="2" >2</option>'.
'</select>';
}
}
How do I pass the selected value to this class so I can do:
<option value="1"SELECTED>1</option>
or
<option value="2" SELECTED>2</option>
Thanks!
It's easier to use what's already there, i.e. extend JFormFieldList in place of JFormField, then all you have to do is return the option's for your list. The inherited functionality will do the rest for you - including selecting the option that matches $this->value
<?php
/**
* Do the Joomla! security check and get the FormHelper to load the class
*/
defined('_JEXEC') or die('Restricted Access');
JFormHelper::loadFieldClass('list');
class JFormFieldMyCustomField extends JFormFieldList
{
/**
* Element name
*
* #var string
*/
public $type = 'MyCustomField';
/**
* getOptions() provides the options for the select
*
* #return array
*/
protected function getOptions()
{
// Create an array for our options
$options = array();
// Add our options to the array
$options[] = array("value" => 1, "text" => "1);
$options[] = array("value" => 1, "text" => "1);
return $options;
}
}
Use $this->value to get selected value.Try this-
class JFormFieldCustom extends JFormField {
protected $type = 'Custom';
// getLabel() left out
public function getInput() {
return '<select id="'.$this->id.'" name="'.$this->name.'">'.
'<option value="1" <?php echo ($this->value==1)?'selected':''?>>1</option>'.
'<option value="2" <?php echo ($this->value==2)?'selected':''?>>2</option>'.
'</select>';
}
}
Hope this will help.
Select for Joomla
http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
* #copyright (c) 2017 YouTech Company. All Rights Reserved.
* #author macasin
*/
defined('_JEXEC') or die;
JFormHelper::loadFieldClass('list');
class JFormFieldSelect extends JFormFieldList
{
protected $type = 'select';
protected function getInput()
{
$html = array();
$attr = '';
// Initialize some field attributes.
$attr .= !empty($this->class) ? ' class=select ' . $this->class . '"' : ' class=select ';
$attr .= $this->readonly ? ' readonly' : '';
$attr .= $this->disabled ? ' disabled' : '';
$attr .= !empty($this->size) ? ' size="' . $this->size . '"' : '';
$attr .= $this->required ? ' required aria-required="true"' : '';
// Initialize JavaScript field attributes.
$attr .= $this->onchange ? ' onchange="' . $this->onchange . '"' : '';
// Get the field options.
$options = $this->getOptions();
// Load the combobox behavior.
JHtml::_('behavior.combobox');
$html[] = '<div class="select input-append">';
// Build the input for the combo box.
$html[] = '<select name="' . $this->name . '" id="' . $this->id . '" value="'
. htmlspecialchars($this->value, ENT_COMPAT, 'UTF-8') . '"' . $attr . ' autocomplete="off" >';
foreach ($options as $option)
{
$html[] = '<option '.($option->value == $this->value ? "selected" : "").' value='.$option->value.'>' . $option->text . '</option>';
}
$html[] = '</select></div>';
return implode($html);
}
}
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.