I am trying to implement embedded widget. Administrators will be able to configure this widget and embed it inside WYSIWYG editor. Two of the many configuration options are list of products that should show up on frontend and list of categories.
I want to allow this selection with "adminhtml/catalog_product_widget_chooser" and "adminhtml/catalog_category_widget_chooser". I tried to implement these widgets with sparse documentation available on the web but all I managed to accomplish is implementation for selecting one product or selecting one category. I need multiselect behavior.
As far as I can see no multiselection possibility is allowed by the current implementation. I checked code for both classes and grid.phtml template and it seams it is badly written and not extensible beyond current intention of use. For example this is how you would suppose to initialize helper block for a widget parameter to allow multiple select:
<helper_block>
<type>adminhtml/catalog_product_widget_chooser</type>
<data>
<button translate="open">
<open>Select Products...</open>
</button>
<use_massaction>1</use_massaction>
</data>
</helper_block>
But product chooser is hard coded for use without mass actions with this part of the code:
public function prepareElementHtml(Varien_Data_Form_Element_Abstract $element)
{
$uniqId = Mage::helper('core')->uniqHash($element->getId());
$sourceUrl = $this->getUrl('*/catalog_product_widget/chooser', array(
'uniq_id' => $uniqId,
'use_massaction' => false,
));
...
And grid.phtml template that is supposed to have some kind of button to confirm multiple selection is just showing "Search" and "Reset filter" buttons. And there is no handling of adding another button. For example here is the default code responsible for printing button html:
public function getMainButtonsHtml()
{
$html = '';
if($this->getFilterVisibility()){
$html.= $this->getResetFilterButtonHtml();
$html.= $this->getSearchButtonHtml();
}
return $html;
}
Only these two buttons are going to be printed by default.
So I started my own implementation based on two implementations mentioned above and it is getting ugly and could end up as an unmaintainable mess of copy-pasta. And I work by principle that if things start to look ugly then I am doing something wrong.
So is there a straightforward way to implement multiple product and multiple category selection on widget configuration screen by using grid widget?
I've found a quick way to get category multiselects on widget parameters using a source model based on adminhtml/system_config_source_category. I've removed the root-level filter and added indentation for subcategories.
widget.xml:
<widgets>
<my_widget type="mymodule/block" translate="name" module="mymodule">
<name>Widget with Multiselect Categories</name>
<parameters>
<category_ids translate="label description">
<visible>1</visible>
<required>1</required>
<label>Categories</label>
<type>multiselect</type>
<source_model>mymodule/system_config_source_category</source_model>
</category_ids>
</parameters>
</my_widget>
</widgets>
The source model:
class Mynamespace_Mymodule_Model_System_Config_Source_Category
{
public function toOptionArray()
{
$collection = Mage::getResourceModel('catalog/category_collection');
$collection->addAttributeToSelect('name')
->addFieldToFilter('path', array('neq' => '1'))
->load();
$options = array();
foreach ($collection as $category) {
$depth = count(explode('/', $category->getPath())) - 2;
$indent = str_repeat('-', max($depth * 2, 0));
$options[] = array(
'label' => $indent . $category->getName(),
'value' => $category->getId()
);
}
return $options;
}
}
The result:
Source: http://www.magentocommerce.com/knowledge-base/entry/tutorial-creating-a-magento-widget-part-2
I have added an answer to this question.
Implement multiple product chooser widget Magento
I have checked the Module that is under https://github.com/dio5/magento-multiproducts-widget.
Use the FORK option instead of Download ZIP.
It works and gives us the exact results i.e Multiple Products Selection in WIDGET. If there are any errors do let me know.
Let me know if that works for you.
Thanks!
[Editing my previous comment, upon request of code here directly]
/Namespace/Modulename/etc/widget.xml
<widgets>
<catalog_product_multiproducts type="namespace_modulename/widget_catalog_product_multiproducts" translate="name description" module="namespace_modulename">
<name>Catalog Multiple Products Widget</name>
<description>Select multiple products for display</description>
<parameters>
<title translate="label">
<visible>1</visible>
<label>Title</label>
<type>text</type>
</title>
<products_count translate="label">
<visible>1</visible>
<required>1</required>
<label>No of Products</label>
<type>text</type>
</products_count>
<ids translate="label">
<visible>1</visible>
<required>1</required>
<label>Products</label>
<type>label</type>
<helper_block>
<type>namespace_modulename/adminhtml_catalog_product_widget_multiproducts_chooser</type>
<data>
<button translate="open">
<open>Select Products...</open>
</button>
</data>
</helper_block>
<sort_order>10</sort_order>
</ids>
<template translate="label description">
<required>1</required>
<visible>1</visible>
<label>Product Carousel Template</label>
<type>text</type>
<value>catalog/product/widget/products_carousel.phtml</value>
<values>
<default translate="label"> <value>catalog/product/widget/products_carousel.phtml</value>
<label>New Products Grid Template</label>
</default>
<list translate="label">
<value>catalog/product/widget/new/content/new_list.phtml</value>
<label>New Products List Template</label>
</list>
</values>
<description>Template path cannot be changed/updated</description>
</template>
</parameters>
</catalog_product_multiproducts>
</widgets>
/NameSpace/ModuleName/Block/Adminhtml/Catalog/Product/MultiProducts/Chooser.php
This function will call the DOCHOOSE() function which will help "Choose" the checked/selected products.
/**
* prepare layout for products grid
*
* #return type Mage_Adminhtml_Block_Catalog_Product_Widget_Chooser
*/
protected function _prepareLayout()
{
$this->setChild('choose_button', $this->getLayout()->createBlock('adminhtml/widget_button')
->setData(array(
'label' => Mage::helper('adminhtml')->__('Choose Selected Products'),
'onclick' => $this->getJsObjectName() . '.doChoose()'
))
);
return parent::_prepareLayout();
}
The below function needs to be used to prepare the product element's HTML, in the format {1}{2}
/**
* Prepare chooser element HTML
*
* #param Varien_Data_Form_Element_Abstract $element Form Element
* #return Varien_Data_Form_Element_Abstract
*/
public function prepareElementHtml(Varien_Data_Form_Element_Abstract $element)
{
$uniqueId = Mage::helper('core')->uniqHash($element->getId());
$sourceUrl = $this->getUrl('*/multiproducts/chooser', array(
'uniq_id' => $uniqueId,
'use_massaction' => true,
));
$chooser = $this->getLayout()->createBlock('widget/adminhtml_widget_chooser')
->setElement($element)
->setTranslationHelper($this->getTranslationHelper())
->setConfig($this->getConfig())
->setFieldsetId($this->getFieldsetId())
->setSourceUrl($sourceUrl)
->setUniqId($uniqueId);
if ($element->getValue())
{
$label = "";
$ids = explode('}{', $element->getValue());
$cleanIds = array();
foreach ($ids as $id)
{
$id = str_replace('{', '', $id);
$id = str_replace('}', '', $id);
$cleanIds[] = $id;
}
$products = $this->_getProductsByIDs($cleanIds);
if ($products)
{
$label .= '<ul>';
foreach ($products as $product)
{
$label .= '<li>' . $product->getName() . '</li>';
}
$label .= '</ul>';
$chooser->setLabel($label);
}
}
$element->setData('after_element_html', $chooser->toHtml());
return $element;
}
JS for checkbox checked/unchecked
/**
* Checkbox Check JS Callback
*
* #return string
*/
public function getCheckboxCheckCallback()
{
if ($this->getUseMassaction())
{
return "function (grid, element) {
$(grid.containerId).fire('product:changed', {element: element});
}";
}
}
JS for Row/Product Clicked/Checked/Selected
/**
* Grid Row JS Callback
*
* #return string
*/
public function getRowClickCallback()
{
if (!$this->getUseMassaction())
{
$chooserJsObject = $this->getId();
return '
function (grid, event) {
var trElement = Event.findElement(event, "tr");
var productId = trElement.down("td").innerHTML;
var productName = trElement.down("td").next().next().innerHTML;
var optionLabel = productName;
var optionValue = "product/" + productId.replace(/^\s+|\s+$/g,"");
if (grid.categoryId) {
optionValue += "/" + grid.categoryId;
}
if (grid.categoryName) {
optionLabel = grid.categoryName + " / " + optionLabel;
}
' . $chooserJsObject . '.setElementValue(optionValue);
' . $chooserJsObject . '.setElementLabel(optionLabel);
' . $chooserJsObject . '.close();
}
';
}
}
JS code, if user is interested in selecting products from specific category.
/**
* Category Tree node onClick listener js function
*
* #return string
*/
public function getCategoryClickListenerJs()
{
$js = '
function (node, e) {
{jsObject}.addVarToUrl("category_id", node.attributes.id);
{jsObject}.reload({jsObject}.url);
{jsObject}.categoryId = node.attributes.id != "none" ? node.attributes.id : false;
{jsObject}.categoryName = node.attributes.id != "none" ? node.text : false;
}
';
$js = str_replace('{jsObject}', $this->getJsObjectName(), $js);
return $js;
}
Additional JS for preparing the POST element with product ids.
/**
* return additional JS for controls
*
* #return JS
*/
public function getAdditionalJavascript()
{
$chooserJsObject = $this->getId();
$js = '
{jsObject}.initChecked = function() {
$$("#' . $chooserJsObject . '_table tbody input:checkbox").each(function(element, i) {
var values = ' . $chooserJsObject . '.getElementValue();
var capture = values.replace("{"+element.value+"}", "match");
var searchValue = "match";
if(capture.search(searchValue) != -1)
{
element.checked = true;
}
});
}
{jsObject}.initChecked();
var values = ' . $chooserJsObject . '.getElementValue();
$("' . $chooserJsObject . '").insert({bottom: "<div class=\"filter\"><input type=\"hidden\" value=\"+values+\" name=\"selected_products\" /></div>"});
$$("#' . $chooserJsObject . '_table tbody input:checkbox").invoke("observe", "change", function(event) {
var element = Event.element(event);
var label = element.up("td").next().next().next().innerHTML;
label = label.replace(/^\s\s*/, "").replace(/\s\s*$/, "");
if(element.checked)
{
{jsObject}.addValue(element.value);
{jsObject}.addLabel(label);
} else {
{jsObject}.removeValue(element.value);
{jsObject}.removeLabel(label);
}
});
{jsObject}.removeValue = function(value) {
var currentValue = ' . $chooserJsObject . '.getElementValue();
currentValue = currentValue.replace("{"+value+"}", "");
' . $chooserJsObject . '.setElementValue(currentValue);
}
{jsObject}.addValue = function(value) {
var currentValue = ' . $chooserJsObject . '.getElementValue();
currentValue = currentValue.replace("{"+value+"}", "");
currentValue = currentValue + "{"+value+"}";
' . $chooserJsObject . '.setElementValue(currentValue);
}
{jsObject}.removeLabel = function(label) {
var currentLabel = ' . $chooserJsObject . '.getElementLabelText();
currentLabel = currentLabel.replace("<li>"+label+"</li>", "");
' . $chooserJsObject . '.setElementLabel(currentLabel);
}
{jsObject}.addLabel = function(label) {
var currentLabel = ' . $chooserJsObject . '.getElementLabelText();
if(currentLabel.search("ul") != -1)
{
currentLabel = currentLabel.replace("</ul>", "");
currentLabel = currentLabel.replace("<li>"+label+"</li>", "");
} else {
currentLabel = "<ul>";
}
currentLabel = currentLabel +"<li>"+label+"</li></ul>";
' . $chooserJsObject . '.setElementLabel(currentLabel);
}
{jsObject}.doChoose = function(node,e) {
' . $chooserJsObject . '.close();
}
';
$js = str_replace('{jsObject}', $this->getJsObjectName(), $js);
return $js;
}
The above are the major functions which will help you select multiple products from the GRID within a pop up.
There is a bit more to the code which can be checked here: https://github.com/dio5/magento-multiproducts-widget
Steps:
Navigate to a CMS Page in ADMIN panel
Click on "Insert Widget" in WYSIWYG editor
Select Widget Type - Catalog Multiple Products Widget
Enter Title, Products Count
Choose a template (One can add as many templates as required as an option)
Click on "Select Products" button
Select products from the GRID
Click on "Choose Selected Products" button
Hope this helps someone!
Happy Coding...
It looks as though you're not the first to go down the path of developing your own implementation for this.
David Manners appears to have tackled the same issue, with his Manners_Widgets.
Features of the Manners_Widgets Extension:
Multiple select for products and categories
I've not had contact with David, nor have I used this solution, so can't comment on quality (or completeness) of this code... but if you haven't already seen this solution, it might save you some time (or at worst, give you a point of contact for collaboration on this issue).
Hope this helps you, good luck with it!
Here's a quick fix: don't use the product widget chooser, but use a textfield instead that allows for comma separated SKUs.
Then in your code explode the skus and get the products by sku. Return that to your template. Much easier :)
Try to https://github.com/dio5/magento-multiproducts-widget.
It seems very useful.
Related
This concerns Drupal 8.
I'm trying to manage a link item on the main navigation. I want to enable/disable an item, programmatically.
I searched but cannot find how to do that. I found MenuLinkManager, MenuLinkContent, but I can't do what I want.
Thank you all for your help.
Assuming you actually want to remove the link I would use hook_menu_links_discovered_alter()
For example:
/**
* Implements hook_menu_links_discovered_alter().
*
* #param array $links
* An array of links.
*/
function HOOK_menu_links_discovered_alter(array &$links): void {
unset($links['machine_name_to_remove']);
}
To Disable/Enable Menu items means show/hide it. So, we can do it via below code in theme file
/**
* Implements hook_preprocess_menu().
*/
function theme_preprocess_menu(&$variables) {
if (isset($variables['menu_name']) && $variables['menu_name'] === 'main') {
foreach($variables['items'] as $key => $item) {
$path = $item['url']->toString();
switch($path) {
case '/menupath':
unset($variables['items'][$key]); //Remove menu item
break;
}
}
}
}
You can install the module Special Menu Items
https://www.drupal.org/project/special_menu_items
Or do it in your theme_link function in your template.php
function myTheme_link($variables) {
if ((isset($variables['path']) && ($variables['path'] == $_GET['q'] || ($variables['path'] == '<front>' && drupal_is_front_page())))) {
return ($variables['options']['html'] ? $variables['text'] : check_plain($variables['text']));
} else {
return '<a href="' . check_plain(url($variables['path'], $variables['options'])) . '"' . drupal_attributes($variables['options']['attributes']) . '>' . ($variables['options']['html'] ? $variables['text'] : check_plain($variables['text'])) . '</a>';
}
}
You need to exclude the menu from cache if you are going to alter it dynamically:
/**
* Implements hook_preprocess_HOOK().
*/
function YOUR_MODULE_preprocess_menu(&$variables) {
foreach ($variables['items'] as $key => $item) {
if ($key == 'depot_opm.document_demande_existant_tabs') {
unset($variables['items'][$key]);
}
}
}
/**
* Implements hook_preprocess_HOOK().
*/
function YOUR_MODULE_preprocess_block(&$variables) {
// Disable the cache of the menu block.
if($variables['derivative_plugin_id'] == 'tabs-documents') {
$variables['#cache']['max-age'] = 0;
}
}
This is what worked for me: tested with Drupal 9.3.9 (April 2022).
/**
* Implements hook_menu_links_discovered_alter().
*/
function MY_MODULE_menu_links_discovered_alter(&$links) {
$links['standard.front_page']['enabled'] = 0;
}
I want to customize the list view based on condition so i followed this blog to add where condition in list view
Developer Blog From Sugar where clause for sugar list-view pages
but after doing this i am not getting search options in my custom list view. Can any one guide me on this?
Following is the modified list view(i followed mentioned blog for this)
<?php
require_once('include/MVC/View/views/view.list.php');
require_once('custom/modules/Contacts/ContactsListViewSmarty.php');
class ContactsViewList extends ViewList
{
/**
* #see ViewList::preDisplay()
*/
var $where = "";
function AccountsViewList()
{
parent::ViewList();
}
public function preDisplay(){
require_once('modules/AOS_PDF_Templates/formLetter.php');
formLetter::LVPopupHtml('Contacts');
parent::preDisplay();
if($_GET['parentTab']=='Sales'){
$this->where .= "contacts.title ='IT Developer'";
}elseif ($_GET['parentTab']=='Marketing') {
$this->where .= "contacts.title ='Director Sales'";
}
$this->lv = new ContactsListViewSmarty();
}
function listViewProcess()
{
$this->lv->setup($this->seed, 'include/ListView/ListViewGeneric.tpl', $this->where, $this->params);
echo $this->lv->display();
}
}
You are using function AccountsViewList instead of ContactsViewList.
Also you will have to copy function prepareSearchForm() from include/MVC/View/views/view.list.php to show your search form.
$this->processSearchForm(); // for search form
$this->lv->searchColumns = $this->searchForm->searchColumns;
if(!$this->headers)
return;
if(empty($_REQUEST['search_form_only']) || $_REQUEST['search_form_only'] == false){
$this->lv->ss->assign("SEARCH",true);
$this->lv->setup($this->seed, 'include/ListView/ListViewGeneric.tpl', $this->where, $this->params); // call the listview's file
$savedSearchName = empty($_REQUEST['saved_search_select_name']) ? '' : (' - ' . $_REQUEST['saved_search_select_name']); // save the last search
echo $this->lv->display(); // display your search
this code should be in listViewProcess()
I want my main menu to not include any categories that are empty. I've done this for the layered navigation very easily in the relevant phtml file by using
$_category->getProductCount()
However, for the navigation menu, I'm finding it impossible to do this as easily (I have seen the Prattski example but it does seem rather OTT).
The main menu seems to be built in Mage_Page_Block_Html_Topmenu.php, specifically in the function _getHtml. This gets all the children in the menu and if I try something like $child->getId(), I get something like "category-node-36".
It doesn't seem like I'm too far from being able to use getProductCount() and so test if it's more than zero.
Is it possible to do this? Can somebody point me to how?
If I can, I'll extend the class with my version.
To do this, go to:
app/code/core/Mage/Catalog/Block Folder and copy Navigation.php and override it in your local package.
Open Navigation.php of your package and paste below code in this file:
if ($category->getIsActive()) {
$cat = Mage::getModel('catalog/category')->load($category->getId());
$products = Mage::getResourceModel('catalog/product_collection')->addCategoryFilter($cat);
Mage::getSingleton('catalog/product_status')->addVisibleFilterToCollection($products);
Mage::getSingleton('catalog/product_visibility')->addVisibleInCatalogFilterToCollection($products);
Mage::getSingleton('cataloginventory/stock')->addInStockFilterToCollection($products);
if(count($products)==0)
return;
}
I hope my code will help you.
I finally cracked it although I'm far from convinced it's an optimum solution. Anyway, I'll described what I did here and hopefully somebody can make it more efficient. I'll give a blow-by-blow description as I was ufamiliar with quite a few areas. So apologies for the length.
As I said, in my case at least, the main menu is built via Topmenu.php in app/code/core/Mage/Page/Block/Html, specifically the method _getHtml. I very definitely don't want to modify a core file so I found out how to extend this method via a new module. (You can skip this bit if you're familiar with creating new modules.)
Configuring a new module
I needed to create a new module (I'll call it MYMOD below). As I'm overwriting the core magento page block, I had to create new folders: app/code/local/MYMOD/Page and in there two sub-folders, Block and etc (I believe they are case sensitive). And within Block another subfolder Html. You can see this is exactly mirroring the folder structure from app/code/core/Mage.
The etc folder holds the specification for the new module in a config.xml file. This is what mine looks like:
<?xml version="1.0" encoding="UTF-8"?>
<!-- The root node for Magento module configuration -->
<config>
<!--
The module's node contains basic
information about each Magento module
-->
<modules>
<!--
This must exactly match the namespace and module's folder
names, with directory separators replaced by underscores
-->
<MYMOD_Page>
<!-- The version of our module, starting at 0.0.1 -->
<version>0.0.1</version>
</MYMOD_Page>
</modules>
<global>
<blocks>
<page>
<rewrite>
<html_topmenu>MYMOD_Page_Block_Html_Topmenu</html_topmenu>
</rewrite>
</page>
</blocks>
</global>
</config>
You can find out about the whys and wherefors of this elsewhere.
Unfortunately (in my opinion!), that's not all you have to do to specify a new module in Magento. You also have to create a file called "MYMOD_Page.xml" in app/etc/modules. This is just telling Magento about your module and where to look for it. Mine looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<config>
<modules>
<MYMOD_Page>
<!-- Whether our module is active: true or false -->
<active>true</active>
<!-- Which code pool to use: core, community or local -->
<codePool>local</codePool>
</MYMOD_Page>
</modules>
</config>
OK, sorry for the irrelevant instructions on modules but I do like self-contained blow-by-blow explanations.
Overwriting the method
I can now create a new file in my module with a subclass in which I can have methods (functions) that will be used instead of the core Magento ones. The file name has to be the same as the original file, Topmenu.php and it goes in app/code/local/MYMOD/Page/Block/Html.
Remember, the object oriented structure means that all of the functions in the original core version of Topmenu.php are available to me, I don't have to copy them in my new version (great for maintainability). My version of Topmenu.php only has to contain any new functions I might want (in this case I didn't need any) and redeclare any functions I want to overwite with my own version. In my case, I needed to modify two functions: _getHtml and _getMenuItemClasses.
_getHtml needs additional checks so any empty categories aren't included.
_getMenuItemClasses needs additional checks so that the class "parent" isn't added to categories whose children are empty.
Here's how I did it (with comments). I'm sure there are better ways but I'm still fairly new to Magento.
class MYMOD_Page_Block_Html_Topmenu extends Mage_Page_Block_Html_Topmenu
// Create my subclass, in accordance with how I've defined the new module
{
/**
* 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);
}
/*
* Find out if this category has any products. I don't know an easier way.
* The id of every child returned by getID is of the form "category-node-nnn"
* where nnn is the id that can be used to select the category details.
* substr strips everything leaving just the nnn.
* Then use getModel-> getCollection with a filter on the id. Although this looks
* like it will return many, obviously category ids are unique so in fact it only
* returns the category we're currently looking at.
*/
$_gcategoryId = substr($child->getId(), 14, 6);
$_gcategories = Mage::getModel('catalog/category')->getCollection()->addFieldToFilter('entity_id', array('eq', $_gcategoryId));
foreach ($_gcategories as $_gcategory) {
$_gcategoryCount = $_gcategory->getProductCount();
}
/*
* Now only include those categories that have products.
* In my case I also wanted to include the top level categories come what may.
*/
if (($childLevel == 0) || ($_gcategoryCount > 0)) {
$html .= '<li ' . $this->_getRenderedMenuItemAttributes($child) . '>';
$html .= '<a href="' . $child->getUrl() . '" ' . $outermostClassCode . '><span>'
. $this->escapeHtml($child->getName()) . '</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;
}
/**
* Returns array of menu item's classes
*
* #param Varien_Data_Tree_Node $item
* #return array
*/
protected function _getMenuItemClasses(Varien_Data_Tree_Node $item)
{
$classes = array();
$classes[] = 'level' . $item->getLevel();
$classes[] = $item->getPositionClass();
if ($item->getIsFirst()) {
$classes[] = 'first';
}
if ($item->getIsActive()) {
$classes[] = 'active';
}
if ($item->getIsLast()) {
$classes[] = 'last';
}
if ($item->getClass()) {
$classes[] = $item->getClass();
}
if ($item->hasChildren()) {
/*
* Don't just check if there are children but, if there are, are they all empty?
* If so, then the changes in _getHtml will mean none of them will be included
* and so this one has no children displayed and so the "parent" class is not appropriate.
*/
$children = $item->getChildren(); // Get all the children from this menu category
foreach ($children as $child) { // Loop over each child and find out how many products (see _getHtml)
$_gcategoryId = substr($child->getId(), 14, 6);
$_gcategories = Mage::getModel('catalog/category')->getCollection()->addFieldToFilter('entity_id', array('eq', $_gcategoryId));
foreach ($_gcategories as $_gcategory) { // Remember, there's actually only one category that will match the child's id
$_gcategoryCount = $_gcategory->getProductCount();
}
if ($_gcategoryCount > 0) { // As soon as one child has products, then we have a parent and can stop looking
$classes[] = 'parent';
break;
}
}
}
return $classes;
}
}
I hope this is clear. It does what I want (my store is small) but any suggestions for improvement welcome.
path: app/design/frontend/rwd/default/template/page/html/topmenu/renderer.phtml
Make this query (it is 0.0004 sec), under the foreach ($children as $child) {
$mageconnection = Mage::getSingleton("core/resource")->getConnection("core_read");
$query="select count(cataloginventory_stock_item.is_in_stock) as subcount,catalog_category_flat_store_1.`name` from catalog_category_flat_store_1 INNER JOIN
catalog_category_product_index on catalog_category_product_index.category_id=catalog_category_flat_store_1.entity_id INNER JOIN
cataloginventory_stock_item on cataloginventory_stock_item.product_id=catalog_category_product_index.product_id
where cataloginventory_stock_item.is_in_stock=1 and catalog_category_product_index.category_id=";
$subCatqueryId = str_replace('category-node-', '', $child->getId());
$prodCollection = $mageconnection->fetchAll("$query'{$subCatqueryId}'");
if($prodCollection[0]["subcount"] > 0) {
$child->setLevel($childLevel);
$child->setIsFirst($counter == 1);
// these are the existing code ..
...
...
$counter++;
}
}
It is very fast and secure way to control product count.
How to add a link category_id added to the admin? (Joomla 2.5)
You could write in the function JToolBarHelper::addNew('select.add'); or other functions...
For example,
index.php?option=com_pictures&view=select&layout=edit&category_id=14
Help, please. Thanks in advance
Reply David F:
Hi, David F. I almost got it.
After clicking "Add" appears category_id=14, and the rest did not work after clicking "Edit", "Save", "Save Close" ... - category_id=0
I've been programming. Here's an example:
...
protected $catid;
public function __construct($config = array()) {
parent::__construct($config);
if (empty($this->catid)) {
$this->catid = JRequest::getInt('category_id', 0);
}
}
protected function allowAdd($data = array()) {
$user = JFactory::getUser();
$categoryId = JArrayHelper::getValue($data, 'catid', JRequest::getInt('filter_category_id'), 'int');
$allow = null;
if ($categoryId) {
$allow = $user->authorise('core.create', $this->option . '.category.' . $categoryId);
}
if ($allow === null) {
return parent::allowAdd($data);
} else {
return $allow;
}
}
protected function allowEdit($data = array(), $key = 'id') {
$recordId = (int) isset($data[$key]) ? $data[$key] : 0;
$categoryId = 0;
if ($recordId) {
$categoryId = (int) $this->getModel()->getItem($recordId)->catid;
}
if ($categoryId) {
return JFactory::getUser()->authorise('core.edit', $this->option . '.category.' . $categoryId);
} else {
return parent::allowEdit($data, $key);
}
}
protected function getRedirectToItemAppend($recordId = null, $urlVar = 'id') {
$append = parent::getRedirectToItemAppend($recordId);
$append .= '&category_id=' . $this->category_id;
return $append;
}
protected function getRedirectToListAppend() {
$append = parent::getRedirectToListAppend();
$append .= '&category_id=' . $this->category_id;
return $append;
}
I believe that the functions that you are looking for are part of JController and should be added to your controller. In your case, this would likely be the select.php controller based on the view name in your url.
You can see a good example of this in the categories component, specifically at administrator/components/com_categories/controllers/category.php.
The following is the code in com_categories. You would want to rename extension to 'category_id' and may want to grab the value from JInput instead of the current class:
/**
* Gets the URL arguments to append to an item redirect.
*
* #param integer $recordId The primary key id for the item.
* #param string $urlVar The name of the URL variable for the id.
*
* #return string The arguments to append to the redirect URL.
*
* #since 1.6
*/
protected function getRedirectToItemAppend($recordId = null, $urlVar = 'id')
{
$append = parent::getRedirectToItemAppend($recordId);
$append .= '&extension=' . $this->extension;
return $append;
}
/**
* Gets the URL arguments to append to a list redirect.
*
* #return string The arguments to append to the redirect URL.
*
* #since 1.6
*/
protected function getRedirectToListAppend()
{
$append = parent::getRedirectToListAppend();
$append .= '&extension=' . $this->extension;
return $append;
}
*EDIT:
You also have to add it as part of the form. This is the easy, but important part. Just make sure the form has a hidden input with the value or add it to the url in the action section of the form:
<form action="<?php echo JRoute::_('index.php?option=com_component&layout=edit&id='.(int) $this->item->id . '&category_id='.$this->category_id); ?>" method="post" name="adminForm">
or use this:
<input type="hidden" name="category_id" value="<?php echo $this->category_id; ?>" />
To further explain what happens, when you click an item to go to the form, you likely add on the variable that you need. Then you add it to the form so that when one of the items is clicked, it will get submitted with the form. Joomla processes this form and either saves it or not depending on the toolbar button clicked. Then Joomla redirects, either back to the form or to the list view. Without the functions in your controller, your variable gets lost.
(Original Questions) I am using jquery ui's selectable script to control specific active keywords in my webapp. View here: www.rickymason.net/letschat/main/home for reference
I have very little experience with javascript and I'm trying to figure out how to launch a function I have in my main model.
Updated function based on answers:
I have updated my code to support the new JSON/AJAX format. This required me to create an active/inactive session filter so that the user can add filters normally, and always use AJAX to update the thread list. This just made more sense to me.
Here is the code I have currently, which still is not working. I am attempting to make it so when the user clicks on a selectable category (through Jquery UI), the divID associated with the selection is passed through AJAX and returns a threadlist array that updates the div id ="board".
Here is my current Controller set up:
public function home($page = 'home')
{
$data['user_id'] = $this->tank_auth->get_user_id();
$data['username'] = $this->tank_auth->get_username();
$data['threads'] = $this->thread_model->session_load();
$data['title'] = ucfirst($page); // Capitalize the first letter
$data['page'] = $page;
$this->load->view('templates/head', $data);
$this->load->view('templates/nav', $data);
$this->load->view('main/newthread', $data);
$this->load->view('main/addfilter', $data);
$this->load->view('main/checkbox', $data);
$this->load->view('main/displayfilter',$data);
$this->load->view('main/board', $data);
$this->load->view('templates/footer');
}
public function updatefilters($filters)
{
$filterarray = split("|", $filters);
$this->thread_model->create_session_filter($filterarray);
$threadarray = $this->thread_model->get_threads();
$data['json'] = '{"content":' + $threadarray + '}';
$this->load->view('json_view', $data); // See step 4!!!
}
Here is my current model code:
public function get_threads()
{
$filter = $this->session->userdata('filter');
$num_tags = count($filter);
if ($num_tags > 0 && $num_tags <= 8) {
$sql_select = "SELECT DISTINCT t.* ";
$sql_from = " FROM ";
$sql_where = " WHERE ";
$sql_joins = "";
$sql_order = "ORDER BY t.timestamp DESC";
for ($i=0;$i<$num_tags;++$i) {
if ($i==0) {
$sql_from .= " filter AS f ";
$sql_where .= " f.tag LIKE '%" . $filter[0] . "%'";
$sql_joins .= " INNER JOIN filter_thread AS ft ON ft.filter_id = f.filter_id
INNER JOIN thread AS t ON ft.thread_id = t.thread_id";
}
else {
$sql_where .= " OR f.tag LIKE '%" . $filter[$i] . "%'";
}
}
} else {
break;
}
$sql = $sql_select . $sql_from . $sql_joins . $sql_where . $sql_order;
$query = $this->db->query($sql);
$thread = $query->result_array();
return json_encode($thread); //I am aware this is not correct
}
public function create_session_filter($filterstring)
{
$filterarray[] = $filterstring;
$filter['filter'] = $filterarray;
if ($this->session->userdata('filter') == TRUE) {
$sessionfilter = $this->session->userdata('filter');
$new = array_merge($sessionfilter, $filter['filter']);
$this->session->unset_userdata('filter');
$filter['filter'] = $new;
$this->session->set_userdata($filter);
} else {
if (!$filterstring) {} else {
$this->session->set_userdata($filter);
}
}
}
public function create_session_inactive_filter($filterstring)
{
$filterarray[] = $filterstring;
$filter['inactivefilter'] = $filterarray;
if ($this->session->userdata('inactivefilter') == TRUE) {
$sessionfilter = $this->session->userdata('inactivefilter');
$new = array_merge($sessionfilter, $filter['inactivefilter']);
$this->session->unset_userdata('inactivefilter');
$filter['inactivefilter'] = $new;
$this->session->set_userdata($filter);
} else {
if (!$filterstring) {} else {
$this->session->set_userdata($filter);
}
}
}
And here is my current view code:
application/main/json_view.php
<?php
header("Content-Type: application/json");
echo $json;
?>
aplication/main/bdisplayfilter.php
<script>
$(function() {
$( "#selectable" ).selectable({
selected: updateFilters,
unselected: updateFilters
});
function updateFilters(ev, ui){
alert ("hello");
// get the selected filters
var $selected = $('#selectable').children('.ui-selected');
// create a string that has each filter separated by a pipe ("|")
var filters = $selected.map(function(){return this.id;}).get().join("|");
$.ajax({
url: '/main/updateFilters', //see step 2
data: { filters: filters },
success: function(data){
// data is whatever json you decide to return from the server.
// An easy way to do things is have data look like this:
// { content: "<div>All my new threads that I want to show up</div>" }
// then, you can replace some element on the page with the new content
// For example, say your container has an id of threadContainer:
$('#select').replaceWith(data.content);
}
}); }
});
</script>
<ol id="selectable">
<li class="ui-state-default" id="everything">Everything!</li>
<li class="ui-state-default" id="entertainment">Entertainment</li>
<li class="ui-state-default" id="sci/tech">Sci/Tech</li>
<li class="ui-state-default" id="news">News</li>
<?php
if ($this->session->userdata('inactivefilter') == true) {
$inactivefilter = $this->session->userdata('inactivefilter');
foreach ($inactivefilter as $new)
{
echo "<li class='ui-state-default' id='custom'>$new</li>";
}
}
?>
</ol>
<?php
if ($this->session->userdata('inactivefilter') == true) {
echo "<form action='".base_url()."main/clear_filter'><input type='submit' value=clear></form>";
} ?>
EDIT: I've updated the url and data parts of the ajax call and added an additional step to enable query string parameters.
1) Make the AJAX call
You will want to make the same call for selected and unselected, since you can have multiple filters and you need things to update accordingly on both events. So, I'll define a common function that both events can call.
$(function() {
$( "#selectable" ).selectable({
selected: updateFilters,
unselected: updateFilters
});
function updatefilters(ev, ui){
// get the selected filters
var $selected = $('#selectable').children('.ui-selected');
// create a string that has each filter separated by a pipe ("|")
var filters = $selected.map(function(){return this.id;}).get().join("|");
$.ajax({
url: '/index.php',
data: { c: main, m: updateFilters, filters: filters },
success: function(data){
// data is whatever json you decide to return from the server.
// An easy way to do things is have data look like this:
// { content: "<div>All my new threads that I want to show up</div>" }
// then, you can replace some element on the page with the new content
// For example, say your container has an id of threadContainer:
$('#threadContainer').replaceWith(data.content);
}
});
}
});
2) Enable query string parameters in application/config.php
The section called Enabling Query Strings at the bottom of this article explains how to do that:
http://codeigniter.com/user_guide/general/urls.html
3) Create an action that will receive the filters
Note that I'm using a controller called Page (which would live in /application/controllers/page.php). This action (updateFilters) could live in any controller you want.
class Page extends CI_Controller {
function __construct()
{
parent::__construct();
}
function index()
{
}
function updateFilters($filters)
{
$filterarray = split("|", $filters);
create_session_filter($filterarray);
$articlesHTML = getThreadList($filterarray); // See step 4!!!
$data['json'] = '{"content":' + $articlesHTML + '}';
$this->load->view('json_view', $data); // See step 5!!!
}
/* I've updated this slightly to accept an array */
public function create_session_filter($filterarray)
{
$filter['filter'] = $filterarray;
//... the rest of your stuff you already had
}
}
4) Implement getThreadList method
I don't think you mentioned if you already had something set up for this. This would basically take an array of filters and then render a thread list based off that.
5) Create json_view (if not already there)
This will set the content type so that the browser knows the content is json.
In /application/views/json_view.php:
<?php
header("Content-Type: application/json");
echo $json;
?>