├── composer.json ├── app ├── design │ ├── adminhtml │ │ └── default │ │ │ └── default │ │ │ └── layout │ │ │ └── manners │ │ │ └── widgets.xml │ └── frontend │ │ └── base │ │ └── default │ │ └── template │ │ └── manners │ │ └── products.phtml ├── code │ └── community │ │ └── Manners │ │ └── Widgets │ │ ├── Helper │ │ └── Data.php │ │ ├── etc │ │ ├── widget.xml │ │ └── config.xml │ │ ├── Model │ │ └── Collection │ │ │ └── Product.php │ │ ├── Block │ │ ├── Products.php │ │ └── Catalog │ │ │ └── Product │ │ │ ├── Massaction.php │ │ │ └── Widget │ │ │ └── Chooser.php │ │ └── controllers │ │ └── Adminhtml │ │ └── Product │ │ └── Multiple │ │ └── WidgetController.php └── etc │ └── modules │ └── Manners_Widgets.xml ├── modman ├── README.md └── js └── manners └── adminhtml └── widgets.js /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "manners/widgets", 3 | "description": "A module for extended widget options in Magento 1.", 4 | "type": "magento-module", 5 | "require": { 6 | "php": "~5.5" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /app/design/adminhtml/default/default/layout/manners/widgets.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/code/community/Manners/Widgets/Helper/Data.php: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 16 | 17 | true 18 | community 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/code/community/Manners/Widgets/etc/widget.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | Multiple Products Widget 12 | Widget to display multiple products on one page 13 | 14 | 15 | 1 16 | 1 17 | 18 | label 19 | 20 | manners_widgets/catalog_product_widget_chooser 21 | 22 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /app/code/community/Manners/Widgets/etc/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 1.2.0 13 | 14 | 15 | 16 | 17 | 18 | 19 | Manners_Widgets_Block 20 | 21 | 22 | 23 | 24 | 25 | Manners_Widgets_Helper 26 | 27 | 28 | 29 | 30 | 31 | Manners_Widgets_Model 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | Manners_Widgets_Adminhtml 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | manners/widgets.xml 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /app/code/community/Manners/Widgets/Model/Collection/Product.php: -------------------------------------------------------------------------------- 1 | oProductCollection = Mage::getModel('catalog/product')->getCollection(); 13 | } 14 | 15 | /** 16 | * Returns a Product Collection filtered by passed product ids. 17 | * 18 | * @param string $sProductIds 19 | * 20 | * @return Mage_Catalog_Model_Resource_Product_Collection 21 | */ 22 | public function getFilteredByProductIds($sProductIds) 23 | { 24 | $aProductIds = array_map('intval', explode(',', $sProductIds)); 25 | 26 | $this->addAttributesToCollection(); 27 | 28 | $this->oProductCollection->addFieldToFilter('entity_id', array('in' => $aProductIds)); 29 | $this->oProductCollection->setVisibility(Mage::getSingleton('catalog/product_visibility')->getVisibleInCatalogIds()); 30 | 31 | $sCollectionOrder = new Zend_Db_Expr('FIELD(e.entity_id, ' . implode(',', $aProductIds).')'); 32 | $this->oProductCollection->getSelect()->order($sCollectionOrder); 33 | 34 | return $this->oProductCollection; 35 | } 36 | 37 | private function addAttributesToCollection() 38 | { 39 | $this->oProductCollection->addAttributeToSelect( 40 | Mage::getSingleton('catalog/config')->getProductAttributes() 41 | ); 42 | $this->oProductCollection->addMinimalPrice(); 43 | $this->oProductCollection->addFinalPrice(); 44 | $this->oProductCollection->addTaxPercents(); 45 | 46 | return $this; 47 | } 48 | } -------------------------------------------------------------------------------- /app/code/community/Manners/Widgets/Block/Products.php: -------------------------------------------------------------------------------- 1 | oProductCollection = Mage::getModel('manners_widgets/collection_product'); 19 | 20 | $this->setTemplate('manners/products.phtml'); 21 | parent::_construct(); 22 | } 23 | 24 | /** 25 | * Gets a collection of predefined products 26 | * 27 | * @return \Mage_Catalog_Model_Resource_Product_Collection 28 | * @throws \Mage_Core_Exception 29 | */ 30 | public function getProductCollection() 31 | { 32 | $sProductIds = $this->getProductIds(); 33 | 34 | return $this->oProductCollection->getFilteredByProductIds($sProductIds); 35 | } 36 | 37 | /** 38 | * Gets the price block to display with the selected products from 39 | * the collection. 40 | * 41 | * Overwrites \Mage_Catalog_Block_Product::getPriceHtml in order 42 | * to be able to build the price on no catalog pages. 43 | * 44 | * @param \Mage_Catalog_Model_Product $oProduct 45 | * 46 | * @return string 47 | */ 48 | public function getPriceHtml(Mage_Catalog_Model_Product $oProduct) 49 | { 50 | $oProductBlock = $this->getLayout()->createBlock('catalog/product_price'); 51 | return $oProductBlock->getPriceHtml($oProduct, true); 52 | } 53 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Manners_Widgets 2 | =============== 3 | 4 | Magento Widget extension add some useful features to the Magento_Widget module. 5 | 6 | Features 7 | -------- 8 | 9 | * Multiple selection for products, 10 | 11 | How it works 12 | ------------ 13 | 14 | *Products* 15 | 16 | The product part of the system is based around the widget of type `manners_widgets/products`. 17 | This widget is defined in the xml file `app/code/community/Manners/Widgets/etc/widget.xml`. 18 | The widget comes with a chooser with the helper type `manners_widgets/catalog_product_widget_chooser`. 19 | 20 | The chooser is based from the Magento standard chooser `Mage_Adminhtml_Block_Catalog_Product_Widget_Chooser` but to make it work with mass actions the following has been changed: 21 | 22 | * define new massaction block in `_construct`, 23 | * add custom columns in `_prepareColumns`, 24 | * add massaction items in `_prepareMassaction`, 25 | * update chooser to use custom url in `prepareElementHtml`, 26 | 27 | The chooser will end up using the controller `app/code/community/Manners/Widgets/controllers/Adminhtml/Product/Multiple/WidgetController.php`. 28 | This controller is again the same as the standard `Mage_Adminhtml_Catalog_Product_WidgetController` apart from it uses `manners_widgets/catalog_product_widget_chooser` to build the grid. 29 | 30 | The class `app/code/community/Manners/Widgets/Block/Catalog/Product/Massaction.php` is used to extend the JavaScript for row and button selection. 31 | This will make sure that the `varienGridMassaction` is updated correctly and the selection is taken into account. 32 | 33 | There is also an extension of the standard JavaScript under `js/manners/adminhtml/widgets.js` for the following: 34 | 35 | * add new parameter, 36 | * extend `varienGridMassaction.onGridRowClick` to set new parameter with the text value no id value of the row, 37 | 38 | ToDo 39 | ---- 40 | 41 | * [Multiple selection for categories](https://github.com/dmanners/Manners_Widgets/issues/9), 42 | * [Second load of selection before save](https://github.com/dmanners/Manners_Widgets/issues/8), -------------------------------------------------------------------------------- /app/design/frontend/base/default/template/manners/products.phtml: -------------------------------------------------------------------------------- 1 | getProductCollection(); 3 | $oOutputHelper = $this->helper('catalog/output'); 4 | $oProductHelper = $this->helper('catalog/product'); 5 | ?> 6 | 7 |
8 | 9 | 10 |
11 | 12 | 15 | <?php echo $this->stripTags($this->getImageLabel($oProduct, 'small_image'), null, true) ?> 17 | 18 | 19 |
20 | stripTags($oProduct->getName(), null, true); ?> 21 |

22 | 24 | productAttribute($oProduct, $oProduct->getName(), 'name'); ?> 25 | 26 |

27 | hasShortDescription()): ?> 28 |

