├── src └── MX │ └── MegaMenu │ ├── view │ ├── adminhtml │ │ ├── web │ │ │ ├── fonts │ │ │ │ ├── fa-solid.eot │ │ │ │ ├── fa-solid.ttf │ │ │ │ ├── fa-solid.woff │ │ │ │ └── fa-solid.woff2 │ │ │ ├── css │ │ │ │ ├── megamenu-core.css │ │ │ │ ├── lib │ │ │ │ │ └── jquery.nestable.min.css │ │ │ │ ├── form │ │ │ │ │ └── element │ │ │ │ │ │ └── toggle.css │ │ │ │ └── megamenu.css │ │ │ └── js │ │ │ │ ├── megamenu │ │ │ │ ├── dialog.js │ │ │ │ ├── form.js │ │ │ │ └── form │ │ │ │ │ └── builder.js │ │ │ │ ├── megamenu.js │ │ │ │ └── megamenu-editor.js │ │ ├── templates │ │ │ ├── editor │ │ │ │ ├── settings │ │ │ │ │ └── form.phtml │ │ │ │ └── form.phtml │ │ │ ├── export.phtml │ │ │ ├── import.phtml │ │ │ └── editor.phtml │ │ ├── layout │ │ │ ├── mx_megamenu_menu_create.xml │ │ │ ├── mx_megamenu_menu_edit.xml │ │ │ ├── default.xml │ │ │ ├── mx_megamenu_importexport.xml │ │ │ ├── mx_megamenu_menu_index.xml │ │ │ ├── mx_megamenu_menu_form.xml │ │ │ ├── mx_megamenu_menu_export.xml │ │ │ ├── mx_megamenu_menu_import.xml │ │ │ └── mx_megamenu_edit.xml │ │ ├── requirejs-config.js │ │ └── ui_component │ │ │ └── mx_megamenu_menu_listing.xml │ └── frontend │ │ ├── requirejs-config.js │ │ ├── templates │ │ ├── topmenu.phtml │ │ └── topmenu │ │ │ ├── structure.phtml │ │ │ └── structure │ │ │ └── children.phtml │ │ ├── layout │ │ └── default.xml │ │ └── web │ │ ├── css │ │ └── megamenu.css │ │ └── js │ │ └── view │ │ └── megamenu.js │ ├── registration.php │ ├── etc │ ├── module.xml │ ├── config.xml │ ├── adminhtml │ │ ├── routes.xml │ │ ├── system.xml │ │ └── menu.xml │ └── di.xml │ ├── Api │ ├── ImportHandlerInterface.php │ ├── MenuRepositoryInterface.php │ └── Data │ │ └── MenuInterface.php │ ├── Controller │ └── Adminhtml │ │ ├── Menu │ │ ├── Index.php │ │ ├── Export.php │ │ ├── Import.php │ │ ├── Create.php │ │ ├── Form.php │ │ ├── Delete.php │ │ ├── Edit.php │ │ ├── ImportPost.php │ │ ├── ExportPost.php │ │ └── Save.php │ │ └── Menu.php │ ├── Model │ ├── ResourceModel │ │ ├── Menu │ │ │ ├── Collection.php │ │ │ └── Relation │ │ │ │ ├── Item │ │ │ │ ├── ReadHandler.php │ │ │ │ └── SaveHandler.php │ │ │ │ └── Store │ │ │ │ ├── ReadHandler.php │ │ │ │ └── SaveHandler.php │ │ └── Menu.php │ ├── MenuRepository.php │ ├── Menu │ │ ├── ImportHandler.php │ │ └── Item.php │ └── Menu.php │ ├── Block │ ├── Adminhtml │ │ └── Menu │ │ │ ├── Export.php │ │ │ ├── Import.php │ │ │ ├── Edit │ │ │ ├── Settings │ │ │ │ └── Form.php │ │ │ └── Form.php │ │ │ └── Edit.php │ ├── TopMenu │ │ └── Children.php │ └── TopMenu.php │ ├── Data │ └── Form │ │ └── Element │ │ ├── Toggle.php │ │ └── Chooser │ │ └── Category.php │ ├── Ui │ └── Component │ │ └── Listing │ │ └── Column │ │ └── Actions.php │ └── Setup │ ├── UpgradeSchema.php │ └── InstallSchema.php ├── .gitignore ├── README.md └── composer.json /src/MX/MegaMenu/view/adminhtml/web/fonts/fa-solid.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inviqa/mx-megamenu/HEAD/src/MX/MegaMenu/view/adminhtml/web/fonts/fa-solid.eot -------------------------------------------------------------------------------- /src/MX/MegaMenu/view/adminhtml/web/fonts/fa-solid.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inviqa/mx-megamenu/HEAD/src/MX/MegaMenu/view/adminhtml/web/fonts/fa-solid.ttf -------------------------------------------------------------------------------- /src/MX/MegaMenu/view/adminhtml/web/fonts/fa-solid.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inviqa/mx-megamenu/HEAD/src/MX/MegaMenu/view/adminhtml/web/fonts/fa-solid.woff -------------------------------------------------------------------------------- /src/MX/MegaMenu/view/adminhtml/web/fonts/fa-solid.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inviqa/mx-megamenu/HEAD/src/MX/MegaMenu/view/adminhtml/web/fonts/fa-solid.woff2 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Composer packages directory 2 | /vendor 3 | 4 | # Composer executable files directory 5 | /bin 6 | 7 | # Installed Composer packages versions 8 | /composer.lock -------------------------------------------------------------------------------- /src/MX/MegaMenu/view/frontend/requirejs-config.js: -------------------------------------------------------------------------------- 1 | var config = { 2 | map: { 3 | '*': { 4 | 'megaMenu': 'MX_MegaMenu/js/view/megamenu' 5 | } 6 | } 7 | }; 8 | -------------------------------------------------------------------------------- /src/MX/MegaMenu/registration.php: -------------------------------------------------------------------------------- 1 | 4 | 5 |
6 | getFormHtml();?> 7 |
8 | -------------------------------------------------------------------------------- /src/MX/MegaMenu/view/adminhtml/layout/mx_megamenu_menu_create.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/MX/MegaMenu/view/adminhtml/layout/mx_megamenu_menu_edit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/MX/MegaMenu/view/adminhtml/layout/default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/MX/MegaMenu/etc/module.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/MX/MegaMenu/view/adminhtml/layout/mx_megamenu_importexport.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/MX/MegaMenu/Api/ImportHandlerInterface.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 0 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/MX/MegaMenu/view/frontend/templates/topmenu.phtml: -------------------------------------------------------------------------------- 1 | 4 | 5 |
6 | 11 |
12 | 13 | 14 | -------------------------------------------------------------------------------- /src/MX/MegaMenu/view/adminhtml/layout/mx_megamenu_menu_index.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/MX/MegaMenu/etc/adminhtml/routes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/MX/MegaMenu/view/adminhtml/layout/mx_megamenu_menu_form.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/MX/MegaMenu/Controller/Adminhtml/Menu/Index.php: -------------------------------------------------------------------------------- 1 | resultFactory->create(ResultFactory::TYPE_PAGE); 13 | $resultPage->getConfig()->getTitle()->prepend((__('MX Mega Menu'))); 14 | 15 | return $resultPage; 16 | } 17 | } -------------------------------------------------------------------------------- /src/MX/MegaMenu/view/adminhtml/layout/mx_megamenu_menu_export.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/MX/MegaMenu/view/adminhtml/layout/mx_megamenu_menu_import.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/MX/MegaMenu/view/adminhtml/requirejs-config.js: -------------------------------------------------------------------------------- 1 | var config = { 2 | map: { 3 | "*": { 4 | "nestable": "MX_MegaMenu/js/lib/jquery.nestable.min", 5 | "MXMegaMenu": "MX_MegaMenu/js/megamenu", 6 | "MXMegaMenuEditor": "MX_MegaMenu/js/megamenu-editor", 7 | "MXMegaMenuForm": "MX_MegaMenu/js/megamenu/form", 8 | "MXMegaMenuFormDialog": "MX_MegaMenu/js/megamenu/dialog", 9 | "MXMegaMenuFormBuilder": "MX_MegaMenu/js/megamenu/form/builder" 10 | } 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /src/MX/MegaMenu/Controller/Adminhtml/Menu/Export.php: -------------------------------------------------------------------------------- 1 | resultFactory->create(ResultFactory::TYPE_PAGE); 13 | $resultPage->getConfig()->getTitle()->prepend(__('MX Mega Menu - Export')); 14 | 15 | return $resultPage; 16 | } 17 | } -------------------------------------------------------------------------------- /src/MX/MegaMenu/Controller/Adminhtml/Menu/Import.php: -------------------------------------------------------------------------------- 1 | resultFactory->create(ResultFactory::TYPE_PAGE); 13 | $resultPage->getConfig()->getTitle()->prepend(__('MX Mega Menu - Import')); 14 | 15 | return $resultPage; 16 | } 17 | } -------------------------------------------------------------------------------- /src/MX/MegaMenu/Controller/Adminhtml/Menu/Create.php: -------------------------------------------------------------------------------- 1 | resultFactory->create(ResultFactory::TYPE_PAGE); 13 | $resultPage->getConfig()->getTitle()->prepend((__('MX Mega Menu - Create New Menu'))); 14 | 15 | return $resultPage; 16 | } 17 | } -------------------------------------------------------------------------------- /src/MX/MegaMenu/view/adminhtml/templates/editor/form.phtml: -------------------------------------------------------------------------------- 1 | 4 | 5 |
6 | getFormHtml();?> 7 | 14 |
15 | -------------------------------------------------------------------------------- /src/MX/MegaMenu/view/adminhtml/templates/export.phtml: -------------------------------------------------------------------------------- 1 | 5 | 6 |
7 |
8 | 9 |
10 |
11 | 12 |
13 | 14 |
-------------------------------------------------------------------------------- /src/MX/MegaMenu/view/adminhtml/web/css/megamenu-core.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'font-awesome-icons'; 3 | src: url('../fonts/fa-solid.eot?diou9j'); 4 | src: url('../fonts/fa-solid.eot?diou9j#iefix') format('embedded-opentype'), 5 | url('../fonts/fa-solid.ttf?diou9j') format('truetype'), 6 | url('../fonts/fa-solid.woff?diou9j') format('woff'), 7 | url('../fonts/fa-solid.woff2?diou9j') format('woff2'), 8 | url('../fonts/fa-solid.svg?diou9j#icomoon') format('svg'); 9 | font-weight: normal; 10 | font-style: normal; 11 | } 12 | 13 | .admin__menu .level-0.item-mxmenuall > a:before { 14 | font-family: 'font-awesome-icons'; 15 | content: '\f0c9'; 16 | } -------------------------------------------------------------------------------- /src/MX/MegaMenu/Model/ResourceModel/Menu/Collection.php: -------------------------------------------------------------------------------- 1 | _init( 21 | 'MX\MegaMenu\Model\Menu', 22 | 'MX\MegaMenu\Model\ResourceModel\Menu' 23 | ); 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /src/MX/MegaMenu/view/adminhtml/templates/import.phtml: -------------------------------------------------------------------------------- 1 | 5 | 6 |
7 |
8 | 9 |
10 |
11 |
12 | 13 | getBlockHtml('formkey')?> 14 |
15 |
16 | 17 |
18 |
19 |
-------------------------------------------------------------------------------- /src/MX/MegaMenu/view/adminhtml/layout/mx_megamenu_edit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/MX/MegaMenu/Controller/Adminhtml/Menu/Form.php: -------------------------------------------------------------------------------- 1 | resultLayoutFactory = $resultLayoutFactory; 25 | parent::__construct($context); 26 | } 27 | 28 | public function execute() 29 | { 30 | $resultLayout = $this->resultLayoutFactory->create(); 31 | $resultLayout->addHandle('overlay_popup'); 32 | 33 | return $resultLayout; 34 | } 35 | } -------------------------------------------------------------------------------- /src/MX/MegaMenu/view/frontend/layout/default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MX_MegaMenu 2 | The module provides useful topmenu to be able to create complex menu structure in Magento 2. 3 | 4 | ## Compatibility 5 | Magento 2.X (tested in magento 2.2.11) 6 | 7 | ## Installation ## 8 | 9 | ### Composer ### 10 | 11 | 1. Make sure that you have added [Github's OAuth token](https://getcomposer.org/doc/articles/troubleshooting.md#api-rate-limit-and-oauth-tokens) to your composer configuration. 12 | 13 | 14 | 2. Add the Git repository to composer as the source of this package 15 | ``` 16 | composer config repositories.mx-megamenu vcs git@github.com:inviqa/mx-megamenu.git 17 | ``` 18 | 19 | 3. Run the command below from your project root directory. 20 | ``` 21 | composer require "inviqa/mx-megamenu" 22 | ``` 23 | 24 | ## Usage 25 | Enable the menu in the admin: 26 | MegaMenu > Settings > Enable Mega Menu 27 | 28 | The default topmenu will be automatically removed when the module is enabled: 29 | To move the megamenu in your layout structure the reference name is *mx.megamenu.topnav* 30 | 31 | -------------------------------------------------------------------------------- /src/MX/MegaMenu/Block/Adminhtml/Menu/Export.php: -------------------------------------------------------------------------------- 1 | _objectId = 'menu_id'; 23 | $this->_controller = 'adminhtml_menu'; 24 | $this->_blockGroup = 'MX_MegaMenu'; 25 | 26 | parent::_construct(); 27 | 28 | $this->removeButton('reset'); 29 | $this->removeButton('delete'); 30 | $this->removeButton('save'); 31 | } 32 | 33 | /** 34 | * Get Export Url 35 | * 36 | * @return string 37 | */ 38 | public function getExportUrl() 39 | { 40 | return $this->getUrl('mx_megamenu/menu/exportpost'); 41 | } 42 | } -------------------------------------------------------------------------------- /src/MX/MegaMenu/Block/Adminhtml/Menu/Import.php: -------------------------------------------------------------------------------- 1 | _objectId = 'menu_id'; 23 | $this->_controller = 'adminhtml_menu'; 24 | $this->_blockGroup = 'MX_MegaMenu'; 25 | 26 | parent::_construct(); 27 | 28 | $this->removeButton('reset'); 29 | $this->removeButton('delete'); 30 | $this->removeButton('save'); 31 | } 32 | 33 | /** 34 | * Get Import Url 35 | * 36 | * @return string 37 | */ 38 | public function getImportUrl() 39 | { 40 | return $this->getUrl('mx_megamenu/menu/importpost'); 41 | } 42 | } -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "inviqa/mx-megamenu", 3 | "description": "Inviqa - MX Mega Menu", 4 | "type": "magento2-module", 5 | "repositories": { 6 | "magento-repo": { 7 | "type": "composer", 8 | "url": "https://repo.magento.com/" 9 | } 10 | }, 11 | "license": "MIT", 12 | "authors": [ 13 | { 14 | "name": "Laszlo Kispal", 15 | "email": "lkispal@inviqa.com", 16 | "homepage": "https://github.com/kispali", 17 | "role": "Developer" 18 | } 19 | ], 20 | "require": { 21 | "symfony/yaml": "~2.1|~3.0", 22 | "magento/framework": "^100.1|^101.0", 23 | "magento/module-backend": "^100.1|^100.2", 24 | "magento/module-cms": "^101.0|^102.0", 25 | "magento/module-ui": "^100.1|^101.0", 26 | "magento/module-catalog": "^101.0|^102.0" 27 | }, 28 | "config": { 29 | "bin-dir": "bin", 30 | "use-include-path": true 31 | }, 32 | "autoload": { 33 | "files": [ 34 | "src/MX/MegaMenu/registration.php" 35 | ], 36 | "psr-0": { 37 | "MX\\MegaMenu\\": "src" 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/MX/MegaMenu/Block/TopMenu/Children.php: -------------------------------------------------------------------------------- 1 | item = $item; 29 | } 30 | 31 | /** 32 | * Get item 33 | * 34 | * @return array 35 | */ 36 | public function getItem() 37 | { 38 | return $this->item; 39 | } 40 | 41 | /** 42 | * Get item level 43 | * 44 | * @param array $item 45 | * @return integer 46 | */ 47 | public function getItemLevel($item) 48 | { 49 | return isset($item['level']) ? $item['level'] : Item::LEVEL_DEFAULT; 50 | } 51 | } -------------------------------------------------------------------------------- /src/MX/MegaMenu/Model/ResourceModel/Menu/Relation/Item/ReadHandler.php: -------------------------------------------------------------------------------- 1 | resourceMenu = $resourceMenu; 25 | } 26 | 27 | /** 28 | * @param object $entity 29 | * @param array $arguments 30 | * @return object 31 | */ 32 | public function execute($entity, $arguments = []) 33 | { 34 | if ($entity->getMenuId()) { 35 | $items = $this->resourceMenu->lookupMenuItems((int)$entity->getMenuId()); 36 | $entity->addSpecialMenuItems($items); 37 | $entity->setMenuItems($items); 38 | } 39 | return $entity; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/MX/MegaMenu/Model/ResourceModel/Menu/Relation/Store/ReadHandler.php: -------------------------------------------------------------------------------- 1 | resourceMenu = $resourceMenu; 25 | } 26 | 27 | /** 28 | * @param object $entity 29 | * @param array $arguments 30 | * @return object 31 | */ 32 | public function execute($entity, $arguments = []) 33 | { 34 | if ($entity->getMenuId()) { 35 | $stores = $this->resourceMenu->lookupStoreIds((int)$entity->getMenuId()); 36 | $entity->setData('store_id', $stores); 37 | $entity->setData('stores', $stores); 38 | } 39 | return $entity; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/MX/MegaMenu/etc/adminhtml/system.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | separator-top 9 | 10 | inviqa 11 | MX_MegaMenu::megamenu_config 12 | 13 | 14 | 15 | 16 | Magento\Config\Model\Config\Source\Yesno 17 | 18 | 19 |
20 |
21 |
22 | -------------------------------------------------------------------------------- /src/MX/MegaMenu/etc/adminhtml/menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/MX/MegaMenu/Data/Form/Element/Toggle.php: -------------------------------------------------------------------------------- 1 | getHtmlId(); 16 | 17 | $beforeElementHtml = $this->getBeforeElementHtml(); 18 | if ($beforeElementHtml) { 19 | $html .= ''; 20 | } 21 | 22 | $html .= '
'; 23 | $html .= '_getUiId() . ' value="' . 24 | $this->getEscapedValue() . '" ' . $this->serialize($this->getHtmlAttributes()) . ' class="onoffswitch-checkbox" />'; 25 | $html .= ''; 27 | $html .= '
'; 28 | 29 | $afterElementJs = $this->getAfterElementJs(); 30 | if ($afterElementJs) { 31 | $html .= $afterElementJs; 32 | } 33 | 34 | $afterElementHtml = $this->getAfterElementHtml(); 35 | if ($afterElementHtml) { 36 | $html .= ''; 37 | } 38 | 39 | return $html; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/MX/MegaMenu/view/adminhtml/web/css/lib/jquery.nestable.min.css: -------------------------------------------------------------------------------- 1 | .dd{position:relative;display:block;margin:0;padding:0;max-width:600px;list-style:none;font-size:13px;line-height:20px}.dd-list{display:block;position:relative;margin:0;padding:0;list-style:none}.dd-list .dd-list{padding-left:30px}.dd-empty,.dd-item,.dd-placeholder{display:block;position:relative;margin:0;padding:0;min-height:20px;font-size:13px;line-height:20px}.dd-handle{display:block;height:30px;margin:5px 0;padding:5px 10px;color:#333;text-decoration:none;font-weight:700;border:1px solid #ccc;background:#fafafa;border-radius:3px;box-sizing:border-box}.dd-handle:hover{color:#2ea8e5;background:#fff}.dd-item>button{position:relative;cursor:pointer;float:left;width:25px;height:20px;margin:5px 0;padding:0;text-indent:100%;white-space:nowrap;overflow:hidden;border:0;background:0 0;font-size:12px;line-height:1;text-align:center;font-weight:700}.dd-item>button:before{display:block;position:absolute;width:100%;text-align:center;text-indent:0}.dd-item>button.dd-expand:before{content:'+'}.dd-item>button.dd-collapse:before{content:'-'}.dd-expand{display:none}.dd-collapsed .dd-collapse,.dd-collapsed .dd-list{display:none}.dd-collapsed .dd-expand{display:block}.dd-empty,.dd-placeholder{margin:5px 0;padding:0;min-height:30px;background:#f2fbff;border:1px dashed #b6bcbf;box-sizing:border-box;-moz-box-sizing:border-box}.dd-empty{border:1px dashed #bbb;min-height:100px;background-color:#e5e5e5;background-size:60px 60px;background-position:0 0,30px 30px}.dd-dragel{position:absolute;pointer-events:none;z-index:9999}.dd-dragel>.dd-item .dd-handle{margin-top:0}.dd-dragel .dd-handle{box-shadow:2px 4px 6px 0 rgba(0,0,0,.1)}.dd-nochildren .dd-placeholder{display:none} -------------------------------------------------------------------------------- /src/MX/MegaMenu/Api/MenuRepositoryInterface.php: -------------------------------------------------------------------------------- 1 | menuFactory = $menuFactory; 34 | parent::__construct($context, $registry, $jsonDataHelper); 35 | } 36 | 37 | public function execute() 38 | { 39 | $resultRedirect = $this->resultRedirectFactory->create(); 40 | 41 | $id = $this->getRequest()->getParam('menu_id'); 42 | if ($id) { 43 | $menuModel = $this->menuFactory->create(); 44 | $menuModel->load($id); 45 | 46 | if (!$menuModel->getMenuId()) { 47 | $this->messageManager->addErrorMessage(__('The menu with this specific ID does not exist.')); 48 | } else { 49 | $menuModel->delete(); 50 | $this->messageManager->addSuccessMessage(__('The menu with this specific ID was successfully deleted.')); 51 | } 52 | } 53 | 54 | return $resultRedirect->setPath('*/*/'); 55 | } 56 | } -------------------------------------------------------------------------------- /src/MX/MegaMenu/view/adminhtml/web/js/megamenu/dialog.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'jquery', 3 | 'jquery/ui' 4 | ], function ($) { 5 | "use strict"; 6 | 7 | // Widget for separate dialog instance to avoid the conflicts with other dialog instances opened by MediaBrowserUtility 8 | $.widget('mx.megaMenuDialog', { 9 | dialogId: 'mega-menu-dialog', 10 | title: 'Insert/Edit Form Data', 11 | 12 | _create: function() { 13 | this.getModalInstance(); 14 | }, 15 | 16 | /** 17 | * Get modal instance 18 | */ 19 | getModalInstance: function() { 20 | var content = ''; 21 | 22 | if (menuModal) { 23 | menuModal.html($(content).html()); 24 | } else { 25 | menuModal = $(content).modal($.extend({ 26 | title: this.title, 27 | modalClass: 'magento', 28 | type: 'slide', 29 | buttons: [] 30 | }, [])); 31 | } 32 | }, 33 | 34 | openDialog: function(url, title) { 35 | if (typeof title !== 'undefined' && title !== '') { 36 | this.title += ' - ' + title; 37 | } 38 | 39 | menuModal.modal('setTitle', this.title); 40 | menuModal.modal('openModal'); 41 | 42 | $.ajax({ 43 | url: url, 44 | type: 'get', 45 | context: $(this), 46 | showLoader: true 47 | }).done(function (data) { 48 | menuModal.html(data).trigger('contentUpdated'); 49 | }); 50 | }, 51 | 52 | /** 53 | * Close dialog. 54 | */ 55 | closeDialog: function() { 56 | menuModal.modal('closeModal'); 57 | } 58 | }); 59 | 60 | return $.mx.megaMenuDialog; 61 | }); -------------------------------------------------------------------------------- /src/MX/MegaMenu/Controller/Adminhtml/Menu/Edit.php: -------------------------------------------------------------------------------- 1 | menuFactory = $menuFactory; 35 | parent::__construct($context, $registry, $jsonDataHelper); 36 | } 37 | 38 | public function execute() 39 | { 40 | $id = $this->getRequest()->getParam('menu_id'); 41 | if ($id) { 42 | $menuModel = $this->menuFactory->create(); 43 | $menuModel->load($id); 44 | if (!$menuModel->getId()) { 45 | $this->messageManager->addErrorMessage(__('The menu with this specific ID does not exist.')); 46 | $resultRedirect = $this->resultRedirectFactory->create(); 47 | 48 | return $resultRedirect->setPath('*/*/'); 49 | } 50 | 51 | // Register data to pass through to the Form 52 | $this->coreRegistry->register('mx_megamenu_menu', $menuModel); 53 | } 54 | 55 | $resultPage = $this->resultFactory->create(ResultFactory::TYPE_PAGE); 56 | $resultPage->getConfig()->getTitle()->prepend((__('MX Mega Menu - Edit Menu'))); 57 | 58 | return $resultPage; 59 | } 60 | } -------------------------------------------------------------------------------- /src/MX/MegaMenu/Controller/Adminhtml/Menu.php: -------------------------------------------------------------------------------- 1 | coreRegistry = $coreRegistry; 45 | $this->jsonDataHelper = $jsonDataHelper; 46 | parent::__construct($context); 47 | } 48 | 49 | /** 50 | * Send Response 51 | * 52 | * @param array $response 53 | * 54 | * @return Http 55 | */ 56 | public function sendHtmlResponse($response) 57 | { 58 | return $this->getResponse()->representJson( 59 | $this->jsonDataHelper->jsonEncode($response) 60 | ); 61 | } 62 | 63 | /** 64 | * Init page 65 | * 66 | * @param Page $resultPage 67 | * @return Page 68 | */ 69 | protected function initPage($resultPage) 70 | { 71 | $resultPage->setActiveMenu('MX_MegaMenu::menu') 72 | ->addBreadcrumb(__('Mega Menu'), __('Mega Menu')) 73 | ->addBreadcrumb(__('Menu'), __('Menu')); 74 | return $resultPage; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/MX/MegaMenu/Controller/Adminhtml/Menu/ImportPost.php: -------------------------------------------------------------------------------- 1 | importHandler = $importHandler; 34 | 35 | parent::__construct($context, $coreRegistry, $jsonDataHelper); 36 | } 37 | 38 | public function execute() 39 | { 40 | try { 41 | $file = $this->getRequest()->getFiles('file'); 42 | 43 | if ($file) { 44 | /** @var $importHandler \MX\MegaMenu\Model\Menu\ImportHandler */ 45 | $importHandler = $this->importHandler->create(); 46 | $importHandler->importFromFile($file); 47 | 48 | $this->messageManager->addSuccessMessage(__('You successfully imported the menu items.')); 49 | } 50 | } catch (LocalizedException $e) { 51 | $this->messageManager->addErrorMessage($e->getMessage()); 52 | } catch (\Exception $e) { 53 | $this->messageManager->addExceptionMessage($e, __('Something went wrong while importing the menu items.')); 54 | } 55 | 56 | /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */ 57 | $resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT); 58 | 59 | return $resultRedirect->setPath('mx_megamenu/menu/import'); 60 | } 61 | } -------------------------------------------------------------------------------- /src/MX/MegaMenu/view/frontend/templates/topmenu/structure.phtml: -------------------------------------------------------------------------------- 1 | getStructure(); 6 | 7 | ?> 8 | 9 | 10 | 11 | 12 |
  • 13 | 14 | 15 | 16 | canShowContent($item)): ?> 17 | 42 | 43 |
  • 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /src/MX/MegaMenu/Model/ResourceModel/Menu/Relation/Store/SaveHandler.php: -------------------------------------------------------------------------------- 1 | metadataPool = $metadataPool; 36 | $this->resourceMenu = $resourceMenu; 37 | } 38 | 39 | /** 40 | * @param object $entity 41 | * @param array $arguments 42 | * @return object 43 | * @throws \Exception 44 | */ 45 | public function execute($entity, $arguments = []) 46 | { 47 | $entityMetadata = $this->metadataPool->getMetadata(MenuInterface::class); 48 | $linkField = $entityMetadata->getLinkField(); 49 | 50 | $connection = $entityMetadata->getEntityConnection(); 51 | 52 | $oldStores = $this->resourceMenu->lookupStoreIds((int)$entity->getMenuId()); 53 | $newStores = (array)$entity->getStores(); 54 | 55 | $table = $this->resourceMenu->getTable('mx_megamenu_store'); 56 | 57 | $delete = array_diff($oldStores, $newStores); 58 | if (!empty($delete)) { 59 | $where = [ 60 | $linkField . ' = ?' => (int)$entity->getData($linkField), 61 | 'store_id IN (?)' => $delete, 62 | ]; 63 | $connection->delete($table, $where); 64 | } 65 | 66 | $insert = array_diff($newStores, $oldStores); 67 | if (!empty($insert)) { 68 | $data = []; 69 | foreach ($insert as $storeId) { 70 | $data[] = [ 71 | $linkField => (int)$entity->getData($linkField), 72 | 'store_id' => (int)$storeId, 73 | ]; 74 | } 75 | $connection->insertMultiple($table, $data); 76 | } 77 | 78 | return $entity; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/MX/MegaMenu/Ui/Component/Listing/Column/Actions.php: -------------------------------------------------------------------------------- 1 | urlBuilder = $urlBuilder; 39 | parent::__construct($context, $uiComponentFactory, $components, $data); 40 | } 41 | 42 | /** 43 | * Prepare Data Source 44 | * 45 | * @param array $dataSource 46 | * @return array 47 | */ 48 | public function prepareDataSource(array $dataSource) 49 | { 50 | if (isset($dataSource['data']['items'])) { 51 | foreach ($dataSource['data']['items'] as & $item) { 52 | $name = $this->getData('name'); 53 | if (isset($item['menu_id'])) { 54 | $item[$name]['edit'] = [ 55 | 'href' => $this->urlBuilder->getUrl(self::URL_PATH_EDIT, ['menu_id' => $item['menu_id']]), 56 | 'label' => __('Edit') 57 | ]; 58 | 59 | $item[$name]['delete'] = [ 60 | 'href' => $this->urlBuilder->getUrl(self::URL_PATH_DELETE, ['menu_id' => $item['menu_id']]), 61 | 'label' => __('Delete'), 62 | 'confirm' => [ 63 | 'title' => __('Delete'), 64 | 'message' => __('Are you sure you wan\'t to delete this record?') 65 | ] 66 | ]; 67 | } 68 | } 69 | } 70 | 71 | return $dataSource; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/MX/MegaMenu/Controller/Adminhtml/Menu/ExportPost.php: -------------------------------------------------------------------------------- 1 | menuFactory = $menuFactory; 41 | $this->menuRepository = $menuRepository; 42 | 43 | parent::__construct($context, $coreRegistry, $jsonDataHelper); 44 | } 45 | 46 | public function execute() 47 | { 48 | $response = [ 49 | 'status' => false, 50 | 'result' => '', 51 | 'redirect' => $this->getUrl('mx_megamenu/menu/export') 52 | ]; 53 | 54 | try { 55 | $result = []; 56 | $items = $this->menuRepository->getAllItems(); 57 | 58 | foreach ($items as $item) { 59 | $id = $item->getMenuId(); 60 | $menu = $this->menuRepository->getById($id); 61 | $result[$id] = $menu->getData(); 62 | 63 | $response['status'] = true; 64 | $response['result'] = json_encode($result); 65 | 66 | $this->messageManager->addSuccessMessage(__('You successfully exported the menu items.')); 67 | } 68 | } catch (LocalizedException $e) { 69 | $this->messageManager->addErrorMessage($e->getMessage()); 70 | } catch (\Exception $e) { 71 | $this->messageManager->addExceptionMessage($e, __('Something went wrong while exporting the menu items.')); 72 | } 73 | 74 | return $this->sendHtmlResponse($response); 75 | } 76 | } -------------------------------------------------------------------------------- /src/MX/MegaMenu/Api/Data/MenuInterface.php: -------------------------------------------------------------------------------- 1 | getMenuJson(); 5 | ?> 6 | 7 |
    8 | 16 |
    17 | getChildHtml('megamenu.edit.settings.form'); ?> 18 |
    19 |
    20 |
    21 | 22 |
    23 | 28 |
    29 |
    30 | 31 | 50 | 51 | 54 | 55 | 58 | 59 | -------------------------------------------------------------------------------- /src/MX/MegaMenu/Setup/UpgradeSchema.php: -------------------------------------------------------------------------------- 1 | getVersion(), '1.1.0', '<')) { 17 | $this->addCategoryTypeField($setup); 18 | } 19 | 20 | if (version_compare($context->getVersion(), '1.2.0', '<')) { 21 | $this->addCustomClassField($setup); 22 | $this->addRemoveCategoryLinkField($setup); 23 | } 24 | } 25 | 26 | /** 27 | * Add category type title 28 | * 29 | * @param SchemaSetupInterface $setup 30 | * @return $this 31 | */ 32 | protected function addCategoryTypeField(SchemaSetupInterface $setup) 33 | { 34 | $setup->getConnection()->addColumn( 35 | $setup->getTable(self::TABLE_MEGAMENU_ITEM), 36 | 'content_category_type', 37 | [ 38 | 'type' => Table::TYPE_TEXT, 39 | 'nullable' => false, 40 | 'size' => 10, 41 | 'after' => 'content_category', 42 | 'comment' => 'Content Category Type' 43 | ] 44 | ); 45 | return $this; 46 | } 47 | 48 | /** 49 | * Add custom class field 50 | * 51 | * @param SchemaSetupInterface $setup 52 | * @return $this 53 | */ 54 | protected function addCustomClassField(SchemaSetupInterface $setup) 55 | { 56 | $setup->getConnection()->addColumn( 57 | $setup->getTable(self::TABLE_MEGAMENU_ITEM), 58 | 'custom_class', 59 | [ 60 | 'type' => Table::TYPE_TEXT, 61 | 'nullable' => false, 62 | 'size' => 255, 63 | 'after' => 'content_category_type', 64 | 'comment' => 'Custom Class' 65 | ] 66 | ); 67 | return $this; 68 | } 69 | 70 | /** 71 | * Add remove category link field 72 | * 73 | * @param SchemaSetupInterface $setup 74 | * @return $this 75 | */ 76 | protected function addRemoveCategoryLinkField(SchemaSetupInterface $setup) 77 | { 78 | $setup->getConnection()->addColumn( 79 | $setup->getTable(self::TABLE_MEGAMENU_ITEM), 80 | 'remove_category_anchor', 81 | [ 82 | 'type' => Table::TYPE_SMALLINT, 83 | 'nullable' => false, 84 | 'unsigned' => true, 85 | 'default' => 0, 86 | 'after' => 'custom_class', 87 | 'comment' => 'Remove Category Link' 88 | ] 89 | ); 90 | return $this; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/MX/MegaMenu/view/frontend/templates/topmenu/structure/children.phtml: -------------------------------------------------------------------------------- 1 | getItem(); 6 | $level = $block->getItemLevel($item); 7 | $levelClass = 'level' . $level; 8 | $first = $block->getFirst(); 9 | 10 | ?> 11 | 12 | 13 |
    14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
    26 | 27 |
    28 | 29 | 30 | 31 |
    32 | 33 |
    34 | 35 | 36 |
    37 | 38 |
    39 | 40 | 41 | 42 | 43 |
    44 | 45 |
    46 | 47 | 48 |
    49 | 50 |
    51 | 52 | 53 | 54 | 55 | $child): ?> 56 | getParentBlock()->renderChildren($child); ?> 57 | 58 | 59 | 60 | 61 |
    62 | 63 | 64 | -------------------------------------------------------------------------------- /src/MX/MegaMenu/etc/di.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | MX\MegaMenu\Api\MenuRepositoryInterface 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | mx_megamenu 16 | menu_id 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | Magento\Framework\EntityManager\AbstractModelHydrator 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | MX\MegaMenu\Model\ResourceModel\Menu\Relation\Store\ReadHandler 34 | MX\MegaMenu\Model\ResourceModel\Menu\Relation\Item\ReadHandler 35 | 36 | 37 | MX\MegaMenu\Model\ResourceModel\Menu\Relation\Store\SaveHandler 38 | MX\MegaMenu\Model\ResourceModel\Menu\Relation\Item\SaveHandler 39 | 40 | 41 | MX\MegaMenu\Model\ResourceModel\Menu\Relation\Store\SaveHandler 42 | MX\MegaMenu\Model\ResourceModel\Menu\Relation\Item\SaveHandler 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | MX\MegaMenu\Model\ResourceModel\Menu\Grid\Collection 52 | 53 | 54 | 55 | 56 | 57 | mx_megamenu 58 | MX\MegaMenu\Model\ResourceModel\Menu 59 | 60 | 61 | -------------------------------------------------------------------------------- /src/MX/MegaMenu/view/frontend/web/css/megamenu.css: -------------------------------------------------------------------------------- 1 | .nav-sections-item-content > .mx-megamenu { 2 | display: block; 3 | } 4 | 5 | /* Fix the iOS background scrolling issue */ 6 | .megamenu-init-toggle.nav-open, 7 | .megamenu-init-toggle body { 8 | height: 100%; 9 | overflow: hidden; 10 | } 11 | /* Fix the iOS background scrolling issue */ 12 | 13 | .mx-megamenu .navigation > ul { 14 | display: flex; 15 | flex-direction: column; 16 | margin: 0; 17 | padding: 0; 18 | list-style-type: none; 19 | } 20 | 21 | .mx-megamenu .navigation .mx-megamenu__item.level-top { 22 | padding: 0.5em; 23 | } 24 | 25 | .mx-megamenu .navigation .mx-megamenu__item.level-top .mx-megamenu__link { 26 | display: block; 27 | padding: 0.5em; 28 | } 29 | 30 | .mx-megamenu .navigation .mx-megamenu__item > .mx-megamenu__link > span { 31 | display: block; 32 | } 33 | 34 | .mx-megamenu .navigation .mx-megamenu__item.level-top .mx-megamenu__submenu { 35 | display: none; 36 | margin-left: 1.25em; 37 | } 38 | 39 | .mx-megamenu .navigation .mx-megamenu__item.current > .mx-megamenu__submenu { 40 | display: block; 41 | } 42 | 43 | .mx-megamenu__sidebar, 44 | .mx-megamenu__header, 45 | .mx-megamenu__footer { 46 | display: none; 47 | } 48 | 49 | @media screen and (max-width: 1024px) { 50 | .navigation li.level0 { 51 | border-bottom: 1px solid #cccccc; 52 | } 53 | } 54 | 55 | @media screen and (min-width: 1025px) { 56 | .mx-megamenu .navigation > ul { 57 | position: relative; 58 | flex-direction: row; 59 | justify-content: center; 60 | } 61 | 62 | .mx-megamenu .navigation .mx-megamenu__item.level-top { 63 | position: static; 64 | padding: 0 1em; 65 | } 66 | 67 | .mx-megamenu .navigation .mx-megamenu__item.level-top.current > .mx-megamenu__submenu { 68 | display: flex; 69 | left: 0; 70 | width: 100%; 71 | border-color: #cccccc; 72 | border-left: 0; 73 | border-right: 0; 74 | margin-left: 0; 75 | } 76 | 77 | .mx-megamenu .navigation .mx-megamenu__item.level-top > .mx-megamenu__link { 78 | display: inline-block; 79 | padding: 0; 80 | } 81 | 82 | .mx-megamenu .navigation .mx-megamenu__item.level-top.current > .mx-megamenu__link { 83 | position: relative; 84 | } 85 | 86 | .mx-megamenu__content { 87 | display: flex; 88 | justify-content: space-between; 89 | } 90 | 91 | .mx-megamenu .navigation .mx-megamenu__item .mx-megamenu__categories { 92 | display: flex; 93 | width: 100%; 94 | flex-wrap: wrap; 95 | -webkit-box-orient: vertical; 96 | -webkit-box-direction: normal; 97 | flex-direction: column; 98 | max-height: 500px; 99 | } 100 | 101 | .mx-megamenu__main-content, 102 | .mx-megamenu__categories { 103 | padding: 0 1em; 104 | } 105 | 106 | .mx-megamenu .navigation .mx-megamenu__item .mx-megamenu__categories .mx-megamenu__category { 107 | margin: 0 0.5em; 108 | } 109 | 110 | .mx-megamenu__item .mx-megamenu__categories .mx-megamenu__category>.mx-megamenu__link { 111 | font-weight: bold; 112 | } 113 | 114 | .mx-megamenu .navigation .mx-megamenu__item .mx-megamenu__categories .mx-megamenu__submenu { 115 | display: flex; 116 | flex-direction: column; 117 | } 118 | 119 | .mx-megamenu .navigation .mx-megamenu__item .mx-megamenu__categories .mx-megamenu__category-item .mx-megamenu__link { 120 | padding: 0 3px; 121 | } 122 | 123 | .mx-megamenu__sidebar, 124 | .mx-megamenu__header, 125 | .mx-megamenu__footer { 126 | display: flex; 127 | width: auto; 128 | text-align: center; 129 | } 130 | 131 | .mx-megamenu__header, 132 | .mx-megamenu__footer { 133 | align-self: center; 134 | } 135 | } -------------------------------------------------------------------------------- /src/MX/MegaMenu/Block/Adminhtml/Menu/Edit/Settings/Form.php: -------------------------------------------------------------------------------- 1 | store = $store; 44 | parent::__construct($context, $registry, $formFactory, $data); 45 | } 46 | 47 | protected function _construct() 48 | { 49 | parent::_construct(); 50 | 51 | $this->setId('settings_form'); 52 | } 53 | 54 | protected function _prepareForm() 55 | { 56 | $menuModel = $this->_coreRegistry->registry('mx_megamenu_menu'); 57 | $menuId = $this->getRequest()->getParam('menu_id', 0); 58 | 59 | $form = $this->_formFactory->create( 60 | ['data' => 61 | [ 62 | 'id' => 'settings_form', 63 | 'enctype' => 'multipart/form-data', 64 | 'action' => $this->getData('action'), 65 | 'method' => 'post' 66 | ] 67 | ] 68 | ); 69 | 70 | $fieldset = $form->addFieldset( 71 | 'settings_fieldset', 72 | [ 73 | 'legend' => __(''), 74 | 'class' => 'fieldset-wide' 75 | ] 76 | ); 77 | 78 | if ($menuId) { 79 | $fieldset->addField( 80 | 'menu_id', 81 | 'hidden', 82 | [ 83 | 'name' => 'menu_id', 84 | 'value' => $menuId 85 | ] 86 | ); 87 | } 88 | 89 | $fieldset->addField( 90 | 'enabled', 91 | 'MX\MegaMenu\Data\Form\Element\Toggle', 92 | [ 93 | 'label' => __('Enabled'), 94 | 'title' => __('Enabled'), 95 | 'required' => false, 96 | 'name' => 'status', 97 | 'value' => '1', 98 | 'checked' => $menuModel ? $menuModel->getStatus() : '' 99 | ] 100 | ); 101 | 102 | $fieldset->addField( 103 | 'name', 104 | 'text', 105 | [ 106 | 'label' => __('Name'), 107 | 'title' => __('Name'), 108 | 'required' => true, 109 | 'name' => 'name', 110 | 'value' => $menuModel ? $menuModel->getName() : '' 111 | ] 112 | ); 113 | 114 | $fieldset->addField( 115 | 'store_id', 116 | 'multiselect', 117 | [ 118 | 'name' => 'store_id', 119 | 'label' => __('Store View'), 120 | 'title' => __('Store View'), 121 | 'required' => true, 122 | 'values' => $this->store->getStoreValuesForForm(false, true), 123 | 'value' => $menuModel ? $menuModel->getStoreId() : self::DEFAULT_STORE_ID 124 | ] 125 | ); 126 | 127 | $form->setUseContainer(true); 128 | $this->setForm($form); 129 | 130 | return parent::_prepareForm(); 131 | } 132 | } -------------------------------------------------------------------------------- /src/MX/MegaMenu/Model/ResourceModel/Menu/Relation/Item/SaveHandler.php: -------------------------------------------------------------------------------- 1 | metadataPool = $metadataPool; 36 | $this->resourceMenu = $resourceMenu; 37 | } 38 | 39 | /** 40 | * @param object $entity 41 | * @param array $arguments 42 | * @return object 43 | * @throws \Exception 44 | */ 45 | public function execute($entity, $arguments = []) 46 | { 47 | $entityMetadata = $this->metadataPool->getMetadata(MenuInterface::class); 48 | $linkField = $entityMetadata->getLinkField(); 49 | 50 | $connection = $entityMetadata->getEntityConnection(); 51 | 52 | $oldItems = $this->resourceMenu->lookupMenuItems((int)$entity->getMenuId()); 53 | $oldItemIds = array_keys($oldItems); 54 | $newItemIds = (array)$entity->getMenuItemIds(); 55 | 56 | $table = $this->resourceMenu->getTable('mx_megamenu_item'); 57 | 58 | // Delete old items 59 | $delete = array_diff($oldItemIds, $newItemIds); 60 | if (!empty($delete)) { 61 | $where = [ 62 | $linkField . ' = ?' => (int)$entity->getData($linkField), 63 | 'menu_item_id IN (?)' => $delete, 64 | ]; 65 | $connection->delete($table, $where); 66 | } 67 | 68 | // Remove special menu items before save 69 | $menuItems = $entity->getMenuItems(); 70 | $entity->removeSpecialMenuItems($menuItems); 71 | 72 | // Insert new items 73 | $insert = array_diff($newItemIds, $oldItemIds); 74 | if (!empty($insert)) { 75 | $data = []; 76 | foreach ($insert as $itemId) { 77 | $data[$itemId] = [ 78 | $linkField => (int)$entity->getData($linkField), 79 | 'menu_item_id' => null, 80 | ]; 81 | 82 | foreach ($menuItems as $menuItem) { 83 | if ($menuItem['menu_item_id'] == $itemId) { 84 | foreach ($menuItem as $name => $value) { 85 | if ($name !== 'menu_item_id') { 86 | $data[$itemId][$name] = $value; 87 | } 88 | } 89 | } 90 | } 91 | } 92 | $connection->insertMultiple($table, $data); 93 | } 94 | 95 | /** 96 | * Update existing items 97 | */ 98 | if (count($oldItemIds) > 0 && count($menuItems) > 0) { 99 | foreach ($menuItems as $menuItem) { 100 | if (in_array($menuItem['menu_item_id'], $oldItemIds)) { 101 | $where = [ 102 | $linkField . ' = ?' => (int)$entity->getData($linkField), 103 | 'menu_item_id = (?)' => $menuItem['menu_item_id'], 104 | ]; 105 | unset($menuItem['menu_id']); 106 | unset($menuItem['menu_item_id']); 107 | $connection->update($table, $menuItem, $where); 108 | } 109 | } 110 | } 111 | 112 | return $entity; 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/MX/MegaMenu/Block/Adminhtml/Menu/Edit.php: -------------------------------------------------------------------------------- 1 | coreRegistry = $registry; 61 | $this->messageManager = $messageManager; 62 | $this->menuFactory = $menuFactory; 63 | $this->menuItemFactory = $menuItemFactory; 64 | parent::__construct($context, $data); 65 | } 66 | 67 | /** 68 | * Initialize cms page edit block 69 | * 70 | * @return void 71 | */ 72 | protected function _construct() 73 | { 74 | $this->_objectId = 'menu_id'; 75 | $this->_controller = 'adminhtml_menu'; 76 | $this->_blockGroup = 'MX_MegaMenu'; 77 | 78 | parent::_construct(); 79 | } 80 | 81 | /** 82 | * Get Menu Json String 83 | * 84 | * @return string 85 | */ 86 | public function getMenuJson() 87 | { 88 | /** @var $menuModel \MX\MegaMenu\Model\Menu|null */ 89 | $menuModel = $this->coreRegistry->registry('mx_megamenu_menu'); 90 | 91 | if ($menuModel) { 92 | $items = $menuModel->getSortedMenuItems(); 93 | return $this->encodeItems($items); 94 | } 95 | 96 | return ''; 97 | } 98 | 99 | /** 100 | * Get Edit form Url 101 | * 102 | * @return string 103 | */ 104 | public function getEditFormUrl() 105 | { 106 | return $this->getUrl('mx_megamenu/menu/form'); 107 | } 108 | 109 | /** 110 | * Get Save Form Url 111 | * 112 | * @return string 113 | */ 114 | public function getSaveFormUrl() 115 | { 116 | return $this->getUrl('mx_megamenu/menu/save'); 117 | } 118 | 119 | /** 120 | * Get encoded string - useful for widget encoded strings 121 | * 122 | * @param array $items 123 | * @return mixed 124 | */ 125 | protected function encodeItems(&$items) 126 | { 127 | $menuItem = $this->menuItemFactory->create(); 128 | foreach ($items as &$item) { 129 | foreach ($item as $name => $value) { 130 | if ($menuItem->needEncode($name)) { 131 | $value = $menuItem->decodeContent($value); 132 | $value = $menuItem->encodeSpecialCharacters($value); 133 | $item[$name] = base64_encode($value); 134 | } 135 | } 136 | } 137 | 138 | return json_encode($items); 139 | } 140 | 141 | /** 142 | * Check permission for passed action 143 | * 144 | * @param string $resourceId 145 | * @return bool 146 | */ 147 | protected function isAllowedAction($resourceId) 148 | { 149 | return $this->_authorization->isAllowed($resourceId); 150 | } 151 | } -------------------------------------------------------------------------------- /src/MX/MegaMenu/Block/TopMenu.php: -------------------------------------------------------------------------------- 1 | storeManager = $storeManager; 55 | $this->menuRepository = $menuRepository; 56 | } 57 | 58 | protected function _construct() 59 | { 60 | parent::_construct(); 61 | 62 | $this->addData( 63 | [ 64 | 'cache_lifetime' => 86400, 65 | 'cache_tags' => [Menu::CACHE_TAG] 66 | ] 67 | ); 68 | } 69 | 70 | /** 71 | * Get key pieces for caching block content 72 | * 73 | * @return array 74 | */ 75 | public function getCacheKeyInfo() 76 | { 77 | return [ 78 | self::CACHE_KEY, 79 | $this->storeManager->getStore()->getId(), 80 | $this->_design->getDesignTheme()->getId(), 81 | 'template' => $this->getTemplate(), 82 | $this->getData('menu_id') 83 | ]; 84 | } 85 | 86 | /** 87 | * Get structure 88 | * 89 | * @return array|false 90 | * @throws \Magento\Framework\Exception\LocalizedException 91 | * @throws \Magento\Framework\Exception\NoSuchEntityException 92 | */ 93 | public function getStructure() 94 | { 95 | $storeId = $this->storeManager->getStore()->getId(); 96 | /** @var \MX\MegaMenu\Model\Menu $menu */ 97 | $menu = $this->menuRepository->getByStoreId($storeId); 98 | if ($menu->getMenuId() && $menu->isActive()) { 99 | return $menu->getProcessedMenuItems(); 100 | } 101 | 102 | return false; 103 | } 104 | 105 | /** 106 | * Render children items 107 | * 108 | * @param array $item 109 | * @param boolean $firstLevelChild 110 | * @throws \Magento\Framework\Exception\LocalizedException 111 | */ 112 | public function renderChildren($item, $firstLevelChild = false) 113 | { 114 | /** @var $block MX\MegaMenu\Block\TopMenu\Children */ 115 | $block = $this->getChildBlock(self::CHILDREN_ALIAS); 116 | $block->setFirst($firstLevelChild); // First level child shouldn't be rendered again as it is already rendered in the parent 117 | $block->setItem($item); 118 | 119 | return $this->getChildHtml(self::CHILDREN_ALIAS, false); 120 | } 121 | 122 | /** 123 | * Can show content 124 | * 125 | * @param array $item 126 | * @return boolean 127 | */ 128 | public function canShowContent($item) 129 | { 130 | return !empty($item['header_status']) 131 | || !empty($item['content_status']) 132 | || !empty($item['leftside_status']) 133 | || !empty($item['rightside_status']) 134 | || !empty($item['footer_status']) 135 | || isset($item['children']); 136 | } 137 | 138 | /** 139 | * Return identifiers for produced content 140 | * 141 | * @return array 142 | */ 143 | public function getIdentities() 144 | { 145 | return [Menu::CACHE_TAG . '_' . $this->getBlockId()]; 146 | } 147 | } -------------------------------------------------------------------------------- /src/MX/MegaMenu/Controller/Adminhtml/Menu/Save.php: -------------------------------------------------------------------------------- 1 | dataPersistor = $dataPersistor; 49 | $this->menuFactory = $menuFactory; 50 | $this->menuRepository = $menuRepository; 51 | 52 | parent::__construct($context, $coreRegistry, $jsonDataHelper); 53 | } 54 | 55 | /** 56 | * Save action 57 | */ 58 | public function execute() 59 | { 60 | $response = [ 61 | 'status' => false, 62 | 'url' => $this->getUrl('*/*/') 63 | ]; 64 | 65 | $resultRedirect = $this->resultRedirectFactory->create(); 66 | $data = $this->getRequest()->getPostValue(); 67 | if ($data) { 68 | if (empty($data['menu_id'])) { 69 | $data['menu_id'] = null; 70 | } 71 | 72 | $this->coreRegistry->register('mx_megamenu_menu_items', $data['items']); 73 | 74 | /** @var \MX\MegaMenu\Model\Menu $model */ 75 | $model = $this->menuFactory->create(); 76 | 77 | $id = $this->getRequest()->getParam('menu_id'); 78 | if ($id) { 79 | try { 80 | $model = $this->menuRepository->getById($id); 81 | } catch (LocalizedException $e) { 82 | $this->messageManager->addErrorMessage(__('This menu no longer exists.')); 83 | $response['url'] = $resultRedirect->setPath('*/*/'); 84 | 85 | return $this->sendHtmlResponse($response); 86 | } 87 | } 88 | 89 | $model->setData($data); 90 | 91 | try { 92 | $this->menuRepository->save($model); 93 | $this->messageManager->addSuccessMessage(__('You saved the menu.')); 94 | $this->dataPersistor->clear('mx_megamenu'); 95 | if ($this->getRequest()->getParam('back')) { 96 | $response['url'] = $resultRedirect->setPath('*/*/edit', ['menu_id' => $model->getId()]); 97 | 98 | return $this->sendHtmlResponse($response); 99 | } 100 | 101 | $response['status'] = true; 102 | $response['url'] = $resultRedirect->setPath('*/*/'); 103 | 104 | return $this->sendHtmlResponse($response); 105 | } catch (LocalizedException $e) { 106 | $this->messageManager->addErrorMessage($e->getMessage()); 107 | } catch (\Exception $e) { 108 | $this->messageManager->addExceptionMessage($e, __('Something went wrong while saving the menu.')); 109 | } 110 | 111 | $this->dataPersistor->set('mx_megamenu', $data); 112 | 113 | $response['url'] = $resultRedirect->setPath( 114 | '*/*/edit', 115 | [ 116 | 'menu_id' => $this->getRequest()->getParam('menu_id') 117 | ] 118 | ); 119 | } 120 | 121 | return $this->sendHtmlResponse($response); 122 | } 123 | } -------------------------------------------------------------------------------- /src/MX/MegaMenu/view/adminhtml/web/css/megamenu.css: -------------------------------------------------------------------------------- 1 | /* Font Awesome icons */ 2 | @font-face { 3 | font-family: 'font-awesome-icons'; 4 | src: url('../fonts/fa-solid.eot'); 5 | src: url('../fonts/fa-solid.eot#iefix') format('embedded-opentype'), 6 | url('../fonts/fa-solid.ttf') format('truetype'), 7 | url('../fonts/fa-solid.woff') format('woff'), 8 | url('../fonts/fa-solid.woff2') format('woff2'), 9 | url('../fonts/fa-solid.svg#icomoon') format('svg'); 10 | font-weight: normal; 11 | font-style: normal; 12 | } 13 | @-webkit-keyframes blink { 14 | 0% { border-color: #eb5202; } 15 | 100% { border-color: #e3e3e3; } 16 | } 17 | @-moz-keyframes blink { 18 | 0% { border-color: #eb5202; } 19 | 100% { border-color: #e3e3e3; } 20 | } 21 | @-keyframes blink { 22 | 0% { border-color: #eb5202; } 23 | 100% { border-color: #e3e3e3; } 24 | } 25 | .fa { 26 | font-family: 'font-awesome-icons'; 27 | font-size: 20px; 28 | line-height: 30px; 29 | color: #007bdb; 30 | } 31 | .fa-edit:before { 32 | content: "\f044"; 33 | } 34 | .fa-remove:before { 35 | content: "\f1f8"; 36 | } 37 | .fa-hand:before { 38 | content: "\f255"; 39 | } 40 | .fa-caret-up, 41 | .fa-caret-down { 42 | font-size: 30px; 43 | } 44 | .fa-caret-up:before { 45 | content: "\f0d8"; 46 | } 47 | .fa-caret-down:before { 48 | content: "\f0d7"; 49 | } 50 | /* Font Awesome icons */ 51 | .megamenu-editor a:hover { 52 | text-decoration: none; 53 | } 54 | .megamenu-editor .input-text { 55 | padding: 3px 6px; 56 | } 57 | .megamenu-editor > .actions { 58 | margin: 10px 0; 59 | } 60 | .megamenu-editor .structure { 61 | margin-top: 20px; 62 | } 63 | .megamenu-editor .structure .dd-list { 64 | margin-left: 50px; 65 | margin-top: 20px; 66 | } 67 | .megamenu-editor .structure .menu-item { 68 | position: relative; 69 | padding: 10px 0; 70 | background-color: #f8f8f8; 71 | border: 1px solid #e3e3e3; 72 | border-left: 0; 73 | border-right: 0; 74 | } 75 | .megamenu-editor .structure .menu-item .btn-caret { 76 | display: none; 77 | padding: 0 10px; 78 | border: 0; 79 | background-color: #f8f8f8; 80 | } 81 | .megamenu-editor .structure .menu-item .btn-caret.show { 82 | display: block; 83 | } 84 | .megamenu-editor .structure .menu-item .actions { 85 | float: right; 86 | display: inline-block; 87 | margin-left: 10px; 88 | margin-top: 4px; 89 | } 90 | .megamenu-editor .structure .menu-item .actions a { 91 | padding: 0 10px; 92 | } 93 | .megamenu-editor .structure .menu-item .actions a:hover { 94 | text-decoration: none; 95 | } 96 | .megamenu-editor .structure .menu-item .label { 97 | font-weight: bold; 98 | padding: 0 10px; 99 | font-size: 14px; 100 | } 101 | .megamenu-editor .structure .menu-item .drag-menu { 102 | display: inline-block; 103 | background-color: #f8f8f8; 104 | width: 74%; 105 | cursor: pointer; 106 | border: 0; 107 | padding: 0 10px; 108 | } 109 | .megamenu-editor .structure .menu-item .drag-menu:hover { 110 | background: none; 111 | } 112 | .megamenu-editor .structure .menu-item.new { 113 | animation: blink 1s linear 1s 3; 114 | -webkit-animation: blink 1s linear 1s 3; 115 | -moz-animation: blink 1s linear 1s 3; 116 | } 117 | .megamenu-editor .megamenu-tabs { 118 | display: none; 119 | } 120 | .megamenu-editor .megamenu-tabs.active { 121 | display: block; 122 | } 123 | .megamenu-editor .nestable { 124 | max-width: 100%; 125 | } 126 | .megamenu-editor .nestable .dd-empty { 127 | display: none; 128 | } 129 | .megamenu-editor .nestable .dd-actions { 130 | display: inline-block; 131 | } 132 | #tabs-menu { 133 | list-style-type: none; 134 | display: flex; 135 | background-color: #f8f8f8; 136 | } 137 | #tabs-menu li { 138 | display: inline-block; 139 | border-top: 3px solid #f8f8f8; 140 | } 141 | #tabs-menu li.active { 142 | background-color: #ffffff; 143 | border-top: 3px solid #007bdb; 144 | } 145 | #tabs-menu li a { 146 | display: inline-block; 147 | padding: 20px 40px; 148 | color: #231d1a; 149 | } 150 | .megamenu-editor .megamenu-tabs { 151 | padding: 10px; 152 | } 153 | .hidden-form { 154 | display: none; 155 | } 156 | .megamenu-container { 157 | margin: 10px 0; 158 | } 159 | .megamenu-container .text { 160 | padding: 10px 0; 161 | } 162 | .megamenu-container .input-field { 163 | margin: 10px 0 30px; 164 | } 165 | .dd-dragel > .dd-item > .dd-handle.drag-menu { 166 | height: 46px; 167 | } -------------------------------------------------------------------------------- /src/MX/MegaMenu/Model/MenuRepository.php: -------------------------------------------------------------------------------- 1 | resource = $resource; 50 | $this->menuFactory = $menuFactory; 51 | $this->menuCollectionFactory = $menuCollectionFactory; 52 | $this->storeManager = $storeManager; 53 | } 54 | 55 | /** 56 | * Save Menu data 57 | * 58 | * @param MenuInterface $menu 59 | * @return Menu 60 | * @throws CouldNotSaveException 61 | */ 62 | public function save(MenuInterface $menu) 63 | { 64 | if (empty($menu->getStores())) { 65 | $menu->setStores($this->storeManager->getStore()->getId()); 66 | } 67 | 68 | try { 69 | $this->resource->save($menu); 70 | } catch (\Exception $exception) { 71 | throw new CouldNotSaveException(__($exception->getMessage())); 72 | } 73 | 74 | return $menu; 75 | } 76 | 77 | /** 78 | * Load Menu data by given Menu Identity 79 | * 80 | * @param string $menuId 81 | * @return Menu 82 | * @throws \Magento\Framework\Exception\NoSuchEntityException 83 | */ 84 | public function getById($menuId) 85 | { 86 | $menu = $this->menuFactory->create(); 87 | $this->resource->load($menu, $menuId); 88 | if (!$menu->getMenuId()) { 89 | throw new NoSuchEntityException(__('Menu with id "%1" does not exist.', $menuId)); 90 | } 91 | 92 | return $menu; 93 | } 94 | 95 | /** 96 | * Load Menu data by given Store Id 97 | * 98 | * @param $storeId 99 | * @return Menu 100 | */ 101 | public function getByStoreId($storeId) 102 | { 103 | $menu = $this->menuFactory->create(); 104 | $menu->setStores($storeId); 105 | $this->resource->load($menu, $storeId, 'store_id'); 106 | 107 | return $menu; 108 | } 109 | 110 | /** 111 | * Get all items 112 | * 113 | * @return MenuInterface 114 | */ 115 | public function getAllItems() 116 | { 117 | $collection = $this->menuCollectionFactory->create(); 118 | 119 | return $collection->getItems(); 120 | } 121 | 122 | /** 123 | * Delete Menu 124 | * 125 | * @param MenuInterface $menu 126 | * @return boolean 127 | * @throws CouldNotDeleteException 128 | */ 129 | public function delete(MenuInterface $menu) 130 | { 131 | try { 132 | $this->resource->delete($menu); 133 | } catch (\Exception $exception) { 134 | throw new CouldNotDeleteException(__($exception->getMessage())); 135 | } 136 | 137 | return true; 138 | } 139 | 140 | /** 141 | * Delete Menu by given Menu Identity 142 | * 143 | * @param string $menuId 144 | * @return bool 145 | * @throws CouldNotDeleteException 146 | * @throws NoSuchEntityException 147 | */ 148 | public function deleteById($menuId) 149 | { 150 | return $this->delete($this->getById($menuId)); 151 | } 152 | 153 | /** 154 | * Truncate tables 155 | **/ 156 | public function deleteAll() 157 | { 158 | $this->resource->deleteAll(); 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/MX/MegaMenu/Model/Menu/ImportHandler.php: -------------------------------------------------------------------------------- 1 | menuFactory = $menuFactory; 39 | $this->menuRepository = $menuRepository; 40 | } 41 | 42 | /** 43 | * Import from file 44 | * 45 | * @param mixed $file 46 | * @throws LocalizedException 47 | */ 48 | public function importFromFile($file) 49 | { 50 | if (!$this->validateFile($file)) { 51 | throw new LocalizedException(__('Invalid file upload attempt.')); 52 | } 53 | 54 | $data = file_get_contents($file['tmp_name']); 55 | if (!$this->validateData($data)) { 56 | throw new LocalizedException(__('Invalid json file.')); 57 | } 58 | 59 | $this->import($data); 60 | } 61 | 62 | /** 63 | * Import data 64 | * 65 | * @param string $data 66 | */ 67 | protected function import($data) 68 | { 69 | // Truncate data from database 70 | $this->menuRepository->deleteAll(); 71 | 72 | // Fetch data 73 | $menuData = $this->decodeData($data); 74 | 75 | $newMenuId = 1; 76 | $newItemId = 1; 77 | $menuIdsRef = []; 78 | $menuItemIdsRef = []; 79 | 80 | // Assign references for the new menu item ids 81 | foreach ($menuData as $menuId => $menu) { 82 | $menuItems = []; 83 | foreach ($menu['menu_items'] as $itemId => $item) { 84 | // Gather old menu item id references 85 | $menuItemIdsRef[$itemId] = $newItemId; 86 | 87 | // Gather item data 88 | $menuItems[$newItemId] = $item; 89 | $menuItems[$newItemId]['menu_item_id'] = $newItemId; 90 | 91 | $newItemId++; 92 | } 93 | 94 | // Unset old indexes and set new indexes 95 | unset($menuData[$menuId]['menu_items']); 96 | $menuData[$menuId]['menu_items'] = $menuItems; 97 | 98 | // Gather old menu id references 99 | $menuIdsRef[$menuId] = $newMenuId; 100 | $newMenuId++; 101 | } 102 | 103 | // Assign references for the new menu ids 104 | foreach ($menuData as $menuId => $menu) { 105 | foreach ($menu['menu_items'] as $itemId => $item) { 106 | $parentId = $item['menu_item_parent_id']; 107 | if ($item['menu_item_parent_id'] > 0 && isset($menuItemIdsRef[$parentId])) { 108 | $menuData[$menuId]['menu_items'][$itemId]['menu_item_parent_id'] = $menuItemIdsRef[$parentId]; 109 | } 110 | 111 | // Save new menu_id for menu item 112 | if (isset($menuIdsRef[$menuId])) { 113 | $menuData[$menuId]['menu_items'][$itemId]['menu_id'] = $menuIdsRef[$menuId]; 114 | } 115 | } 116 | 117 | // Save new menu_id for menu 118 | if (isset($menuIdsRef[$menuId])) { 119 | $menuData[$menuId]['menu_id'] = $menuIdsRef[$menuId]; 120 | } 121 | } 122 | 123 | // Save 124 | /** @var \MX\MegaMenu\Model\Menu $model */ 125 | $model = $this->menuFactory->create(); 126 | 127 | foreach ($menuData as $menu) { 128 | $model->setData($menu); 129 | $this->menuRepository->save($model); 130 | } 131 | } 132 | 133 | /** 134 | * Validate file data 135 | * 136 | * @param string $data 137 | * @return boolean 138 | */ 139 | protected function validateData($data) 140 | { 141 | $menuData = $this->decodeData($data); 142 | if ($menuData) { 143 | $keysFound = 0; 144 | foreach ($menuData as $menu) { 145 | foreach ($menu as $key => $item) { 146 | if (in_array($key, $this->menuKeys)) { 147 | $keysFound++; 148 | } 149 | } 150 | } 151 | 152 | return $keysFound == (count($menuData) * count($this->menuKeys)); 153 | } 154 | 155 | return false; 156 | } 157 | 158 | /** 159 | * Validate file 160 | * 161 | * @param mixed $file 162 | * @return boolean 163 | */ 164 | protected function validateFile($file) 165 | { 166 | return $file && isset($file['tmp_name']) && $file['error'] == self::FILE_NO_ERROR; 167 | } 168 | 169 | /** 170 | * @param string $data 171 | * @return mixed 172 | */ 173 | protected function decodeData($data) 174 | { 175 | return json_decode($data, true); 176 | } 177 | } -------------------------------------------------------------------------------- /src/MX/MegaMenu/view/adminhtml/ui_component/mx_megamenu_menu_listing.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | mx_megamenu_menu_listing.mx_megamenu_menu_listing_data_source 5 | mx_megamenu_menu_listing.mx_megamenu_menu_listing_data_source 6 | 7 | megamenu_columns 8 | 9 | 10 | add 11 | Create New Menu 12 | primary 13 | */*/create 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | Magento\Framework\View\Element\UiComponent\DataProvider\DataProvider 24 | mx_megamenu_menu_listing_data_source 25 | menu_id 26 | id 27 | 28 | 29 | Magento_Ui/js/grid/provider 30 | 31 | 32 | menu_id 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | false 43 | 55 44 | menu_id 45 | 46 | 47 | 48 | 49 | 50 | 51 | textRange 52 | asc 53 | ID 54 | 55 | 56 | 57 | 58 | 59 | 60 | text 61 | 62 | text 63 | 64 | true 65 | 66 | 67 | Name 68 | 69 | 70 | 71 | 72 | 73 | 74 | dateRange 75 | Magento_Ui/js/grid/columns/date 76 | date 77 | Created 78 | 79 | 80 | 81 | 82 | 83 | 84 | dateRange 85 | Magento_Ui/js/grid/columns/date 86 | date 87 | Modified 88 | 89 | 90 | 91 | 92 | 93 | 94 | false 95 | 120 96 | menu_id 97 | 98 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /src/MX/MegaMenu/Data/Form/Element/Chooser/Category.php: -------------------------------------------------------------------------------- 1 | widgetChooser = $widgetChooser; 68 | $this->categoryFactory = $categoryFactory; 69 | $this->jsonEncoder = $jsonEncoder; 70 | 71 | parent::__construct($factoryElement, $factoryCollection, $escaper, $data); 72 | } 73 | 74 | public function getElementHtml() 75 | { 76 | $this->prepareElementHtml($this); 77 | 78 | $html = $this->getBeforeElementHtml() 79 | . '
    ' . $this->getText() . '
    ' 80 | . $this->getAfterElementHtml() 81 | . $this->getAfterElementJs(); 82 | 83 | return $html; 84 | } 85 | 86 | protected function prepareElementHtml(AbstractElement $element) 87 | { 88 | $id = $this->getId(); 89 | $config = $this->getConfig(); 90 | 91 | // Chooser element 92 | $currentValue = $element->getValue(); 93 | $currentLabel = $config->getLabel(); 94 | $element->setId($id . 'label')->setForm($element->getForm()); 95 | if ($currentValue) { 96 | $value = explode('/', $currentValue); 97 | $categoryId = false; 98 | 99 | if (isset($value[0]) && isset($value[1]) && $value[0] == 'category') { 100 | $categoryId = $value[1]; 101 | } 102 | 103 | if ($categoryId) { 104 | $currentLabel = $this->categoryFactory->create()->load($categoryId)->getName(); 105 | } 106 | } 107 | $element->setText($currentLabel); 108 | 109 | // Hidden element 110 | $hidden = $this->_factoryElement->create(Hidden::class, ['data' => $element->getData()]); 111 | $hidden->setId($id . 'value')->setForm($element->getForm())->setValue($currentValue); 112 | $hiddenHtml = $hidden->getElementHtml(); 113 | 114 | // Button 115 | $buttons = $config->getButtons(); 116 | $button = $this->widgetChooser->getLayout()->createBlock( 117 | Button::class 118 | )->setType( 119 | 'button' 120 | )->setId( 121 | $id . 'control' 122 | )->setClass( 123 | 'btn-chooser' 124 | )->setLabel( 125 | $buttons['open'] 126 | )->setDisabled( 127 | $element->getReadonly() 128 | ); 129 | $element->setData('after_element_html', $hiddenHtml . $button->toHtml()); 130 | 131 | // Chooser Scripts 132 | $sourceUrl = $this->widgetChooser->getUrl( 133 | 'catalog/category_widget/chooser', 134 | ['uniq_id' => $id, 'use_massaction' => false] 135 | ); 136 | $configJson = $this->jsonEncoder->encode($config->getData()); 137 | $afterElementJs = ' 138 | 158 | '; 159 | $element->setData('after_element_js', $afterElementJs); 160 | } 161 | 162 | /** 163 | * Convert Array config to Object 164 | * 165 | * @return DataObject 166 | */ 167 | protected function getConfig() 168 | { 169 | $config = new DataObject(); 170 | $this->setConfig($config); 171 | 172 | // define chooser label 173 | $config->setData('label', self::DEFAULT_CATEGORY_LABEL); 174 | 175 | // chooser control buttons 176 | $buttons = [ 177 | 'open' => __(self::BUTTON_OPEN_LABEL), 178 | 'close' => __(self::BUTTON_CLOSE_LABEL) 179 | ]; 180 | 181 | $config->setButtons($buttons); 182 | 183 | return $config; 184 | } 185 | 186 | } 187 | -------------------------------------------------------------------------------- /src/MX/MegaMenu/view/frontend/web/js/view/megamenu.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'jquery', 3 | 'matchMedia', 4 | 'jquery/ui' 5 | ], function($, mediaCheck) { 6 | 'use strict'; 7 | 8 | $.widget('mx.megaMenu', { 9 | _create: function() { 10 | this.init(); 11 | this.bind(); 12 | }, 13 | 14 | init: function() { 15 | var self = this; 16 | 17 | if (!$('html').hasClass('mx-megamenu-init')) { 18 | $('html').addClass('mx-megamenu-init'); 19 | 20 | this.element.data('mage-menu', 1); // Add mageMenu attribute to fix breadcrumbs rendering on product page 21 | 22 | $(document).on('click', '.action.nav-toggle', function () { 23 | if ($('html').hasClass('nav-open')) { 24 | $('html').removeClass('nav-open'); 25 | 26 | self.element.find('.level-top').removeClass('current'); 27 | self.element.find('.mx-megamenu__item').removeClass('current'); 28 | 29 | setTimeout(function () { 30 | $('html').removeClass('nav-before-open'); 31 | }, 300); 32 | } else { 33 | $('html').addClass('nav-before-open'); 34 | 35 | setTimeout(function () { 36 | $('html').addClass('nav-open'); 37 | }, 42); 38 | } 39 | }); 40 | } 41 | 42 | this._adjustSubMenuItems(); 43 | 44 | // Add class for nav-anchor where the link has href 45 | this.element.find('.mx-megamenu__item .mx-megamenu__link').each(function(i, item) { 46 | if (self._hasSubmenu($(item))) { 47 | $(item).addClass('has-submenu'); 48 | } 49 | }); 50 | }, 51 | 52 | bind: function() { 53 | var self = this; 54 | 55 | mediaCheck({ 56 | media: '(min-width: 1025px)', 57 | 58 | /** 59 | * Switch to Desktop Version. 60 | */ 61 | entry: function () { 62 | self.element.find('.level-top').hover(function() { 63 | self.element.find('.level-top').removeClass('current'); 64 | $(this).addClass('current'); 65 | }, function() { 66 | $(this).removeClass('current'); 67 | }); 68 | 69 | /** 70 | * New functionality - toggle 71 | */ 72 | self.element.find('.level1').find('.nav-anchor').each(function(i, el) { 73 | if ($(el).hasClass('hide')) { 74 | $(el).next('.mx-megamenu__submenu').hide(); 75 | } 76 | 77 | if ($(el).hasClass('toggle')) { 78 | $(el).next('.mx-megamenu__submenu').hide(); 79 | $(el).on('mouseenter', function() { 80 | $(el).next('.mx-megamenu__submenu').show(); 81 | }); 82 | 83 | $(el).next('.mx-megamenu__submenu').on('mouseleave', function() { 84 | $(this).hide(); 85 | }); 86 | } 87 | }); 88 | }, 89 | /** 90 | * Switch to Mobile Version. 91 | */ 92 | exit: function () { 93 | var $item, 94 | $items; 95 | 96 | // Init sidebar links. Add link class for sidebar elements 97 | self._initSideBarLinks(); 98 | 99 | self.element.find('.mx-megamenu__item > .mx-megamenu__link').on('click', function(e) { 100 | $item = $(e.target).closest('.mx-megamenu__item'); 101 | 102 | if (self._canShowSubmenu($(e.target))) { 103 | // Open, close 104 | e.preventDefault(); 105 | 106 | if (!$item.hasClass('current')) { 107 | if ($item.hasClass('level0')) { 108 | $items = $('.level0.mx-megamenu__item'); 109 | } 110 | 111 | if ($item.hasClass('level1')) { 112 | $items = $('.level1.mx-megamenu__item'); 113 | } 114 | 115 | if ($items.length) { 116 | $items.not($item).removeClass('current'); 117 | } 118 | } 119 | 120 | $item.toggleClass('current'); 121 | } 122 | }); 123 | } 124 | }); 125 | }, 126 | 127 | // Adjust sub menu items where wrapper is defined 128 | _adjustSubMenuItems: function() { 129 | var $parent, 130 | $subMenu; 131 | 132 | $('.mx-megamenu__submenu.wrapper').each(function(wrapperIndex, wrapperItem) { 133 | $subMenu = $(this); 134 | $parent = $subMenu.parent(); 135 | 136 | $parent.find('.mx-megamenu__item').each(function(i, item) { 137 | $subMenu.append($(item)); 138 | }); 139 | }); 140 | }, 141 | 142 | // Sidebar items with "menu-sidebar__item" class can be handled as generated links 143 | _initSideBarLinks: function() { 144 | this.element.find('.menu-sidebar__item').each(function(i, item) { 145 | $(item).addClass('mx-megamenu__item').addClass('level1'); 146 | $(item).find('h4, a').addClass('mx-megamenu__link'); 147 | $(item).find('ul').addClass('mx-megamenu__submenu'); 148 | }); 149 | }, 150 | 151 | _canShowSubmenu: function($item) { 152 | var $link = $item.closest('.mx-megamenu__link'); 153 | 154 | return this._hasSubmenu($link) || $link.next('.mx-megamenu__submenu').length 155 | }, 156 | 157 | _hasSubmenu: function($item) { 158 | return $item.next('.mx-megamenu__submenu').length == 1; 159 | } 160 | }); 161 | 162 | return $.mx.megaMenu; 163 | }); 164 | -------------------------------------------------------------------------------- /src/MX/MegaMenu/view/adminhtml/web/js/megamenu/form.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'jquery', 3 | 'MXMegaMenuFormBuilder', 4 | 'MXMegaMenuFormDialog', 5 | 'jquery/ui' 6 | ], function ($, formBuilder, menuDialog) { 7 | "use strict"; 8 | 9 | var defaultLabel = 'Menu Item', 10 | categoryLabelId = 'category-label', 11 | $generalNameContainer, 12 | $categoryLabelContainer; 13 | 14 | $.widget('mx.megaMenuForm', { 15 | options: { 16 | statusDisabled: 0, 17 | statusEnabled: 1 18 | }, 19 | 20 | _create: function() { 21 | this.bind(); 22 | }, 23 | 24 | _init: function() { 25 | var $form = this._getForm(); 26 | $generalNameContainer = $('#general_name'); 27 | $categoryLabelContainer = $('#content_categorylabel'); 28 | 29 | $form.find('.field-content_category').hide(); // Hide the category selector at content 30 | $form.find('.field-content_category_type').hide(); // Hide the category type selector at content 31 | $form.find('.field-remove_category_anchor').hide(); // Hide the remove category link selector at content 32 | 33 | // Load saved params 34 | this._loadSavedParams(); 35 | }, 36 | 37 | bind: function() { 38 | var self = this; 39 | 40 | // Save form 41 | this.element.find('.action-primary').on('click', function() { 42 | self._saveForm(); 43 | }); 44 | 45 | // Category / Content Switcher 46 | $('#content_chooser').on('change', function() { 47 | var value = $(this).val(); 48 | 49 | self._switchContent(value); 50 | }); 51 | 52 | // Category Select 53 | $('#content_categorycontrol').on('click', function() { 54 | if (typeof window.content_category !== 'undefined') { 55 | window.content_category.choose(); 56 | } 57 | }); 58 | 59 | // Toggle - Status Box Change 60 | $('input[type="checkbox"]').on('change', function() { 61 | if ($(this).prop('checked')) { 62 | $(this).val(self.options.statusEnabled); 63 | } else { 64 | $(this).val(self.options.statusDisabled); 65 | } 66 | }); 67 | }, 68 | 69 | _loadSavedParams: function() { 70 | var self = this, 71 | itemId = this._getItemId(), 72 | $form = this._getForm(), 73 | $megaMenuContainer = formBuilder().getMegaMenuContainer(itemId), 74 | $dataProvider = formBuilder().getDataProvider(itemId), 75 | $elements = $megaMenuContainer.find('>.form').find('.menu_item_hidden'), 76 | $formElement, 77 | params, 78 | value; 79 | 80 | if ($elements.length) { 81 | $elements.each(function(i, el) { 82 | params = formBuilder().decodeParams($(el).val()); 83 | $formElement = $form.find('[name="' + params.name + '"]'); 84 | if ($formElement.length) { 85 | // Reset form element first 86 | $formElement.val(''); 87 | 88 | value = params.value; 89 | 90 | if ($formElement.is('select')) { 91 | $formElement.val(value); 92 | $formElement.trigger('change'); 93 | } 94 | 95 | if ($formElement.is('input') || $formElement.is('textarea')) { 96 | $formElement.val(formBuilder().decodeContent(params.name, value)); 97 | } 98 | 99 | if ($formElement.hasClass('onoffswitch-checkbox')) { 100 | $formElement.val(value); 101 | if (params.value == self.options.statusEnabled) { 102 | $formElement.prop('checked', true); 103 | } 104 | } 105 | } 106 | }); 107 | } 108 | 109 | if ($dataProvider.length) { 110 | // Load the category name back 111 | params = formBuilder().decodeParams($dataProvider.val()); 112 | if (params.name === categoryLabelId) { 113 | $categoryLabelContainer.html(params.value); 114 | } 115 | } else { 116 | // No params defined - set default label for name 117 | $generalNameContainer.val(defaultLabel); 118 | } 119 | }, 120 | 121 | _saveForm: function() { 122 | var $form = this._getForm(), 123 | itemId = this._getItemId(), 124 | miscData, 125 | $dataProvider = formBuilder().getDataProvider(itemId), 126 | $megaMenuContainer = formBuilder().getMegaMenuContainer(itemId), 127 | name = defaultLabel, 128 | $formNameValue = $.trim($generalNameContainer.val()); 129 | 130 | // Save form data 131 | $form.find('input,select,textarea').each(function(i, el) { 132 | var name = $(el).attr('name'), 133 | value = $(el).val(); 134 | 135 | formBuilder().saveHiddenElement(itemId, name, formBuilder().encodeContent(name, value)); 136 | }); 137 | 138 | // Save misc data - category name 139 | miscData = { 140 | 'name': 'category-label', 141 | 'value': $categoryLabelContainer.html() 142 | }; 143 | $dataProvider.val(formBuilder().encodeParams(miscData)); 144 | 145 | // Pass Category name for menu item 146 | if ($formNameValue !== '') { 147 | name = $formNameValue; 148 | } 149 | $megaMenuContainer.find('>.drag-menu').find('.label').html(name); 150 | 151 | menuDialog().closeDialog(); 152 | }, 153 | 154 | _switchContent: function(value) { 155 | var $form = this._getForm(), 156 | itemId = this._getItemId(), 157 | $element; 158 | 159 | $element = (value === 'category') ? $form.find('.field-content_' + value) : $form.find('.field-content_' + value + '_' + itemId); 160 | if ($element.length) { 161 | $form.find('.field-content_wysiwyg_' + itemId).hide(); 162 | $form.find('.field-content_category').hide(); 163 | $form.find('.field-content_category_type').hide(); 164 | $form.find('.field-remove_category_anchor').hide(); 165 | $element.show(); 166 | 167 | if (value === 'category') { 168 | $form.find('.field-content_category_type').show(); 169 | $form.find('.field-remove_category_anchor').show(); 170 | } 171 | } 172 | }, 173 | 174 | _getItemId: function() { 175 | var $form = this._getForm(); 176 | 177 | return $form.find('input[name="menu_item_id"]').val(); 178 | }, 179 | 180 | _getForm: function() { 181 | return this.element.find('form'); 182 | } 183 | }); 184 | 185 | return $.mx.megaMenuForm; 186 | }); -------------------------------------------------------------------------------- /src/MX/MegaMenu/Model/ResourceModel/Menu.php: -------------------------------------------------------------------------------- 1 | storeManager = $storeManager; 57 | $this->entityManager = $entityManager; 58 | $this->metadataPool = $metadataPool; 59 | 60 | parent::__construct($context, $connectionName); 61 | } 62 | 63 | protected function _construct() 64 | { 65 | $this->_init('mx_megamenu', 'menu_id'); 66 | } 67 | 68 | /** 69 | * @param AbstractModel $object 70 | * @param mixed $value 71 | * @param null $field 72 | * @return bool|int|string 73 | * @throws LocalizedException 74 | * @throws \Exception 75 | */ 76 | private function getMenuId(AbstractModel $object, $value, $field = null) 77 | { 78 | $entityMetadata = $this->metadataPool->getMetadata(MenuInterface::class); 79 | if (!is_numeric($value) && $field === null) { 80 | $field = MenuModel::MENU_ID; 81 | } elseif (!$field) { 82 | $field = $entityMetadata->getIdentifierField(); 83 | } 84 | 85 | $entityId = $value; 86 | if ($field != $entityMetadata->getIdentifierField() || $object->getStoreId()) { 87 | $select = $this->_getLoadSelect($field, $value, $object); 88 | $select->reset(Select::COLUMNS) 89 | ->columns($this->getMainTable() . '.' . $entityMetadata->getIdentifierField()) 90 | ->limit(1); 91 | 92 | $result = $this->getConnection()->fetchCol($select); 93 | $entityId = count($result) ? $result[0] : false; 94 | } 95 | 96 | return $entityId; 97 | } 98 | 99 | /** 100 | * Load an object 101 | * 102 | * @param MenuModel|AbstractModel $object 103 | * @param mixed $value 104 | * @param string $field field to load by (defaults to model id) 105 | * @return $this 106 | */ 107 | public function load(AbstractModel $object, $value, $field = null) 108 | { 109 | $menuId = $this->getMenuId($object, $value, $field); 110 | if ($menuId) { 111 | $this->entityManager->load($object, $menuId); 112 | } 113 | 114 | return $this; 115 | } 116 | 117 | /** 118 | * @param AbstractModel $object 119 | * @return $this 120 | * @throws \Exception 121 | */ 122 | public function save(AbstractModel $object) 123 | { 124 | $this->entityManager->save($object); 125 | return $this; 126 | } 127 | 128 | /** 129 | * @inheritDoc 130 | */ 131 | public function delete(AbstractModel $object) 132 | { 133 | $this->entityManager->delete($object); 134 | return $this; 135 | } 136 | 137 | /** 138 | * Delete all menu and items 139 | */ 140 | public function deleteAll() 141 | { 142 | $connection = $this->getConnection(); 143 | 144 | // Disable foreign key check for truncate 145 | $sql = "SET FOREIGN_KEY_CHECKS=0;"; 146 | $connection->query($sql); 147 | 148 | foreach ($this->tableNames as $tableName) { 149 | $connection->truncateTable($this->getTable($tableName)); 150 | } 151 | 152 | // Enable foreign key check for truncate 153 | $sql = "SET FOREIGN_KEY_CHECKS=1;"; 154 | $connection->query($sql); 155 | } 156 | 157 | /** 158 | * Get store ids to which specified item is assigned 159 | * 160 | * @param int $id 161 | * @return array 162 | */ 163 | public function lookupStoreIds($id) 164 | { 165 | $connection = $this->getConnection(); 166 | 167 | $entityMetadata = $this->metadataPool->getMetadata(MenuInterface::class); 168 | $linkField = $entityMetadata->getLinkField(); 169 | 170 | $select = $connection->select() 171 | ->from(['cbs' => $this->getTable('mx_megamenu_store')], 'store_id') 172 | ->join( 173 | ['cb' => $this->getMainTable()], 174 | 'cbs.' . $linkField . ' = cb.' . $linkField, 175 | [] 176 | ) 177 | ->where('cb.' . $entityMetadata->getIdentifierField() . ' = :menu_id'); 178 | 179 | return $connection->fetchCol($select, ['menu_id' => (int)$id]); 180 | } 181 | 182 | /** 183 | * Get store ids to which specified item is assigned 184 | * 185 | * @param int $id 186 | * @return array 187 | */ 188 | public function lookupMenuItems($id) 189 | { 190 | $connection = $this->getConnection(); 191 | 192 | $entityMetadata = $this->metadataPool->getMetadata(MenuInterface::class); 193 | $linkField = $entityMetadata->getLinkField(); 194 | 195 | $select = $connection->select() 196 | ->from(['cbs' => $this->getTable('mx_megamenu_item')]) 197 | ->join( 198 | ['cb' => $this->getMainTable()], 199 | 'cbs.' . $linkField . ' = cb.' . $linkField, 200 | [] 201 | ) 202 | ->where('cb.' . $entityMetadata->getIdentifierField() . ' = :menu_id'); 203 | 204 | return $connection->fetchAssoc($select, ['menu_id' => (int)$id]); 205 | } 206 | 207 | /** 208 | * Retrieve select object for load object data 209 | * 210 | * @param string $field 211 | * @param mixed $value 212 | * @param \Magento\Cms\Model\Block|AbstractModel $object 213 | * @return Select 214 | */ 215 | protected function _getLoadSelect($field, $value, $object) 216 | { 217 | $entityMetadata = $this->metadataPool->getMetadata(MenuInterface::class); 218 | $linkField = $entityMetadata->getLinkField(); 219 | 220 | $select = parent::_getLoadSelect($field, $value, $object); 221 | 222 | if ($object->getStoreId()) { 223 | $stores = [(int)$object->getStoreId(), Store::DEFAULT_STORE_ID]; 224 | 225 | $select->reset(Select::WHERE) 226 | ->join( 227 | ['cbs' => $this->getTable('mx_megamenu_store')], 228 | $this->getMainTable() . '.' . $linkField . ' = cbs.' . $linkField, 229 | ['store_id'] 230 | ) 231 | ->where($this->getMainTable() . '.status = ?', MenuModel::STATUS_ENABLED) 232 | ->where('cbs.store_id in (?)', $stores) 233 | ->order('store_id DESC') 234 | ->limit(1); 235 | } 236 | 237 | return $select; 238 | } 239 | } -------------------------------------------------------------------------------- /src/MX/MegaMenu/view/adminhtml/web/js/megamenu/form/builder.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'jquery', 3 | 'mage/template', 4 | 'jquery/ui' 5 | ], function ($, mageTemplate) { 6 | "use strict"; 7 | 8 | var $megaMenuContainer, 9 | $structureContainer, 10 | megaMenuEditor = '#megamenu-editor', 11 | menuItemTemplate = '#menu-item-template'; 12 | 13 | $.widget('mx.megaMenuFormBuilder', { 14 | _create: function() { 15 | $megaMenuContainer = $('#megamenu-editor'); 16 | $structureContainer = $megaMenuContainer.find('.structure'); 17 | }, 18 | 19 | buildItem: function(item) { 20 | var self = this, 21 | miscData, 22 | itemId = item['menu_item_id'], 23 | $dataProvider; 24 | 25 | // Build main list element 26 | var tmpl = mageTemplate(menuItemTemplate), 27 | $parentElement, 28 | label = this.decodeContent('name', item['name']); 29 | 30 | var newItem = tmpl({ 31 | data: { 32 | id: itemId, 33 | label: label, 34 | classname: item['classname'] || '' 35 | } 36 | }); 37 | 38 | if (item['menu_item_parent_id'] == 0) { 39 | // No parent 40 | $structureContainer.append(newItem); 41 | } else { 42 | // Nested 43 | $parentElement = $('.dd-item-' + item['menu_item_parent_id']); 44 | if ($parentElement.length) { 45 | if (!$parentElement.find('.dd-list').length) { 46 | $parentElement.append('
      '); 47 | } 48 | $parentElement.find('.dd-list').append(newItem); 49 | } 50 | } 51 | 52 | // Build hidden values 53 | $.each(item, function(name, value) { 54 | self.saveHiddenElement(itemId, name, value); 55 | }); 56 | 57 | // Build data for data provider 58 | miscData = { 59 | 'name': 'category-label', 60 | 'value': item['category_name'] == '' ? 'Not Selected' : item['category_name'] 61 | }; 62 | $dataProvider = this.getDataProvider(itemId); 63 | $dataProvider.val(this.encodeParams(miscData)); 64 | }, 65 | 66 | saveHiddenElement: function(itemId, name, value) { 67 | var $megaMenuContainer = this.getMegaMenuContainer(itemId), 68 | elementClassName = this.getHiddenElementClassName(itemId, name), 69 | elementValue = this.getHiddenElementValue(name, value), 70 | $element = $megaMenuContainer.find('>.form').find('.' + elementClassName); 71 | 72 | if ($element.length) { 73 | $element.val(elementValue); 74 | } else { 75 | this.createHiddenElement(itemId, name, value); 76 | } 77 | }, 78 | 79 | createHiddenElement: function(itemId, name, value) { 80 | var $megaMenuContainer = this.getMegaMenuContainer(itemId), 81 | elementClassName = this.getHiddenElementClassName(itemId, name), 82 | elementName = this.getHiddenElementName(itemId), 83 | elementValue = this.getHiddenElementValue(name, value); 84 | 85 | $megaMenuContainer.find('>.form').append(""); 86 | }, 87 | 88 | getHiddenElementValue: function(name, value) { 89 | // Set default value for category type (new field > 1.1.0) 90 | if (name === 'content_category_type' && value === '') { 91 | value = 'show'; 92 | } 93 | 94 | var params = { 95 | 'name': name, 96 | 'value': value 97 | }; 98 | 99 | return this.encodeParams(params); 100 | }, 101 | 102 | getHiddenElementName: function(itemId) { 103 | return 'menu_item_' + itemId; 104 | }, 105 | 106 | getHiddenElementClassName: function(itemId, name) { 107 | return 'menu_item_' + itemId + '_' + name; 108 | }, 109 | 110 | getDataProvider: function(itemId) { 111 | return this.getMegaMenuContainer(itemId).find('>.form').find('.data-provider'); 112 | }, 113 | 114 | getMegaMenuContainer: function(itemId) { 115 | return $(megaMenuEditor).find('.dd-item-' + itemId); 116 | }, 117 | 118 | encodeParams: function(params) { 119 | if (params !== '') { 120 | return JSON.stringify(params); 121 | } 122 | 123 | return ''; 124 | }, 125 | 126 | decodeParams: function(params) { 127 | if (params !== '') { 128 | return JSON.parse(params); 129 | } 130 | 131 | return ''; 132 | }, 133 | 134 | encodeContent: function(name, value) { 135 | if (name.match('_content')) { 136 | value = this._convertContentForEditor(value); 137 | 138 | return window.btoa(value); 139 | } 140 | 141 | if (name.match('link') || name === 'name') { 142 | return window.btoa(value); 143 | } 144 | 145 | return value; 146 | }, 147 | 148 | decodeContent: function(name, value) { 149 | if (name.match('_content')) { 150 | value = window.atob(value); 151 | value = this._decodeSpecialCharacters(value); 152 | 153 | return this._convertContentForEditor(value); 154 | } 155 | 156 | if (name === 'name') { 157 | value = window.atob(value); 158 | value = this._decodeSpecialCharacters(value); 159 | 160 | return this._htmlEntityDecode(value); 161 | } 162 | 163 | if (name.match('link')) { 164 | return window.atob(value); 165 | } 166 | 167 | return value; 168 | }, 169 | 170 | _htmlEntityDecode: function(content) { 171 | var $elem = $('
      '); 172 | 173 | $elem.html(content); 174 | 175 | return $elem.html().replace(/&/g, '&'); 176 | }, 177 | 178 | /** 179 | * Decode special symbols e.g. ® ©right; 180 | * 181 | * @param string content 182 | * @returns {*} 183 | * @private 184 | */ 185 | _decodeSpecialCharacters: function(content) { 186 | return content.replace(/{amp}/g, '&').replace(/{comma}/g, ';'); 187 | }, 188 | 189 | /** 190 | * Convert content - workaround for html contents in widget textarea fields 191 | * 192 | * @param string content 193 | * @returns string 194 | * @private 195 | */ 196 | _convertContentForEditor: function(content) { 197 | content = content 198 | .replace(/>\s+</g,'><') // <> 199 | .replace(/"/g, '"') // Fix for M2 Wysiwyg Editor 200 | .replace(/\'/g, '"'); // Backward compatiblity <= 1.1.2 201 | 202 | return this._convertSpecialAttributes(content); 203 | }, 204 | 205 | /** 206 | * Convert special attributes. 207 | * Place any logic here that relates to any desired html attributes. 208 | * This is within the widget string e.g. content="something" 209 | * 210 | * @param string content 211 | * @returns string 212 | * @private 213 | */ 214 | _convertSpecialAttributes: function(content) { 215 | return content.replace(/(href|src|class)="(.*?)"/g, '$1="$2"'); 216 | } 217 | }); 218 | 219 | return $.mx.megaMenuFormBuilder; 220 | }); 221 | -------------------------------------------------------------------------------- /src/MX/MegaMenu/Model/Menu/Item.php: -------------------------------------------------------------------------------- 1 | categoryRepository = $categoryRepository; 56 | $this->filterProvider = $filterProvider; 57 | parent::__construct($context, $registry, $resource, $resourceCollection, $data); 58 | } 59 | 60 | /** 61 | * Get Item Data 62 | * 63 | * @param array $item 64 | * @return array 65 | */ 66 | public function getItemData($item) 67 | { 68 | return [ 69 | 'status' => $this->isEnabled($item, 'status'), 70 | 'name' => $this->getDecodedContent($item['name']), 71 | 'link' => $this->getItemLink($item), 72 | 'header_status' => $this->isEnabled($item, 'header_status'), 73 | 'header_content' => $this->getDecodedContent($item['header_content']), 74 | 'content_status' => $this->isEnabled($item, 'content_status'), 75 | 'content_content' => $this->getDecodedContent($item['content_content']), 76 | 'content_categories' => $this->getCategories($item), 77 | 'content_category_type' => $item['content_category_type'], 78 | 'remove_category_anchor' => $item['remove_category_anchor'], 79 | 'custom_class' => $item['custom_class'], 80 | 'footer_status' => $this->isEnabled($item, 'footer_status'), 81 | 'footer_content' => $this->getDecodedContent($item['footer_content']), 82 | 'leftside_status' => $this->isEnabled($item, 'leftside_status'), 83 | 'leftside_content' => $this->getDecodedContent($item['leftside_content']), 84 | 'rightside_status' => $this->isEnabled($item, 'rightside_status'), 85 | 'rightside_content' => $this->getDecodedContent($item['rightside_content']), 86 | 'level' => self::LEVEL_DEFAULT 87 | ]; 88 | } 89 | 90 | /** 91 | * Need encode 92 | * 93 | * @param string $name 94 | * @return boolean 95 | */ 96 | public function needEncode($name) 97 | { 98 | return strpos($name, '_content') !== false || strpos($name, 'link') !== false 99 | || $name === 'name'; 100 | } 101 | 102 | /** 103 | * Encode entities - do not encode & " < > 104 | * It's for very special cases: e.g. ® ©right; 105 | * 106 | * @param string $content 107 | * @return string 108 | */ 109 | public function encodeSpecialCharacters($content) 110 | { 111 | return preg_replace('#(&)([^amp|quot|lt|gt].*?)(;)#', '{amp}$2{comma}', $content); 112 | } 113 | 114 | /** 115 | * Decode entities - see encode special characters above 116 | * 117 | * @param string $content 118 | * @return string 119 | */ 120 | public function decodeSpecialCharacters($content) 121 | { 122 | return str_replace(['{amp}', '{comma}'], ['&', ';'], $content); 123 | } 124 | 125 | /** 126 | * Encode content 127 | * 128 | * @param string $content 129 | * @return string 130 | */ 131 | public function encodeContent($content) 132 | { 133 | return htmlentities($content); 134 | } 135 | 136 | /** 137 | * Decode content 138 | * 139 | * @param string $content 140 | * @return string 141 | */ 142 | public function decodeContent($content) 143 | { 144 | return html_entity_decode($content, ENT_QUOTES); 145 | } 146 | 147 | /** 148 | * Get category name 149 | * 150 | * @param string $item 151 | * @return string 152 | */ 153 | public function getCategoryName($item) 154 | { 155 | $category = $this->getCategory($item['content_category']); 156 | if (!is_null($category)) { 157 | return $category->getName(); 158 | } 159 | 160 | return ''; 161 | } 162 | 163 | /** 164 | * Get categories 165 | * 166 | * @param array $item 167 | * @return array|string 168 | */ 169 | protected function getCategories($item) 170 | { 171 | if ($item['content_type'] === self::CONTENT_TYPE_CATEGORY) { 172 | $category = $this->getCategory($item['content_category']); 173 | // Parent Category 174 | if (!is_null($category)) { 175 | $categories = [ 176 | 'category' => $this->getCategoryData($category), 177 | 'children' => [] 178 | ]; 179 | 180 | // Overwrite main category link if link is defined already for the menu item 181 | if (!empty($item['link'])) { 182 | $categories['category']['link'] = $this->getItemLink($item); 183 | } 184 | 185 | // Remove main category link if it is defined separately 186 | if (!empty($item['remove_category_anchor'])) { 187 | $categories['category']['link'] = ''; 188 | } 189 | 190 | // Subcategories 191 | if ($this->isChildrenCategoriesVisible($item)) { 192 | $subcategories = $category->getChildrenCategories(); 193 | foreach ($subcategories as $subcategory) { 194 | $categories['children'][] = $this->getCategoryData($subcategory); 195 | } 196 | } 197 | 198 | return $categories; 199 | } 200 | } 201 | 202 | return ''; 203 | } 204 | 205 | /** 206 | * Get Category 207 | * 208 | * @param string $categoryIdPath 209 | * @return Category|null 210 | */ 211 | protected function getCategory($categoryIdPath) 212 | { 213 | try { 214 | $contentCategory = explode('/', $categoryIdPath); 215 | if ($contentCategory && isset($contentCategory[self::OFFSET_CATEGORY_ID])) { 216 | $categoryId = $contentCategory[self::OFFSET_CATEGORY_ID]; 217 | 218 | return $this->categoryRepository->get($categoryId); 219 | } 220 | } catch (NoSuchEntityException $error) { 221 | // Magento Category Repository throws NoSuchEntityException when no category found. That shouldn't break the FE rendering 222 | $this->_logger->error($error); 223 | } 224 | 225 | return null; 226 | } 227 | 228 | /** 229 | * Is children categories visible 230 | * 231 | * @param array $item 232 | * @return boolean 233 | */ 234 | protected function isChildrenCategoriesVisible($item) 235 | { 236 | return $item['content_category_type'] === self::CATEGORY_TYPE_SHOW 237 | || $item['content_category_type'] === self::CATEGORY_TYPE_TOGGLE; 238 | } 239 | 240 | /** 241 | * Get Category Data 242 | * 243 | * @param Category $category 244 | * @return array 245 | */ 246 | protected function getCategoryData($category) 247 | { 248 | $result = []; 249 | 250 | if ($category->getId() && $category->getIsActive() == 1) { 251 | $result = [ 252 | 'id' => $category->getId(), 253 | 'name' => $category->getName(), 254 | 'link' => $category->getUrl(), 255 | ]; 256 | } 257 | 258 | return $result; 259 | } 260 | 261 | /** 262 | * Is the property enabled 263 | * 264 | * @param array $item 265 | * @param string $property 266 | * @return boolean 267 | */ 268 | protected function isEnabled($item, $property) 269 | { 270 | return isset($item[$property]) && $item[$property] == self::STATUS_ENABLED; 271 | } 272 | 273 | /** 274 | * Get Decoded Content 275 | * 276 | * @param string $content 277 | * @return string 278 | */ 279 | protected function getDecodedContent($content) 280 | { 281 | $content = $this->decodeContent($content); 282 | $content = $this->decodeSpecialCharacters($content); 283 | 284 | return $this->filterProvider->getBlockFilter()->filter($content); 285 | } 286 | 287 | /** 288 | * Get Item Link 289 | * 290 | * @param array $item 291 | * @return string 292 | */ 293 | protected function getItemLink($item) 294 | { 295 | if (!empty($item['link'])) { 296 | return $this->getDecodedContent($item['link']); // Also resolve magento url directives 297 | } 298 | 299 | return 'javascript:;'; // For opening submenu items, no links defined 300 | } 301 | } -------------------------------------------------------------------------------- /src/MX/MegaMenu/Model/Menu.php: -------------------------------------------------------------------------------- 1 | menuItemFactory = $menuItemFactory; 44 | parent::__construct($context, $registry, $resource, $resourceCollection, $data); 45 | } 46 | 47 | protected function _construct() 48 | { 49 | $this->_init(ResourceMenu::class); 50 | } 51 | 52 | public function getIdentities() 53 | { 54 | return [self::CACHE_TAG . '_' . $this->getMenuId()]; 55 | } 56 | 57 | /** 58 | * Retrieve menu id 59 | * 60 | * @return integer 61 | */ 62 | public function getMenuId() 63 | { 64 | return $this->getData(self::MENU_ID); 65 | } 66 | 67 | /** 68 | * Retrieve name 69 | * 70 | * @return string 71 | */ 72 | public function getName() 73 | { 74 | return $this->getData(self::NAME); 75 | } 76 | 77 | /** 78 | * Retrieve link 79 | * 80 | * @return string 81 | */ 82 | public function getLink() 83 | { 84 | return $this->getData(self::LINK); 85 | } 86 | 87 | /** 88 | * Retrieve creation time 89 | * 90 | * @return string 91 | */ 92 | public function getCreatedAt() 93 | { 94 | return $this->getData(self::CREATED_AT); 95 | } 96 | 97 | /** 98 | * Retrieve update time 99 | * 100 | * @return string 101 | */ 102 | public function getUpdatedAt() 103 | { 104 | return $this->getData(self::UPDATED_AT); 105 | } 106 | 107 | /** 108 | * Get status 109 | * 110 | * @return boolean 111 | */ 112 | public function getStatus() 113 | { 114 | return (bool)$this->getData(self::STATUS); 115 | } 116 | 117 | /** 118 | * Receive page store ids 119 | * 120 | * @return mixed 121 | */ 122 | public function getStores() 123 | { 124 | return $this->getData(self::STORE_ID); 125 | } 126 | 127 | /** 128 | * Get sorted menu items 129 | * 130 | * @return array 131 | */ 132 | public function getSortedMenuItems() 133 | { 134 | $items = $this->getMenuItems(); 135 | 136 | usort($items, function($previous, $next) { 137 | return ($previous['sort_order'] <=> $next['sort_order']); 138 | }); 139 | 140 | return $items; 141 | } 142 | 143 | /** 144 | * Receive menu items 145 | * 146 | * @return array 147 | */ 148 | public function getMenuItems() 149 | { 150 | if ($this->hasData(self::MENU_ITEMS)) { 151 | return $this->getData(self::MENU_ITEMS); 152 | } 153 | 154 | $items = []; 155 | $data = $this->_registry->registry('mx_megamenu_menu_items'); 156 | if ($data) { 157 | $menuItems = json_decode($data, true); 158 | if ($menuItems) { 159 | $items = $menuItems; 160 | $this->setData(self::MENU_ITEMS, $items); 161 | } 162 | } 163 | 164 | return $items; 165 | } 166 | 167 | /** 168 | * Get menu item ids 169 | * 170 | * @return array 171 | */ 172 | public function getMenuItemIds() 173 | { 174 | $ids = []; 175 | $items = $this->getMenuItems(); 176 | 177 | if (count($items)) { 178 | foreach ($items as $item) { 179 | $ids[] = $item['menu_item_id']; 180 | } 181 | } 182 | 183 | return $ids; 184 | } 185 | 186 | /** 187 | * Is active 188 | * 189 | * @return boolean 190 | */ 191 | public function isActive() 192 | { 193 | $status = $this->getStatus(); 194 | 195 | return $status == self::STATUS_ENABLED; 196 | } 197 | 198 | /** 199 | * Set ID 200 | * 201 | * @param integer $id 202 | * @return MenuInterface 203 | */ 204 | public function setMenuId($id) 205 | { 206 | return $this->setData(self::MENU_ID, $id); 207 | } 208 | 209 | /** 210 | * Set Stores 211 | * 212 | * @param int|array $storeId 213 | * @return $this 214 | */ 215 | public function setStores($storeId) 216 | { 217 | return $this->setData(self::STORE_ID, $storeId); 218 | } 219 | 220 | /** 221 | * Set NAME 222 | * 223 | * @param string $name 224 | * @return MenuInterface 225 | */ 226 | public function setName($name) 227 | { 228 | return $this->setData(self::NAME, $name); 229 | } 230 | 231 | /** 232 | * Set Link 233 | * 234 | * @param string $link 235 | * @return MenuInterface 236 | */ 237 | public function setLink($link) 238 | { 239 | return $this->setData(self::NAME, $link); 240 | } 241 | 242 | /** 243 | * Set creation time 244 | * 245 | * @param string $createdAt 246 | * @return MenuInterface 247 | */ 248 | public function setCreatedAt($createdAt) 249 | { 250 | return $this->setData(self::CREATED_AT, $createdAt); 251 | } 252 | 253 | /** 254 | * Set update time 255 | * 256 | * @param string $updatedAt 257 | * @return MenuInterface 258 | */ 259 | public function setUpdatedAt($updatedAt) 260 | { 261 | return $this->setData(self::UPDATED_AT, $updatedAt); 262 | } 263 | 264 | /** 265 | * Set status 266 | * 267 | * @param bool|int $status 268 | * @return MenuInterface 269 | */ 270 | public function setStatus($status) 271 | { 272 | return $this->setData(self::STATUS, $status); 273 | } 274 | 275 | /** 276 | * Set menu items 277 | * 278 | * @param array $items 279 | * @return $this 280 | */ 281 | public function setMenuItems(&$items) 282 | { 283 | $menuItem = $this->menuItemFactory->create(); 284 | foreach ($items as $id => $item) { 285 | foreach ($item as $name => $value) { 286 | if ($menuItem->needEncode($name)) { 287 | $value = $menuItem->encodeContent($value); 288 | 289 | $items[$id][$name] = $menuItem->encodeSpecialCharacters($value); 290 | } 291 | } 292 | } 293 | 294 | return $this->setData(self::MENU_ITEMS, $items); 295 | } 296 | 297 | /** 298 | * Add special items - e.g. real category name based on the category id 299 | * 300 | * @param array $items 301 | */ 302 | public function addSpecialMenuItems(&$items) 303 | { 304 | $menuItem = $this->menuItemFactory->create(); 305 | foreach ($items as $id => $item) { 306 | $items[$id]['category_name'] = $menuItem->getCategoryName($item); 307 | } 308 | } 309 | 310 | /** 311 | * Remove special items before save 312 | * 313 | * @param array $items 314 | */ 315 | public function removeSpecialMenuItems(&$items) 316 | { 317 | foreach ($items as $id => $item) { 318 | unset($items[$id]['category_name']); 319 | } 320 | } 321 | 322 | /** 323 | * Prepare menu's statuses. 324 | * 325 | * @return array 326 | */ 327 | public function getAvailableStatuses() 328 | { 329 | return [ 330 | self::STATUS_ENABLED => __('Enabled'), 331 | self::STATUS_DISABLED => __('Disabled') 332 | ]; 333 | } 334 | 335 | /** 336 | * Get Processed Menu Items 337 | * 338 | * @return array 339 | */ 340 | public function getProcessedMenuItems() 341 | { 342 | $result = []; 343 | foreach ($this->getSortedMenuItems() as $item) { 344 | $itemId = $item['menu_item_id']; 345 | $parentId = $item['menu_item_parent_id']; 346 | 347 | $menuItem = $this->menuItemFactory->create(); 348 | $menuItemData = $menuItem->getItemData($item); 349 | 350 | if ($parentId == 0) { 351 | $result[$itemId] = $menuItemData; 352 | } else { 353 | // 2nd level (1st child) 354 | if (isset($result[$parentId])) { 355 | $menuItemData['level'] = Item::LEVEL_2; 356 | $result[$parentId]['children'][$itemId] = $menuItemData; 357 | } else { 358 | // 3rd level (2nd child) 359 | $menuItemData['level'] = Item::LEVEL_3; 360 | $this->setChildrenItems($result, $item, $menuItemData); 361 | } 362 | } 363 | } 364 | 365 | return $result; 366 | } 367 | 368 | /** 369 | * Set lower level children 370 | * 371 | * @param array $result 372 | * @param array $item 373 | * @param array $menuItemData 374 | */ 375 | protected function setChildrenItems(&$result, $item, $menuItemData) 376 | { 377 | $parentId = $item['menu_item_parent_id']; 378 | $itemId = $item['menu_item_id']; 379 | 380 | foreach ($result as $id => $child) { 381 | if (isset($child['children'])) { 382 | foreach ($child['children'] as $i => $ch) { 383 | if ($i == $parentId) { 384 | $result[$id]['children'][$i]['wrapper'] = true; // Add wrapper for 3rd level items 385 | $result[$id]['children'][$i]['children'][$itemId] = $menuItemData; 386 | } 387 | } 388 | } 389 | } 390 | } 391 | } -------------------------------------------------------------------------------- /src/MX/MegaMenu/view/adminhtml/web/js/megamenu-editor.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'jquery', 3 | 'mage/template', 4 | 'MXMegaMenuFormBuilder', 5 | 'MXMegaMenuFormDialog', 6 | 'nestable', 7 | 'mage/adminhtml/browser', 8 | 'jquery/ui' 9 | ], function ($, mageTemplate, formBuilder, menuDialog) { 10 | "use strict"; 11 | 12 | var $megaMenuContainer, 13 | $structureContainer, 14 | $actionsContainer, 15 | $tabsMenu, 16 | $saveButton, 17 | $settingsForm, 18 | $menuItemsForm, 19 | $nestableContainer, 20 | dataProviderLabel = 'data-provider', 21 | categoryNameLabel = 'category-label', 22 | classnameLabel = 'classname', 23 | formKeyLabel = 'form_key', 24 | defaultMenuItemLabel = 'Menu Item'; 25 | 26 | $.widget('mx.megaMenuEditor', { 27 | options: { 28 | editUrl: '', 29 | saveUrl: '', 30 | maxDepth: 3 31 | }, 32 | 33 | _create: function() { 34 | $megaMenuContainer = this.element; 35 | $structureContainer = $megaMenuContainer.find('.structure'); 36 | $actionsContainer = $megaMenuContainer.find('.actions'); 37 | $tabsMenu = $('#tabs-menu'), 38 | $saveButton = $('#save'); 39 | $settingsForm = $('#settings_form'); 40 | $menuItemsForm = $('#menu-items-form'), 41 | $nestableContainer = $menuItemsForm.find('.nestable'); 42 | 43 | this.bind(); 44 | this.build(); 45 | }, 46 | 47 | bind: function() { 48 | var self = this; 49 | 50 | $nestableContainer.nestable({ 51 | maxDepth: self.options.maxDepth, 52 | onDragStart: function(l, e) { 53 | $(e).find('.actions').hide(); 54 | $(e).find('.dd-actions').hide(); 55 | }, 56 | beforeDragStop: function(l, e) { 57 | $(e).find('.actions').show(); 58 | $(e).find('.dd-actions').show(); 59 | } 60 | }); 61 | 62 | // Add 63 | $actionsContainer.find('.btn-add').on('click', function() { 64 | self.addMenuItem(); 65 | $("html, body").animate({ scrollTop: $(document).height() }, 1000); // Scroll to the bottom as the new menu item is added to the bottom 66 | }); 67 | 68 | //Tabs 69 | $tabsMenu.find('a').on('click', function(e) { 70 | var currentTab = $(e.target).data('target'); 71 | 72 | $tabsMenu.find('li').removeClass('active'); 73 | $(e.target).closest('li').addClass('active'); 74 | 75 | $megaMenuContainer.find('.megamenu-tabs').removeClass('active'); 76 | if ($(currentTab).length) { 77 | $(currentTab).addClass('active'); 78 | } 79 | }); 80 | 81 | // Save 82 | $saveButton.on('click', function() { 83 | self.save(); 84 | }); 85 | }, 86 | 87 | build: function() { 88 | if (menuItemsData) { 89 | // Build items 90 | this.buildItems(menuItemsData); 91 | 92 | // Add expand/collapse buttons as it's buggy for the third-party 93 | this.buildButtons(); 94 | } 95 | }, 96 | 97 | buildButtons: function() { 98 | var self = this; 99 | 100 | $structureContainer.find('.menu-item').each(function(i, el) { 101 | if ($(el).find('>.dd-list').length) { 102 | $(el).find('>.dd-actions').append(self._getButtonHtml('#btn-expand-template')); 103 | $(el).find('>.dd-actions').append(self._getButtonHtml('#btn-collapse-template')); 104 | $(el).find('>.dd-actions').find('.btn-collapse').addClass('show'); 105 | } 106 | }); 107 | 108 | $structureContainer.find('.menu-item').find('.btn-caret').on('click', function(e) { 109 | e.preventDefault(); 110 | 111 | var $this = $(e.target).closest('.btn-caret'), 112 | $parent = $this.closest('.dd-actions'), 113 | $expandButton = $parent.find('.btn-expand'), 114 | $collapseButton = $parent.find('.btn-collapse'), 115 | action = $this.data('action'), 116 | $children = $this.closest('.menu-item').find('.dd-list'); 117 | 118 | if (action === 'expand') { 119 | $collapseButton.addClass('show'); 120 | $expandButton.removeClass('show'); 121 | $children.slideDown(); 122 | } 123 | 124 | if (action === 'collapse') { 125 | $expandButton.addClass('show'); 126 | $collapseButton.removeClass('show'); 127 | $children.slideUp(); 128 | } 129 | }); 130 | }, 131 | 132 | save: function() { 133 | var self = this; 134 | 135 | if (this.options.saveUrl !== '') { 136 | var data = $settingsForm.serialize(), 137 | itemsData = {}; 138 | 139 | $structureContainer.find('.menu-item').each(function(i, el) { 140 | var itemId = $(el).data('id'), 141 | $form = $(el).find('>.form'); 142 | 143 | itemsData[itemId] = self._getDefaultsForMenuItem(itemId); 144 | 145 | $form.find('input').each(function(idx, element) { 146 | var item = formBuilder().decodeParams($(element).val()), 147 | parentId = 0, 148 | $parentElement; 149 | 150 | // Get menu item data 151 | if (!$(element).hasClass(dataProviderLabel) && self._canSaveMenuItem(item)) { 152 | itemsData[itemId][item.name] = formBuilder().decodeContent(item.name, item.value); 153 | } 154 | 155 | // Get parent 156 | $parentElement = $(element).closest('.dd-list').closest('.menu-item'); 157 | if ($parentElement.length && $parentElement.data('id') != itemId) { 158 | parentId = $parentElement.data('id'); 159 | } 160 | itemsData[itemId]['menu_item_parent_id'] = parentId; 161 | }); 162 | 163 | // Get sort order 164 | var sortOrder = $(el).index(); 165 | if (itemsData[itemId]['menu_item_parent_id'] != 0) { 166 | sortOrder = (itemsData[itemId]['menu_item_parent_id'] * 100) + sortOrder; 167 | } 168 | itemsData[itemId]['sort_order'] = sortOrder; 169 | }); 170 | 171 | if (itemsData !== '') { 172 | data += '&items=' + encodeURIComponent(formBuilder().encodeParams(itemsData)); 173 | } 174 | 175 | // Send Main Form Data 176 | $.ajax({ 177 | url: this.options.saveUrl, 178 | type: 'POST', 179 | data: data, 180 | showLoader: true, 181 | success: function(response) { 182 | if (response.status && response.url) { 183 | location.href = response.url; 184 | } 185 | } 186 | }); 187 | } 188 | }, 189 | 190 | buildItems: function(data) { 191 | var self = this, 192 | items = formBuilder().decodeParams(data); 193 | 194 | $.each(items, function(i, el) { 195 | self.addMenuItem(el); 196 | }); 197 | }, 198 | 199 | addMenuItem: function(item) { 200 | var self = this; 201 | 202 | if (typeof item === 'undefined') { 203 | var item = { 204 | menu_item_id: this._getMaxMenuItemId() + 1, 205 | menu_item_parent_id: 0, 206 | name: formBuilder().encodeContent('name', defaultMenuItemLabel), 207 | classname: 'new' 208 | }; 209 | } 210 | 211 | formBuilder().buildItem(item); 212 | 213 | $(document).on('click', '.btn-edit', function(e) { 214 | e.stopImmediatePropagation(); 215 | 216 | self.editMenuItem($(e.target).closest('.menu-item')); 217 | }); 218 | 219 | $(document).on('click', '.btn-remove', function(e) { 220 | e.stopImmediatePropagation(); 221 | 222 | self.removeMenuItem($(e.target).closest('.menu-item')); 223 | }); 224 | }, 225 | 226 | _getMaxMenuItemId: function() { 227 | var value, 228 | max = 0; 229 | 230 | $structureContainer.find('.menu-item').each(function() { 231 | value = parseInt($(this).data('id')); 232 | max = (value > max) ? value : max; 233 | }); 234 | 235 | return max; 236 | }, 237 | 238 | editMenuItem: function($element) { 239 | var additionalParams = '', 240 | $nameElement, 241 | menuItemNameValue, 242 | menuItemName = ''; 243 | 244 | if ($element.data('id')) { 245 | additionalParams = 'item_id/' + $element.data('id'); 246 | $nameElement = $element.find('.form').find('.menu_item_' + $element.data('id') + '_name'); 247 | menuItemNameValue = $nameElement.val(); 248 | menuItemNameValue = JSON.parse(menuItemNameValue); 249 | menuItemName = formBuilder().decodeContent('name', menuItemNameValue.value); 250 | } 251 | 252 | menuDialog().openDialog(this.options.editUrl + additionalParams, menuItemName); 253 | }, 254 | 255 | removeMenuItem: function($element) { 256 | if ($element.length && confirm('Are you sure you want to remove this item?')) { 257 | $element.remove(); 258 | } 259 | }, 260 | 261 | _getButtonHtml: function(templateId) { 262 | var tmpl = mageTemplate(templateId); 263 | 264 | return tmpl(); 265 | }, 266 | 267 | _canSaveMenuItem(item) { 268 | return item.name !== categoryNameLabel 269 | && item.name != formKeyLabel 270 | && item.name !== classnameLabel; 271 | }, 272 | 273 | _getDefaultsForMenuItem: function(itemId) { 274 | return { 275 | 'menu_item_id': itemId, 276 | 'status': 0, 277 | 'name': defaultMenuItemLabel, 278 | 'link': '', 279 | 'custom_class': '', 280 | 'header_status': 0, 281 | 'header_content': '', 282 | 'content_status': 0, 283 | 'content_category': '', 284 | 'content_content': '', 285 | 'content_category_type': 'show', 286 | 'content_type': 'wysiwyg', 287 | 'remove_category_anchor': 0, 288 | 'leftside_status': 0, 289 | 'leftside_content': '', 290 | 'rightside_status': 0, 291 | 'rightside_content': '', 292 | 'footer_status': 0, 293 | 'footer_content': '', 294 | 'sort_order': 0 295 | } 296 | } 297 | }); 298 | 299 | return $.mx.megaMenuEditor; 300 | }); -------------------------------------------------------------------------------- /src/MX/MegaMenu/Block/Adminhtml/Menu/Edit/Form.php: -------------------------------------------------------------------------------- 1 | 'Content', 46 | 'category' => 'Category' 47 | ]; 48 | 49 | /** 50 | * @var array 51 | */ 52 | protected $categoryTypeChooserOptions = [ 53 | 'show' => 'Show children always', 54 | 'hide' => 'Hide children always', 55 | 'toggle' => 'Toggle children' 56 | ]; 57 | 58 | /** 59 | * @param Context $context 60 | * @param Registry $registry 61 | * @param FormFactory $formFactory 62 | * @param Config $wysiwygConfig 63 | * @param CategoryFactory $categoryFactory 64 | * @param array $data 65 | */ 66 | public function __construct( 67 | Context $context, 68 | Registry $registry, 69 | FormFactory $formFactory, 70 | Config $wysiwygConfig, 71 | CategoryFactory $categoryFactory, 72 | array $data = [] 73 | ) { 74 | $this->wysiwygConfig = $wysiwygConfig; 75 | $this->categoryFactory = $categoryFactory; 76 | parent::__construct($context, $registry, $formFactory, $data); 77 | } 78 | 79 | protected function _construct() 80 | { 81 | parent::_construct(); 82 | 83 | $this->setId('wysiwyg_form'); 84 | $this->setTitle(__('Edit Menu Item')); 85 | } 86 | 87 | protected function _prepareForm() 88 | { 89 | $itemId = $this->getRequest()->getParam('item_id', 0); 90 | 91 | $form = $this->_formFactory->create( 92 | ['data' => 93 | [ 94 | 'id' => 'edit_form', 95 | 'enctype' => 'multipart/form-data', 96 | 'action' => $this->getData('action'), 97 | 'method' => 'post' 98 | ] 99 | ] 100 | ); 101 | 102 | $general = $form->addFieldset( 103 | 'general_fieldset', 104 | [ 105 | 'legend' => __('General'), 106 | 'class' => 'fieldset-wide' 107 | ] 108 | ); 109 | $general->addField( 110 | 'general_item_id', 111 | 'hidden', 112 | [ 113 | 'name' => 'menu_item_id', 114 | 'value' => $itemId 115 | ] 116 | ); 117 | $general->addField( 118 | 'general_status', 119 | Toggle::class, 120 | [ 121 | 'label' => __('Enabled'), 122 | 'title' => __('Enabled'), 123 | 'required' => false, 124 | 'name' => 'status', 125 | 'value' => Menu::STATUS_DISABLED 126 | ] 127 | ); 128 | $general->addField( 129 | 'general_name', 130 | 'text', 131 | [ 132 | 'label' => __('Name'), 133 | 'title' => __('Name'), 134 | 'required' => false, 135 | 'name' => 'name' 136 | ] 137 | ); 138 | $general->addField( 139 | 'general_link', 140 | 'text', 141 | [ 142 | 'label' => __('Link'), 143 | 'title' => __('Link'), 144 | 'required' => false, 145 | 'name' => 'link' 146 | ] 147 | ); 148 | $general->addField( 149 | 'general_custom_class', 150 | 'text', 151 | [ 152 | 'label' => __('Custom Classes'), 153 | 'title' => __('Custom Classes'), 154 | 'required' => false, 155 | 'name' => 'custom_class' 156 | ] 157 | ); 158 | 159 | $header = $form->addFieldset( 160 | 'header_fieldset', 161 | [ 162 | 'legend' => __('Header'), 163 | 'class' => 'fieldset-wide' 164 | ] 165 | ); 166 | $header->addField( 167 | 'header_status', 168 | Toggle::class, 169 | [ 170 | 'label' => __('Status'), 171 | 'title' => __('Status'), 172 | 'required' => false, 173 | 'name' => 'header_status', 174 | 'value' => Menu::STATUS_DISABLED 175 | ] 176 | ); 177 | $header->addField( 178 | $this->getFormFieldId('header_wysiwyg', $itemId), 179 | 'editor', 180 | [ 181 | 'name' => 'header_content', 182 | 'label' => __('Content'), 183 | 'title' => __('Content'), 184 | 'style' => 'height:10em', 185 | 'required' => false, 186 | 'config' => $this->wysiwygConfig->getConfig() 187 | ] 188 | ); 189 | 190 | $content = $form->addFieldset( 191 | 'content_fieldset', 192 | [ 193 | 'legend' => __('Main Content'), 194 | 'class' => 'fieldset-wide' 195 | ] 196 | ); 197 | $content->addField( 198 | 'content_status', 199 | Toggle::class, 200 | [ 201 | 'label' => __('Status'), 202 | 'title' => __('Status'), 203 | 'required' => false, 204 | 'name' => 'content_status', 205 | 'value' => Menu::STATUS_DISABLED 206 | ] 207 | ); 208 | $content->addField( 209 | 'content_chooser', 210 | 'select', 211 | [ 212 | 'label' => __('Content Type'), 213 | 'title' => __('Content Type'), 214 | 'required' => true, 215 | 'options' => $this->getContentChooserOptions(), 216 | 'name' => 'content_type', 217 | ] 218 | ); 219 | $content->addField( 220 | $this->getFormFieldId('content_wysiwyg', $itemId), 221 | 'editor', 222 | [ 223 | 'name' => 'content_content', 224 | 'label' => __('Content'), 225 | 'title' => __('Content'), 226 | 'style' => 'height:10em', 227 | 'required' => false, 228 | 'config' => $this->wysiwygConfig->getConfig() 229 | ] 230 | ); 231 | $content->addField( 232 | 'content_category', 233 | 'MX\MegaMenu\Data\Form\Element\Chooser\Category', 234 | [ 235 | 'label' => __('Category'), 236 | 'title' => __('Category'), 237 | 'required' => false, 238 | 'name' => 'content_category', 239 | 'button_label' => __('Select Category...') 240 | ] 241 | ); 242 | $content->addField( 243 | 'content_category_type', 244 | 'select', 245 | [ 246 | 'label' => __('Category Type'), 247 | 'title' => __('Category Type'), 248 | 'required' => false, 249 | 'options' => $this->getCategoryTypeChooserOptions(), 250 | 'name' => 'content_category_type', 251 | ] 252 | ); 253 | $content->addField( 254 | 'remove_category_anchor', 255 | Toggle::class, 256 | [ 257 | 'label' => __('Remove Category Link'), 258 | 'title' => __('Remove Category Link'), 259 | 'required' => false, 260 | 'name' => 'remove_category_anchor', 261 | 'value' => Menu::STATUS_DISABLED 262 | ] 263 | ); 264 | 265 | $leftside = $form->addFieldset( 266 | 'left_side_fieldset', 267 | [ 268 | 'legend' => __('Left Side'), 269 | 'class' => 'fieldset-wide' 270 | ] 271 | ); 272 | $leftside->addField( 273 | 'leftside_status', 274 | Toggle::class, 275 | [ 276 | 'label' => __('Status'), 277 | 'title' => __('Status'), 278 | 'required' => false, 279 | 'name' => 'leftside_status', 280 | 'value' => Menu::STATUS_DISABLED 281 | ] 282 | ); 283 | $leftside->addField( 284 | $this->getFormFieldId('left_side_wysiwyg', $itemId), 285 | 'editor', 286 | [ 287 | 'name' => 'leftside_content', 288 | 'label' => __('Content'), 289 | 'title' => __('Content'), 290 | 'style' => 'height:10em', 291 | 'required' => false, 292 | 'config' => $this->wysiwygConfig->getConfig() 293 | ] 294 | ); 295 | 296 | $rightside = $form->addFieldset( 297 | 'right_side_fieldset', 298 | [ 299 | 'legend' => __('Right Side'), 300 | 'class' => 'fieldset-wide' 301 | ] 302 | ); 303 | $rightside->addField( 304 | 'rightside_status', 305 | Toggle::class, 306 | [ 307 | 'label' => __('Status'), 308 | 'title' => __('Status'), 309 | 'required' => false, 310 | 'name' => 'rightside_status', 311 | 'value' => Menu::STATUS_DISABLED 312 | ] 313 | ); 314 | $rightside->addField( 315 | $this->getFormFieldId('right_side_wysiwyg', $itemId), 316 | 'editor', 317 | [ 318 | 'name' => 'rightside_content', 319 | 'label' => __('Content'), 320 | 'title' => __('Content'), 321 | 'style' => 'height:10em', 322 | 'required' => false, 323 | 'config' => $this->wysiwygConfig->getConfig() 324 | ] 325 | ); 326 | 327 | $footer = $form->addFieldset( 328 | 'footer_fieldset', 329 | [ 330 | 'legend' => __('Footer'), 331 | 'class' => 'fieldset-wide' 332 | ] 333 | ); 334 | $footer->addField( 335 | 'footer_status', 336 | Toggle::class, 337 | [ 338 | 'label' => __('Status'), 339 | 'title' => __('Status'), 340 | 'required' => false, 341 | 'name' => 'footer_status', 342 | 'value' => Menu::STATUS_DISABLED 343 | ] 344 | ); 345 | $footer->addField( 346 | $this->getFormFieldId('footer_wysiwyg', $itemId), 347 | 'editor', 348 | [ 349 | 'name' => 'footer_content', 350 | 'label' => __('Content'), 351 | 'title' => __('Content'), 352 | 'style' => 'height:10em', 353 | 'required' => false, 354 | 'config' => $this->wysiwygConfig->getConfig() 355 | ] 356 | ); 357 | 358 | $form->setUseContainer(true); 359 | $this->setForm($form); 360 | 361 | return parent::_prepareForm(); 362 | } 363 | 364 | /** 365 | * Get unique form field id 366 | * 367 | * @param string $field 368 | * @param integer $itemId 369 | * @return string 370 | */ 371 | protected function getFormFieldId($field, $itemId) 372 | { 373 | return $field . '_' . $itemId; 374 | } 375 | 376 | /** 377 | * Get content chooser options 378 | * 379 | * @return array 380 | */ 381 | protected function getContentChooserOptions() 382 | { 383 | return $this->contentChooserOptions; 384 | } 385 | 386 | /** 387 | * Get categor type chooser options 388 | * 389 | * @return array 390 | */ 391 | protected function getCategoryTypeChooserOptions() 392 | { 393 | return $this->categoryTypeChooserOptions; 394 | } 395 | } -------------------------------------------------------------------------------- /src/MX/MegaMenu/Setup/InstallSchema.php: -------------------------------------------------------------------------------- 1 | startSetup(); 21 | 22 | if (!$installer->tableExists(self::TABLE_MEGAMENU)) { 23 | $table = $installer->getConnection()->newTable( 24 | $installer->getTable(self::TABLE_MEGAMENU) 25 | ) 26 | ->addColumn( 27 | 'menu_id', 28 | Table::TYPE_SMALLINT, 29 | null, 30 | [ 31 | 'identity' => true, 32 | 'nullable' => false, 33 | 'primary' => true, 34 | 'unsigned' => true, 35 | ], 36 | 'Menu ID' 37 | ) 38 | ->addColumn( 39 | 'name', 40 | Table::TYPE_TEXT, 41 | 255, 42 | [ 43 | 'nullable' => false 44 | ], 45 | 'Menu Name' 46 | ) 47 | ->addColumn( 48 | 'status', 49 | Table::TYPE_SMALLINT, 50 | 1, 51 | [ 52 | 'nullable' => false, 53 | 'unsigned' => true, 54 | 'default' => 0 55 | ], 56 | 'Menu Status' 57 | ) 58 | ->addColumn( 59 | 'created_at', 60 | Table::TYPE_TIMESTAMP, 61 | null, 62 | ['nullable' => false, 'default' => Table::TIMESTAMP_INIT], 63 | 'Created At' 64 | )->addColumn( 65 | 'updated_at', 66 | Table::TYPE_TIMESTAMP, 67 | null, 68 | ['nullable' => false, 'default' => Table::TIMESTAMP_INIT_UPDATE], 69 | 'Updated At') 70 | ->setComment('Menu Table'); 71 | 72 | $installer->getConnection()->createTable($table); 73 | } 74 | 75 | if (!$installer->tableExists(self::TABLE_MEGAMENU_ITEM)) { 76 | $table = $installer->getConnection()->newTable( 77 | $installer->getTable(self::TABLE_MEGAMENU_ITEM) 78 | ) 79 | ->addColumn( 80 | 'menu_item_id', 81 | Table::TYPE_INTEGER, 82 | null, 83 | [ 84 | 'identity' => true, 85 | 'nullable' => false, 86 | 'primary' => true, 87 | 'unsigned' => true, 88 | ], 89 | 'Menu Item ID' 90 | ) 91 | ->addColumn( 92 | 'menu_id', 93 | Table::TYPE_SMALLINT, 94 | null, 95 | [ 96 | 'nullable' => false, 97 | 'unsigned' => true, 98 | ], 99 | 'Menu ID' 100 | ) 101 | ->addColumn( 102 | 'menu_item_parent_id', 103 | Table::TYPE_INTEGER, 104 | null, 105 | [ 106 | 'nullable' => false, 107 | 'unsigned' => true, 108 | ], 109 | 'Menu Item Parent ID' 110 | ) 111 | ->addColumn( 112 | 'status', 113 | Table::TYPE_SMALLINT, 114 | 1, 115 | [ 116 | 'nullable' => false, 117 | 'unsigned' => true, 118 | 'default' => 0 119 | ], 120 | 'Menu Item Status' 121 | ) 122 | ->addColumn( 123 | 'name', 124 | Table::TYPE_TEXT, 125 | 255, 126 | [ 127 | 'nullable' => false 128 | ], 129 | 'Menu Item Name' 130 | ) 131 | ->addColumn( 132 | 'link', 133 | Table::TYPE_TEXT, 134 | 255, 135 | [ 136 | 'nullable' => false 137 | ], 138 | 'Menu Item Link' 139 | ) 140 | ->addColumn( 141 | 'header_status', 142 | Table::TYPE_SMALLINT, 143 | 1, 144 | [ 145 | 'nullable' => false, 146 | 'unsigned' => true, 147 | 'default' => 0 148 | ], 149 | 'Menu Item Header Status' 150 | ) 151 | ->addColumn( 152 | 'header_content', 153 | Table::TYPE_TEXT, 154 | '64k', 155 | [ 156 | 'nullable' => false 157 | ], 158 | 'Menu Item Header Content' 159 | ) 160 | ->addColumn( 161 | 'content_status', 162 | Table::TYPE_SMALLINT, 163 | 1, 164 | [ 165 | 'nullable' => false, 166 | 'unsigned' => true, 167 | 'default' => 0 168 | ], 169 | 'Menu Item Main Content Status' 170 | ) 171 | ->addColumn( 172 | 'content_content', 173 | Table::TYPE_TEXT, 174 | '64k', 175 | [ 176 | 'nullable' => false 177 | ], 178 | 'Menu Item Main Content Content' 179 | ) 180 | ->addColumn( 181 | 'content_type', 182 | Table::TYPE_TEXT, 183 | 255, 184 | [ 185 | 'nullable' => false 186 | ], 187 | 'Menu Item Content Type' 188 | ) 189 | ->addColumn( 190 | 'content_category', 191 | Table::TYPE_TEXT, 192 | 255, 193 | [ 194 | 'nullable' => false 195 | ], 196 | 'Menu Item Content Category' 197 | ) 198 | ->addColumn( 199 | 'leftside_status', 200 | Table::TYPE_SMALLINT, 201 | 1, 202 | [ 203 | 'nullable' => false, 204 | 'unsigned' => true, 205 | 'default' => 0 206 | ], 207 | 'Menu Item Left Side Status' 208 | ) 209 | ->addColumn( 210 | 'leftside_content', 211 | Table::TYPE_TEXT, 212 | '64k', 213 | [ 214 | 'nullable' => false 215 | ], 216 | 'Menu Item Left Side Content' 217 | ) 218 | ->addColumn( 219 | 'rightside_status', 220 | Table::TYPE_SMALLINT, 221 | 1, 222 | [ 223 | 'nullable' => false, 224 | 'unsigned' => true, 225 | 'default' => 0 226 | ], 227 | 'Menu Item Right Side Status' 228 | ) 229 | ->addColumn( 230 | 'rightside_content', 231 | Table::TYPE_TEXT, 232 | '64k', 233 | [ 234 | 'nullable' => false 235 | ], 236 | 'Menu Item Right Side Content' 237 | ) 238 | ->addColumn( 239 | 'footer_status', 240 | Table::TYPE_SMALLINT, 241 | 1, 242 | [ 243 | 'nullable' => false, 244 | 'unsigned' => true, 245 | 'default' => 0 246 | ], 247 | 'Menu Item Footer Status' 248 | ) 249 | ->addColumn( 250 | 'footer_content', 251 | Table::TYPE_TEXT, 252 | '64k', 253 | [ 254 | 'nullable' => false 255 | ], 256 | 'Menu Item Footer Content' 257 | ) 258 | ->addColumn( 259 | 'sort_order', 260 | Table::TYPE_SMALLINT, 261 | null, 262 | [ 263 | 'nullable' => false, 264 | 'unsigned' => true, 265 | ], 266 | 'Menu Item Sort Order' 267 | ) 268 | ->addColumn( 269 | 'created_at', 270 | Table::TYPE_TIMESTAMP, 271 | null, 272 | ['nullable' => false, 'default' => Table::TIMESTAMP_INIT], 273 | 'Created At' 274 | ) 275 | ->addColumn( 276 | 'updated_at', 277 | Table::TYPE_TIMESTAMP, 278 | null, 279 | ['nullable' => false, 'default' => Table::TIMESTAMP_INIT_UPDATE], 280 | 'Updated At') 281 | ->addIndex( 282 | $installer->getIdxName(self::TABLE_MEGAMENU_ITEM, ['menu_item_parent_id']), 283 | ['menu_item_parent_id'] 284 | ) 285 | ->addForeignKey( 286 | $installer->getFkName( 287 | self::TABLE_MEGAMENU_ITEM, 288 | 'menu_id', 289 | self::TABLE_MEGAMENU, 290 | 'menu_id' 291 | ), 292 | 'menu_id', 293 | $installer->getTable(self::TABLE_MEGAMENU), 294 | 'menu_id', 295 | Table::ACTION_CASCADE 296 | ) 297 | ->setComment('Menu Item Table'); 298 | 299 | $installer->getConnection()->createTable($table); 300 | } 301 | 302 | if (!$installer->tableExists(self::TABLE_MEGAMENU_STORE)) { 303 | $table = $installer->getConnection()->newTable( 304 | $installer->getTable(self::TABLE_MEGAMENU_STORE) 305 | ) 306 | ->addColumn( 307 | 'menu_id', 308 | Table::TYPE_SMALLINT, 309 | null, 310 | [ 311 | 'nullable' => false, 312 | 'unsigned' => true, 313 | ], 314 | 'Menu ID' 315 | ) 316 | ->addColumn( 317 | 'store_id', 318 | Table::TYPE_SMALLINT, 319 | null, 320 | [ 321 | 'nullable' => false, 322 | 'unsigned' => true, 323 | ], 324 | 'Store ID' 325 | ) 326 | ->addIndex( 327 | $installer->getIdxName(self::TABLE_MEGAMENU_STORE, ['menu_id']), 328 | ['menu_id'] 329 | ) 330 | ->addIndex( 331 | $installer->getIdxName(self::TABLE_MEGAMENU_STORE, ['store_id']), 332 | ['store_id'] 333 | ) 334 | ->addForeignKey( 335 | $installer->getFkName( 336 | self::TABLE_MEGAMENU_STORE, 337 | 'menu_id', 338 | self::TABLE_MEGAMENU, 339 | 'menu_id' 340 | ), 341 | 'menu_id', 342 | $installer->getTable(self::TABLE_MEGAMENU), 343 | 'menu_id', 344 | Table::ACTION_CASCADE 345 | ) 346 | ->addForeignKey( 347 | $installer->getFkName( 348 | self::TABLE_MEGAMENU_STORE, 349 | 'store_id', 350 | self::TABLE_STORE, 351 | 'store_id' 352 | ), 353 | 'store_id', 354 | $installer->getTable(self::TABLE_STORE), 355 | 'store_id', 356 | Table::ACTION_CASCADE 357 | ) 358 | ->setComment('Menu Stores Table'); 359 | 360 | $installer->getConnection()->createTable($table); 361 | } 362 | 363 | $installer->endSetup(); 364 | } 365 | } --------------------------------------------------------------------------------