├── Plugin ├── CatalogSearch │ ├── Advanced.php │ └── Result.php └── Category │ └── View.php ├── README.md ├── composer.json ├── etc ├── di.xml └── module.xml ├── registration.php └── view └── frontend ├── requirejs-config.js └── web └── js └── product └── list └── toolbar.js /Plugin/CatalogSearch/Advanced.php: -------------------------------------------------------------------------------- 1 | _catalogSearchAdvanced = $catalogSearchAdvanced; 50 | $this->_urlFactory = $urlFactory; 51 | $this->_resultJsonFactory = $resultJsonFactory; 52 | $this->_resultPageFactory = $resultPageFactory; 53 | } 54 | 55 | public function aroundExecute(\Magento\CatalogSearch\Controller\Advanced\Result $subject, \Closure $method) { 56 | if ($subject->getRequest()->getParam('ajax') == 1) { 57 | try { 58 | $this->_catalogSearchAdvanced->addFilters($subject->getRequest()->getQueryValue()); 59 | $resultsBlockHtml = $this->_resultPageFactory->create()->getLayout()->getBlock('search_result_list')->toHtml(); 60 | 61 | return $this->_resultJsonFactory->create()->setData([ 62 | 'success' => true, 63 | 'html' => [ 64 | 'products_list' => $resultsBlockHtml 65 | ] 66 | ]); 67 | } catch (\Magento\Framework\Exception\LocalizedException $e) { 68 | $this->messageManager->addError($e->getMessage()); 69 | $defaultUrl = $this->_urlFactory->create() 70 | ->addQueryParams($this->getRequest()->getQueryValue()) 71 | ->getUrl('*/*/'); 72 | $this->getResponse()->setRedirect($this->_redirect->error($defaultUrl)); 73 | } 74 | } else { 75 | return $method(); 76 | } 77 | 78 | } 79 | 80 | } -------------------------------------------------------------------------------- /Plugin/CatalogSearch/Result.php: -------------------------------------------------------------------------------- 1 | _storeManager = $storeManager; 77 | $this->_queryFactory = $queryFactory; 78 | $this->layerResolver = $layerResolver; 79 | $this->_resultJsonFactory = $resultJsonFactory; 80 | $this->_resultPageFactory = $resultPageFactory; 81 | $this->_objectManager = $objectManager; 82 | } 83 | 84 | public function aroundExecute( 85 | \Magento\CatalogSearch\Controller\Result\Index $subject, 86 | \Closure $method 87 | ){ 88 | if($subject->getRequest()->getParam('ajax') == 1){ 89 | $this->layerResolver->create(Resolver::CATALOG_LAYER_SEARCH); 90 | /* @var $query \Magento\Search\Model\Query */ 91 | $query = $this->_queryFactory->get(); 92 | 93 | $query->setStoreId($this->_storeManager->getStore()->getId()); 94 | 95 | $resultJson = $this->_resultJsonFactory->create(); 96 | 97 | if ($query->getQueryText() != '') { 98 | if ($this->_objectManager->get('Magento\CatalogSearch\Helper\Data')->isMinQueryLength()) { 99 | $query->setId(0)->setIsActive(1)->setIsProcessed(1); 100 | } else { 101 | $query->saveIncrementalPopularity(); 102 | 103 | if ($query->getRedirect()) { 104 | $data = [ 105 | 'success' => true, 106 | 'redirect_url' => $query->getRedirect() 107 | ]; 108 | return $resultJson->setData($data); 109 | } 110 | } 111 | 112 | $this->_objectManager->get('Magento\CatalogSearch\Helper\Data')->checkNotes(); 113 | $resultsBlockHtml = $this->_resultPageFactory->create()->getLayout()->getBlock('search.result') 114 | ->toHtml(); 115 | 116 | return $this->_resultJsonFactory->create()->setData(['success' => true, 'html' => [ 117 | 'products_list' => $resultsBlockHtml 118 | ]]); 119 | return $data; 120 | } else { 121 | $data = [ 122 | 'success' => true, 123 | 'redirect_url' => $this->_redirect->getRedirectUrl() 124 | ]; 125 | return $resultJson->setData($data); 126 | } 127 | }else{ 128 | return $method(); 129 | } 130 | 131 | } 132 | 133 | } -------------------------------------------------------------------------------- /Plugin/Category/View.php: -------------------------------------------------------------------------------- 1 | _resultJsonFactory = $resultJsonFactory; 20 | } 21 | 22 | public function aroundExecute(\Magento\Catalog\Controller\Category\View $subject, \Closure 23 | $method){ 24 | $response = $method(); 25 | if($response instanceof Page){ 26 | if($subject->getRequest()->getParam('ajax') == 1){ 27 | 28 | $subject->getRequest()->getQuery()->set('ajax', null); 29 | $requestUri = $subject->getRequest()->getRequestUri(); 30 | $requestUri = preg_replace('/(\?|&)ajax=1/', '', $requestUri); 31 | $subject->getRequest()->setRequestUri($requestUri); 32 | 33 | //$ajaxParam = $subject->getRequest()->getQuery('ajax'); 34 | //$requestUri = $subject->getRequest()->getRequestUri(); 35 | 36 | $productsBlockHtml = $response->getLayout()->getBlock('category.products') 37 | ->toHtml(); 38 | $leftNavBlockHtml = $response->getLayout()->getBlock('catalog.leftnav') 39 | ->toHtml(); 40 | return $this->_resultJsonFactory->create()->setData(['success' => true, 'html' => [ 41 | 'products_list' => $productsBlockHtml, 42 | 'filters' => $leftNavBlockHtml 43 | ]]); 44 | } 45 | } 46 | return $response; 47 | } 48 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Ajax Layered Navigation module for Magento2 2 | This module applies AJAX on the products catalog page. The left layered navigation block links are also modified to load via AJAX. 3 | 4 | The module has been tested with Magento 2.0.x and 2.1.x, however, it should work fine for other Magento 2.x versions as well. 5 | Please note that while this module works oob for Magento_Blank and Magento_Luma themes, it may break if a custom theme modifies certain view files related to layered navigation or products list block. This seems an unavoidable issue at the moment. 6 | If you have any issues using this module, you may contact us at support@czonetechnologies.com 7 | 8 | ###Why AJAX? 9 | Catalog is the most visited section of any e-commerce site. A fast loading catalog section is intrinstic to a superior user experience. 10 | For faster loading of the catalog section, it is necessary that we do not rebuild the entire page on each request, instead only the minimum required sections are rebuilt. This results in a faster UX. 11 | 12 | #### Demo 13 | You can see this extension in action here- 14 | 15 | 1. Products Catalog page- 16 | http://work.czonetechnologies.com/mage2.0/women/tops-women/jackets-women.html 17 | http://work.czonetechnologies.com/mage2.1/women/tops-women/jackets-women.html 18 | 19 | 2. Catalog Search page- 20 | http://work.czonetechnologies.com/mage2.0/catalogsearch/result/?q=jacket 21 | http://work.czonetechnologies.com/mage2.1/catalogsearch/result/?q=jacket 22 | 23 | ####1 - Installation 24 | ##### Manual Installation 25 | 26 | * Download the extension 27 | * Unzip the file 28 | * Create a folder {Magento root}/app/code/CzoneTech 29 | * Extract the contents of the zipped folder inside it. 30 | 31 | 32 | #####Using Composer 33 | 34 | ``` 35 | composer require czonetech/ajaxified-catalog 36 | ``` 37 | 38 | ####2 - Enabling the module 39 | Using command line access to your server, run the following commands - 40 | ``` 41 | $ cd 42 | $ php bin/magento module:enable --clear-static-content CzoneTech_AjaxifiedCatalog 43 | $ php bin/magento setup:upgrade 44 | $ rm -r var/di 45 | $ php bin/magento setup:di:compile 46 | $ php bin/magento cache:clean 47 | ``` 48 | 49 | 50 | ## Screenshot 51 | ![CzoneTech_AjaxifiedCatalog](https://cloud.githubusercontent.com/assets/1729518/18914661/a9f63caa-85ab-11e6-9598-85a2eaa387df.png) 52 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "czonetech/ajaxified-catalog", 3 | "authors":[ 4 | { 5 | "name": "Czone Technologies", 6 | "email": "support@czonetechnologies.com", 7 | "homepage": "http://www.czonetechnologies.com/" 8 | } 9 | ], 10 | "license": "GPL-2.0+", 11 | "description": "Implements AJAX loading in layered navigation and product list sections.", 12 | "keywords": [ 13 | "magento 2", 14 | "ajax layered navigation", 15 | "layered navigation", 16 | "magento2 layered navigation", 17 | "magento2 ajax catalog", 18 | "magento2 ajax" 19 | ], 20 | "type": "magento2-module", 21 | "version": "1.0.11", 22 | "require": { 23 | "php": "~5.5.9.0|~5.6.0|7.0.2|~7.0.6", 24 | "magento/framework": "100.0.*|100.1.*" 25 | }, 26 | "autoload": { 27 | "files": [ 28 | "registration.php" 29 | ], 30 | "psr-4": { 31 | "CzoneTech\\AjaxifiedCatalog\\": "" 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /etc/di.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 10 | 11 | 12 | 14 | 15 | -------------------------------------------------------------------------------- /etc/module.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /registration.php: -------------------------------------------------------------------------------- 1 | 0) { 78 | url += '?' + paramData; 79 | } 80 | 81 | if (typeof history.replaceState === 'function') { 82 | history.replaceState(null, null, url); 83 | } 84 | }, 85 | 86 | getParams: function (urlParams, paramName, paramValue, defaultValue) { 87 | var paramData = {}, 88 | parameters; 89 | 90 | for (var i = 0; i < urlParams.length; i++) { 91 | parameters = urlParams[i].split('='); 92 | if (parameters[1] !== undefined) { 93 | paramData[parameters[0]] = parameters[1]; 94 | } else { 95 | paramData[parameters[0]] = ''; 96 | } 97 | } 98 | 99 | paramData[paramName] = paramValue; 100 | if (paramValue == defaultValue) { 101 | delete paramData[paramName]; 102 | } 103 | return window.decodeURIComponent($.param(paramData).replace(/\+/g, '%20')); 104 | }, 105 | _updateContent: function (content) { 106 | $(this.options.productsToolbarControl).remove(); 107 | if(content.products_list){ 108 | $(this.options.productsListBlock) 109 | .replaceWith(content.products_list) 110 | ; 111 | } 112 | 113 | if(content.filters){ 114 | $(this.options.layeredNavigationFilterBlock).replaceWith(content.filters) 115 | } 116 | 117 | $('body').trigger('contentUpdated'); 118 | }, 119 | 120 | updateContent: function (content) { 121 | $('html, body').animate( 122 | { 123 | scrollTop: $(this.options.productsToolbarControl+":first").offset().top 124 | }, 125 | 100, 126 | 'swing', 127 | this._updateContent(content) 128 | ); 129 | }, 130 | 131 | 132 | changeUrl: function (paramName, paramValue, defaultValue) { 133 | var urlPaths = this.options.url.split('?'), 134 | baseUrl = urlPaths[0], 135 | urlParams = urlPaths[1] ? urlPaths[1].split('&') : [], 136 | paramData = this.getParams(urlParams, paramName, paramValue, defaultValue); 137 | 138 | this.makeAjaxCall(baseUrl, paramData); 139 | }, 140 | 141 | makeAjaxCall: function (baseUrl, paramData) { 142 | var self = this; 143 | $.ajax({ 144 | url: baseUrl, 145 | data: (paramData && paramData.length > 0 ? paramData + '&ajax=1' : 'ajax=1'), 146 | type: 'get', 147 | dataType: 'json', 148 | cache: true, 149 | showLoader: true, 150 | timeout: 10000 151 | }).done(function (response) { 152 | if (response.success) { 153 | self.updateUrl(baseUrl, paramData); 154 | self.updateContent(response.html); 155 | self.setMessage({ 156 | type: 'success', 157 | text: 'Sections have been updated' 158 | }); 159 | 160 | } else { 161 | var msg = response.error_message; 162 | if (msg) { 163 | self.setMessage({ 164 | type: 'error', 165 | text: msg 166 | }); 167 | } 168 | } 169 | }).fail(function (error) { 170 | self.setMessage({ 171 | type: 'error', 172 | text: 'Sorry, something went wrong. Please try again later.' 173 | }); 174 | 175 | }); 176 | }, 177 | setMessage: function (obj) { 178 | var messages = ko.observableArray([obj]); 179 | messageComponent().messages({ 180 | messages: messages 181 | }); 182 | } 183 | }); 184 | 185 | return $.mage.productListToolbarForm; 186 | }); 187 | --------------------------------------------------------------------------------