productAttribute($oProduct, $oProduct->getShortDescription(), 'short_description') ?>

29 | 30 | load($oProduct->getId());?> 31 | getPriceHtml($productObject) ?> 32 |
33 |
34 | 35 |
36 | -------------------------------------------------------------------------------- /app/code/community/Manners/Widgets/controllers/Adminhtml/Product/Multiple/WidgetController.php: -------------------------------------------------------------------------------- 1 | getRequest(); 18 | $iUniqId = $oRequest->getParam('uniq_id'); 19 | $bMassAction = $oRequest->getParam('use_massaction', false); 20 | $iProductTypeId = $oRequest->getParam('product_type_id', null); 21 | 22 | $oLayout = $this->getLayout(); 23 | $oProductsGrid = $oLayout->createBlock( 24 | 'manners_widgets/catalog_product_widget_chooser', 25 | '', 26 | [ 27 | 'id' => $iUniqId, 28 | 'use_massaction' => $bMassAction, 29 | 'product_type_id' => $iProductTypeId, 30 | 'category_id' => $this->getRequest()->getParam('category_id') 31 | ] 32 | ); 33 | 34 | $sChooserHtml = $oProductsGrid->toHtml(); 35 | 36 | if (!$oRequest->getParam('products_grid')) { 37 | $oCategoriesTree = $oLayout->createBlock( 38 | 'adminhtml/catalog_category_widget_chooser', 39 | '', 40 | [ 41 | 'id' => $iUniqId . 'Tree', 42 | 'node_click_listener' => $oProductsGrid->getCategoryClickListenerJs(), 43 | 'with_empty_node' => true 44 | ] 45 | ); 46 | 47 | $oChooserContainer = $oLayout->createBlock('adminhtml/catalog_product_widget_chooser_container'); 48 | $oChooserContainer->setTreeHtml($oCategoriesTree->toHtml()); 49 | $oChooserContainer->setGridHtml($sChooserHtml); 50 | $sChooserHtml = $oChooserContainer->toHtml(); 51 | } 52 | 53 | $this->getResponse()->setBody($sChooserHtml); 54 | } 55 | 56 | /** 57 | * Check is allowed access to action 58 | * 59 | * @return bool 60 | */ 61 | protected function _isAllowed() 62 | { 63 | return Mage::getSingleton('admin/session')->isAllowed('cms/widget_instance'); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /js/manners/adminhtml/widgets.js: -------------------------------------------------------------------------------- 1 | varienGridMassaction = Class.create( 2 | varienGridMassaction, 3 | { 4 | textString: '' 5 | } 6 | ); 7 | 8 | varienGridMassaction.addMethods( 9 | { 10 | onGridRowClick: function(grid, evt) { 11 | var tdElement = Event.findElement(evt, 'td'); 12 | var trElement = Event.findElement(evt, 'tr'); 13 | 14 | var trChildren = trElement.children; 15 | var gridName = trChildren[trChildren.length - 1].innerHTML.trim(); 16 | if (!$(tdElement).down('input')) { 17 | if ($(tdElement).down('a') || $(tdElement).down('select')) { 18 | return; 19 | } 20 | if (trElement.title) { 21 | setLocation(trElement.title); 22 | } 23 | else { 24 | console.log('else'); 25 | var checkbox = Element.select(trElement, 'input'); 26 | var isInput = Event.element(evt).tagName == 'input'; 27 | var checked = isInput ? checkbox[0].checked : !checkbox[0].checked; 28 | 29 | if (checked) { 30 | this.checkedString = varienStringArray.add(checkbox[0].value, this.checkedString); 31 | this.textString = varienStringArray.add(checkbox[0].value, this.textString); 32 | } else { 33 | this.checkedString = varienStringArray.remove(checkbox[0].value, this.checkedString); 34 | this.textString = varienStringArray.remove(checkbox[0].value, this.textString); 35 | } 36 | this.grid.setCheckboxChecked(checkbox[0], checked); 37 | this.updateCount(); 38 | } 39 | return; 40 | } 41 | 42 | if (Event.element(evt).isMassactionCheckbox) { 43 | this.setTextValue(Event.element(evt), gridName); 44 | this.setCheckbox(Event.element(evt)); 45 | } else if (checkbox = this.findCheckbox(evt)) { 46 | checkbox.checked = !checkbox.checked; 47 | this.setTextValue(checkbox, gridName); 48 | this.setCheckbox(checkbox); 49 | } 50 | }, 51 | setTextValue: function(checkbox, textValue) { 52 | if(checkbox.checked) { 53 | this.textString = varienStringArray.add(textValue, this.textString); 54 | } else { 55 | this.textString = varienStringArray.remove(textValue, this.textString); 56 | } 57 | }, 58 | initTextValue: function(textValue) { 59 | this.textString = textValue; 60 | }, 61 | getTextValue: function() { 62 | return this.textString; 63 | } 64 | } 65 | ); -------------------------------------------------------------------------------- /app/code/community/Manners/Widgets/Block/Catalog/Product/Massaction.php: -------------------------------------------------------------------------------- 1 | sInternalText = $sInternalText; 25 | return $this; 26 | } 27 | 28 | /** 29 | * Set the field name for internal use 30 | * 31 | * @param string $sInternalName 32 | * @return Manners_Widgets_Block_Catalog_Product_Massaction 33 | */ 34 | public function setFormFieldNameInternal($sInternalName) 35 | { 36 | $this->sInternalName = $sInternalName; 37 | return $this; 38 | } 39 | 40 | /** 41 | * Get the form field name used internally 42 | * 43 | * @return string 44 | */ 45 | public function getFormFieldNameInternal() 46 | { 47 | if ($this->sInternalName !== null) { 48 | return $this->sInternalName; 49 | } 50 | return parent::getFormFieldNameInternal(); 51 | } 52 | 53 | /** 54 | * Get the form field text used internally 55 | * 56 | * @return string 57 | */ 58 | public function getFormFieldTextInternal() 59 | { 60 | return $this->sInternalText; 61 | } 62 | 63 | /** 64 | * Set-up the button 65 | * 66 | * @return string 67 | */ 68 | public function getApplyButtonHtml() 69 | { 70 | return $this->getButtonHtml( 71 | $this->__('Submit'), 72 | $this->getButtonJs() 73 | ); 74 | } 75 | 76 | /** 77 | * Get the onClick function 78 | * 79 | * @return string 80 | */ 81 | private function getButtonJs() 82 | { 83 | return sprintf( 84 | '(function(){ 85 | %1$s.setElementValue(window.%2$s.getCheckedValues()); 86 | %1$s.setElementLabel(window.%2$s.getTextValue()); 87 | %1$s.close(); 88 | })()', 89 | $this->getData('parent_id'), 90 | $this->getJsObjectName() 91 | ); 92 | } 93 | 94 | /** 95 | * Get the javascript used for the grid massaction 96 | * 97 | * @return string 98 | */ 99 | public function getJavaScript() 100 | { 101 | $sJavaScript = sprintf( 102 | 'window.%1$s = new varienGridMassaction( 103 | "%2$s", 104 | %3$s, 105 | "%4$s", 106 | "%5$s", 107 | "%6$s"); 108 | %1$s.setItems(%7$s); 109 | %1$s.setGridIds("%8$s"); 110 | %1$s.errorText = "%9$s"; 111 | %1$s.initTextValue("%10$s");', 112 | $this->getJsObjectName(), 113 | $this->getHtmlId(), 114 | $this->getGridJsObjectName(), 115 | $this->getSelectedJson(), 116 | $this->getFormFieldNameInternal(), 117 | $this->getFormFieldName(), 118 | $this->getItemsJson(), 119 | $this->getGridIdsJson(), 120 | $this->getErrorText(), 121 | $this->getSelectedTextJson() 122 | ); 123 | if ($this->getUseAjax()) { 124 | $sJavaScript .= sprintf( 125 | '%1$s.setUseAjax(true);', 126 | $this->getJsObjectName() 127 | ); 128 | } 129 | if ($this->getUseSelectAll()) { 130 | $sJavaScript .= sprintf( 131 | '%1$s.setUseSelectAll(true);', 132 | $this->getJsObjectName() 133 | ); 134 | } 135 | return $sJavaScript; 136 | } 137 | 138 | /** 139 | * Retrieve JSON string of selected checkboxes 140 | * 141 | * @return string 142 | */ 143 | private function getSelectedTextJson() 144 | { 145 | if($selected = $this->getRequest()->getParam($this->getFormFieldTextInternal())) { 146 | $selected = explode(',', $selected); 147 | return join(',', $selected); 148 | } else { 149 | return ''; 150 | } 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /app/code/community/Manners/Widgets/Block/Catalog/Product/Widget/Chooser.php: -------------------------------------------------------------------------------- 1 | setMassactionBlockName('manners_widgets/catalog_product_massaction'); 18 | } 19 | 20 | /** 21 | * Add all the desired columns to the grid 22 | * 23 | * @return Manners_Widgets_Block_Catalog_Product_Widget_Chooser 24 | * @throws Exception 25 | */ 26 | protected function _prepareColumns() 27 | { 28 | $this->addColumn( 29 | 'entity_id', 30 | [ 31 | 'header' => Mage::helper('catalog')->__('ID'), 32 | 'sortable' => true, 33 | 'width' => '60px', 34 | 'index' => 'entity_id', 35 | ] 36 | ); 37 | $this->addColumn( 38 | 'chooser_sku', 39 | [ 40 | 'header' => Mage::helper('catalog')->__('SKU'), 41 | 'name' => 'chooser_sku', 42 | 'width' => '80px', 43 | 'index' => 'sku' 44 | ] 45 | ); 46 | $this->addColumn( 47 | 'chooser_name', 48 | [ 49 | 'header' => Mage::helper('catalog')->__('Product Name'), 50 | 'name' => 'chooser_name', 51 | 'index' => 'name' 52 | ] 53 | ); 54 | return $this; 55 | } 56 | 57 | /** 58 | * Prepare the massaction 59 | * - Block, 60 | * - Item 61 | * 62 | * @return Manners_Widgets_Block_Catalog_Product_Widget_Chooser 63 | */ 64 | protected function _prepareMassaction() 65 | { 66 | $this->setMassactionIdField('entity_id'); 67 | $this->setMassactionIdFilter('entity_id'); 68 | 69 | /** @var Manners_Widgets_Block_Catalog_Product_Massaction $oMassActionBlock */ 70 | $oMassActionBlock = $this->getMassactionBlock(); 71 | $oMassActionBlock->setFormFieldName('products'); 72 | $oMassActionBlock->setFormFieldNameInternal('element_value'); 73 | $oMassActionBlock->setFormFieldTextInternal('element_label'); 74 | $oMassActionBlock->setData('parent_id', $this->getId()); 75 | 76 | /** 77 | * This is a dummy item that we do not actually need because of JavaScript processing 78 | */ 79 | $oMassActionBlock->addItem( 80 | 'add', 81 | [ 82 | 'label' => Mage::helper('catalog')->__('Add Products'), 83 | 'url' => $this->getUrl('*/*/addProducts') 84 | ] 85 | ); 86 | 87 | Mage::dispatchEvent( 88 | 'manners_widgets_catalog_product_grid_prepare_massaction', 89 | ['block' => $this] 90 | ); 91 | return $this; 92 | } 93 | 94 | /** 95 | * Prepare chooser element HTML 96 | * - set the product names on the label 97 | * 98 | * @param Varien_Data_Form_Element_Abstract $oElement Form Element 99 | * @return Varien_Data_Form_Element_Abstract 100 | */ 101 | public function prepareElementHtml(Varien_Data_Form_Element_Abstract $oElement) 102 | { 103 | $oChooser = $this->getChooserBlock($oElement); 104 | 105 | if ($oElement->getValue()) { 106 | $aElementValues = explode(',', $oElement->getValue()); 107 | $aLabels = []; 108 | foreach ($aElementValues as $iProductId) { 109 | $aLabels[] = Mage::getResourceSingleton('catalog/product')->getAttributeRawValue( 110 | $iProductId, 111 | 'name', 112 | Mage::app()->getStore() 113 | ); 114 | } 115 | $oChooser->setLabel(implode(',', $aLabels)); 116 | } 117 | 118 | $oElement->setData('after_element_html', $oChooser->toHtml()); 119 | return $oElement; 120 | } 121 | 122 | /** 123 | * Get the chooser block to be used 124 | * 125 | * @param Varien_Data_Form_Element_Abstract $oElement 126 | * @return Mage_Core_Block_Abstract 127 | */ 128 | private function getChooserBlock(Varien_Data_Form_Element_Abstract $oElement) 129 | { 130 | $iUniqId = Mage::helper('core')->uniqHash($oElement->getId()); 131 | $sSourceUrl = $this->getUrl( 132 | '*/product_multiple_widget/chooser', 133 | [ 134 | 'uniq_id' => $iUniqId, 135 | 'use_massaction' => true, 136 | ] 137 | ); 138 | 139 | $oLayout = $this->getLayout(); 140 | $oChooser = $oLayout->createBlock('widget/adminhtml_widget_chooser'); 141 | $oChooser->setElement($oElement); 142 | $oChooser->setTranslationHelper($this->getTranslationHelper()); 143 | $oChooser->setConfig($this->getConfig()); 144 | $oChooser->setFieldsetId($this->getFieldsetId()); 145 | $oChooser->setSourceUrl($sSourceUrl); 146 | $oChooser->setUniqId($iUniqId); 147 | return $oChooser; 148 | } 149 | } 150 | --------------------------------------------------------------------------------