Following this post
Magento 2 : How to display Shipping Rates on a Product Page
i can get shipping rates for current product, tested with curl:
curl -ik -X POST -H "Content-Type: application/json" -d "{""address"":{""country_id"": ""IT""}}" https://example.com/rest/V1/shipping/estimate/51003PRI
got the following response:
[{"carrier_code":"tablerate","method_code":"bestway","carrier_title":"Shipping Method","method_title":"Express Delivery","amount":0,"base_amount":0,"available":true,"error_message":"","price_excl_tax":0,"price_incl_tax":0}]
Now i need to display a form in product page like the "Estimate Shipping and Tax" in cart page to pass the address in post action.
I would like to do it using ui form and ajax and need to update region field depending country field.
Any suggestions?
EDIT
I got the form in product page with the following:
[vendor][module]\Block\Produt\View\Shipping:
<?php
namespace [vendor]\[module]\Block\Product\View;
class Shipping extends \Magento\Catalog\Block\Product\View\AbstractView
{
protected $_customerSession;
/**
* @param \Magento\Catalog\Block\Product\Context $context
* @param \Magento\Framework\Stdlib\ArrayUtils $arrayUtils
* @param array $layoutProcessors
* @param array $data
*/
public function __construct(
\Magento\Catalog\Block\Product\Context $context,
\Magento\Framework\Stdlib\ArrayUtils $arrayUtils,
\Magento\Framework\App\Request\Http $request,
\Magento\Customer\Api\CustomerRepositoryInterface $customerRepository,
\Magento\Customer\Model\SessionFactory $customerSession,
array $layoutProcessors = [],
array $data = []
) {
$this->request = $request;
$this->customerRepository = $customerRepository;
$this->_customerSession = $customerSession;
$this->layoutProcessors = $layoutProcessors;
parent::__construct(
$context,
$arrayUtils,
$data
);
}
/**
* Retrieve serialized JS layout configuration ready to use in template
*
* @return string
*/
public function getJsLayout()
{
foreach ($this->layoutProcessors as $processor) {
$this->jsLayout = $processor->process($this->jsLayout);
}
return json_encode($this->jsLayout, JSON_HEX_TAG);
}
public function getCurrentCustomer()
{
$customer = $this->_customerSession->create();
return $customer->getDefaultTaxShippingAddress();
}
public function getSerializedCurrentCustomer()
{
return json_encode($this->getCurrentCustomer(), JSON_HEX_TAG);
}
}
[vendor][module]\view\frontend\layout\catalog_product_view.xml
<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
<referenceContainer name="product.info.main">
<block class="[vendor]\[module]\Block\Product\View\Shipping" name="estimate.product.shipping" template="[vendor]_[module]::product/view/shipping.phtml" after="checkout.cart.summary.title">
<arguments>
<argument name="jsLayout" xsi:type="array">
<item name="types" xsi:type="array">
<item name="form.input" xsi:type="array">
<item name="component" xsi:type="string">Magento_Ui/js/form/element/abstract</item>
<item name="config" xsi:type="array">
<item name="provider" xsi:type="string">checkoutProvider</item>
<item name="deps" xsi:type="array">
<item name="0" xsi:type="string">checkoutProvider</item>
</item>
<item name="template" xsi:type="string">ui/form/field</item>
<item name="elementTmpl" xsi:type="string">ui/form/element/input</item>
</item>
</item>
</item>
<item name="components" xsi:type="array">
<item name="block-summary" xsi:type="array">
<item name="component" xsi:type="string">uiComponent</item>
<item name="children" xsi:type="array">
<item name="block-shipping" xsi:type="array">
<item name="component" xsi:type="string">[vendor]_[module]/js/product/view/shipping-estimation</item>
<item name="provider" xsi:type="string">checkoutProvider</item>
<item name="sortOrder" xsi:type="string">1</item>
<item name="deps" xsi:type="array">
<item name="0" xsi:type="string">block-summary.block-shipping.address-fieldsets</item>
</item>
<item name="children" xsi:type="array">
<item name="address-fieldsets" xsi:type="array">
<item name="component" xsi:type="string">uiComponent</item>
<item name="config" xsi:type="array">
<item name="deps" xsi:type="array">
<item name="0" xsi:type="string">checkoutProvider</item>
</item>
</item>
<item name="displayArea" xsi:type="string">address-fieldsets</item>
<item name="children" xsi:type="array">
<item name="city" xsi:type="array">
<item name="sortOrder" xsi:type="string">115</item>
<item name="dataScope" xsi:type="string">shippingAddress.city</item>
<item name="provider" xsi:type="string">checkoutProvider</item>
</item>
<item name="country_id" xsi:type="array">
<item name="sortOrder" xsi:type="string">110</item>
<item name="dataScope" xsi:type="string">shippingAddress.country_id</item>
<item name="provider" xsi:type="string">checkoutProvider</item>
</item>
<item name="region_id" xsi:type="array">
<item name="component" xsi:type="string">Magento_Ui/js/form/element/region</item>
<item name="sortOrder" xsi:type="string">112</item>
<item name="config" xsi:type="array">
<item name="template" xsi:type="string">ui/form/field</item>
<item name="elementTmpl" xsi:type="string">ui/form/element/select</item>
<item name="customEntry" xsi:type="string">shippingAddress.region</item>
</item>
<!-- Value of region_id field is filtered by the value of county_id attribute -->
<item name="filterBy" xsi:type="array">
<item name="target" xsi:type="string">
<![CDATA[${ $.provider }:${ $.parentScope }.country_id]]>
</item>
<item name="field" xsi:type="string">country_id</item>
</item>
</item>
<!-- The following items override configuration of corresponding address attributes -->
<item name="region" xsi:type="array">
<!-- Make region attribute invisible on frontend. Corresponding input element is created by region_id field -->
<item name="visible" xsi:type="boolean">false</item>
<item name="component" xsi:type="string">Magento_Ui/js/form/element/abstract</item>
<item name="sortOrder" xsi:type="string">111</item>
<item name="dataScope" xsi:type="string">shippingAddress.region</item>
</item>
<item name="postcode" xsi:type="array">
<!-- post-code field has custom UI component -->
<item name="component" xsi:type="string">Magento_Ui/js/form/element/abstract</item>
<item name="dataScope" xsi:type="string">shippingAddress.postcode</item>
<item name="sortOrder" xsi:type="string">114</item>
<item name="provider" xsi:type="string">checkoutProvider</item>
</item>
</item>
</item>
</item>
</item>
</item>
</item>
<item name="checkoutProvider" xsi:type="array">
<item name="component" xsi:type="string">uiComponent</item>
</item>
</item>
</argument>
</arguments>
</block>
</referenceContainer>
</page>
[vendor][module]\view\frontend\templates\product\view\shipping.phtml
<?php
/** @var [vendor]\[module]\Block\Product\View\Shipping */
$product = $block->getProduct();
?>
<?php if ($product->getTypeId() != 'virtual' ) : ?>
<div id="block-shipping"
class="block shipping"
data-mage-init='{"collapsible":{"openedState": "active", "saveState": true}}'
>
<div class="title" data-role="title">
<strong id="block-shipping-heading" role="heading" aria-level="2">
<?= $block->escapeHtml(__('Estimate Shipping Cost')) ?>
</strong>
</div>
<div id="block-summary"
data-bind="scope:'block-summary'"
class="content"
data-role="content"
aria-labelledby="block-shipping-heading"
>
<!-- ko template: getTemplate() --><!-- /ko -->
<script type="text/x-magento-init">
{
"#block-summary": {
"Magento_Ui/js/core/app": <?= /* @noEscape */ $block->getJsLayout() ?>
}
}
</script>
<script>
window.customerAddress = <?= /* @noEscape */ $block->getSerializedCurrentCustomer() ?>;
require([
'mage/url',
'Magento_Ui/js/block-loader'
], function(url, blockLoader) {
blockLoader(
"<?= $block->escapeJs($block->escapeUrl($block->getViewFileUrl('images/loader-1.gif'))) ?>"
);
return url.setBaseUrl('<?= $block->escapeJs($block->escapeUrl($block->getBaseUrl())) ?>');
})
</script>
</div>
</div>
<?php endif; ?>
[vendor][module]\view\frontend\web\js\product\view\shipping-estimation.js
define(
[
'jquery',
'ko',
'Magento_Ui/js/form/form',
'uiRegistry',
'mage/storage',
'mage/validation'
],
function (
$,
ko,
Component,
registry,
storage
) {
'use strict';
return Component.extend({
defaults: {
template: '[vendor]_[module]/product/view/shipping-estimation'
},
/**
* @override
*/
initialize: function () {
this._super();
var self = this;
registry.async('checkoutProvider')(function (checkoutProvider) {
var address, estimatedAddress;
address = window.customerAddress;
if (address) {
estimatedAddress =
{
// only the following fields must be used by estimation form data provider
'country_id': address.country_id,
region: address.region,
'region_id': address.region_id,
postcode: address.postcode
};
checkoutProvider.set(
'shippingAddress',
$.extend({}, checkoutProvider.get('shippingAddress'), estimatedAddress)
);
self.getRates(address);
}
});
return this;
},
initElement: function (element) {
this._super();
var self = this,
checkoutProvider = registry.get('checkoutProvider');
if (element.index === 'address-fieldsets') {
setTimeout(function () {
$.each(element.elems(), function (index, elem) {
elem.on('value', function () {
clearTimeout(self.validateAddressTimeout);
self.validateAddressTimeout = setTimeout(function () {
self.getRates(checkoutProvider.shippingAddress);
}, 1000);
});
});
},1000);
}
},
getRates: function (address){
var self = this,
productId = window.productId,
url = 'rest/V1/shipping/estimate/' + productId;
self.shippingRates = ko.observableArray([]);
storage.post(
url,
JSON.stringify({
'address': address
}),
false
).done(function (result) {
self.shippingRates(result);
}).fail(function (response) {
});
}
});
}
);
[vendor][module]\view\frontend\web\template\product\view\shipping-estimation.html
<form method="post" id="shipping-zip-form">
<fieldset class="fieldset estimate">
<legend class="legend">
<span data-bind="text: $t('Estimate Shipping Cost') "></span>
</legend><br>
<p class="field note" data-bind="text: $t('Enter your destination to get a shipping estimate.')"></p>
<!-- ko foreach: getRegion('address-fieldsets') -->
<!-- ko template: getTemplate() --><!-- /ko -->
<!--/ko-->
</fieldset>
</form>
<div class="items methods" data-bind="foreach: shippingRates">
<pre data-bind="text: ko.toJSON($data, null, 2)"></pre>
</div>
Now i need to subimit the form to this action url /rest//V1/shipping/estimate/:sku
on change and get the result for a ko template.
Any help?
EDIT
Added the function getRates on js to get the shipping methods and it works, but how to reload the template (html update) when get e new response?