├── 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 |
17 |
18 |
19 |
20 | stripTags($oProduct->getName(), null, true); ?>
21 |
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 |
--------------------------------------------------------------------------------