1.点击Editing 编辑地址执行流程

pub/static/frontend/Ayd/inf/hu_HU/Magento_Checkout/js/view/shipping-address/address-renderer/default.js
editAddress: function () {
formPopUpState.isVisible(true);
this.showPopup();
},
/**
* Show popup.
*/
showPopup: function () {
$('[data-open-modal="opc-new-shipping-address"]').trigger('click');
}
editAddress
函数:
功能:这是入口函数。它首先设置一个可观察的状态变量 formPopUpState.isVisible
为 true
,标志着弹窗应该被打开。
逻辑:随后它调用 showPopup
方法来实际触发弹窗的显示。将状态管理和实际触发分离,是一种比较清晰的代码组织方式。
showPopup
函数:
功能:此函数通过 jQuery 选择器 $('[data-open-modal="opc-new-shipping-address"]')
查找到负责打开新送货地址模态框(弹窗)的按钮或元素,并模拟一次点击事件(.trigger('click')
)。
pub/static/frontend/Ayd/inf/hu_HU/Magento_Checkout/template/shipping.html
<!--
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-->
<li id="shipping" class="checkout-shipping-address" data-bind="fadeVisible: visible()">
<div class="step-title" translate="'Shipping Address'" data-role="title"></div>
<div id="checkout-step-shipping"
class="step-content"
data-role="content">
<each if="!quoteIsVirtual" args="getRegion('customer-email')" render="" ></each>
<each args="getRegion('address-list')" render="" ></each>
<each args="getRegion('address-list-additional-addresses')" render="" ></each>
<!-- Address form pop up -->
<if args="!isFormInline">
<div class="new-address-popup">
<button type="button"
class="action action-show-popup"
click="showFormPopUp"
visible="!isNewAddressAdded()">
<span translate="'New Address'"></span>
</button>
</div>
<div id="opc-new-shipping-address"
visible="isFormPopUpVisible()"
render="shippingFormTemplate"></div>
</if>
<each args="getRegion('before-form')" render="" ></each>
<!-- Inline address form -->
<render if="isFormInline" args="shippingFormTemplate"></render>
</div>
</li>
<!--Shipping method template-->
<li id="opc-shipping_method"
class="checkout-shipping-method"
data-bind="fadeVisible: visible(), blockLoader: isLoading"
role="presentation">
<div class="checkout-shipping-method">
<div class="step-title"
translate="'Shipping Methods'"
data-role="title"></div>
<each args="getRegion('before-shipping-method-form')" render="" ></each>
<div id="checkout-step-shipping_method"
class="step-content"
data-role="content"
role="tabpanel"
aria-hidden="false">
<form id="co-shipping-method-form"
class="form methods-shipping"
if="rates().length"
submit="setShippingInformation"
novalidate="novalidate">
<render args="shippingMethodListTemplate"></render>
<div id="onepage-checkout-shipping-method-additional-load">
<each args="getRegion('shippingAdditional')" render="" ></each>
</div>
<div role="alert"
if="errorValidationMessage().length"
class="message notice">
<span text="errorValidationMessage()"></span>
</div>
<div class="actions-toolbar" id="shipping-method-buttons-container">
<div class="primary">
<button data-role="opc-continue" type="submit" class="button action continue primary">
<span translate="'Next'"></span>
</button>
</div>
</div>
</form>
<div class="no-quotes-block"
ifnot="rates().length > 0"
translate="'Sorry, no quotes are available for this order at this time'"></div>
</div>
</div>
</li>
2. 点击Shipping 保存
保存的js页面 pub/static/frontend/Ayd/inf/hu_HU/Magento_Checkout/js/view/shipping.js
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
define([
'jquery',
'underscore',
'Magento_Ui/js/form/form',
'ko',
'Magento_Customer/js/model/customer',
'Magento_Customer/js/model/address-list',
'Magento_Checkout/js/model/address-converter',
'Magento_Checkout/js/model/quote',
'Magento_Checkout/js/action/create-shipping-address',
'Magento_Checkout/js/action/select-shipping-address',
'Magento_Checkout/js/model/shipping-rates-validator',
'Magento_Checkout/js/model/shipping-address/form-popup-state',
'Magento_Checkout/js/model/shipping-service',
'Magento_Checkout/js/action/select-shipping-method',
'Magento_Checkout/js/model/shipping-rate-registry',
'Magento_Checkout/js/action/set-shipping-information',
'Magento_Checkout/js/model/step-navigator',
'Magento_Ui/js/modal/modal',
'Magento_Checkout/js/model/checkout-data-resolver',
'Magento_Checkout/js/checkout-data',
'uiRegistry',
'mage/translate',
'Magento_Checkout/js/model/shipping-rate-service'
], function (
$,
_,
Component,
ko,
customer,
addressList,
addressConverter,
quote,
createShippingAddress,
selectShippingAddress,
shippingRatesValidator,
formPopUpState,
shippingService,
selectShippingMethodAction,
rateRegistry,
setShippingInformationAction,
stepNavigator,
modal,
checkoutDataResolver,
checkoutData,
registry,
$t
) {
'use strict';
var popUp = null;
return Component.extend({
defaults: {
template: 'Magento_Checkout/shipping',
shippingFormTemplate: 'Magento_Checkout/shipping-address/form',
shippingMethodListTemplate: 'Magento_Checkout/shipping-address/shipping-method-list',
shippingMethodItemTemplate: 'Magento_Checkout/shipping-address/shipping-method-item',
imports: {
countryOptions: '${ $.parentName }.shippingAddress.shipping-address-fieldset.country_id:indexedOptions'
}
},
visible: ko.observable(!quote.isVirtual()),
errorValidationMessage: ko.observable(false),
isCustomerLoggedIn: customer.isLoggedIn,
isFormPopUpVisible: formPopUpState.isVisible,
isFormInline: addressList().length === 0,
isNewAddressAdded: ko.observable(false),
saveInAddressBook: 1,
quoteIsVirtual: quote.isVirtual(),
/**
* @return {exports}
*/
initialize: function () {
var self = this,
hasNewAddress,
fieldsetName = 'checkout.steps.shipping-step.shippingAddress.shipping-address-fieldset';
this._super();
if (!quote.isVirtual()) {
stepNavigator.registerStep(
'shipping',
'',
$t('Shipping'),
$t('Shipping'),
this.visible, _.bind(this.navigate, this),
this.sortOrder
);
}
checkoutDataResolver.resolveShippingAddress();
hasNewAddress = addressList.some(function (address) {
return address.getType() == 'new-customer-address'; //eslint-disable-line eqeqeq
});
this.isNewAddressAdded(hasNewAddress);
this.isFormPopUpVisible.subscribe(function (value) {
if (value) {
self.getPopUp().openModal();
}
});
quote.shippingMethod.subscribe(function () {
self.errorValidationMessage(false);
});
registry.async('checkoutProvider')(function (checkoutProvider) {
var shippingAddressData = checkoutData.getShippingAddressFromData();
if (shippingAddressData) {
checkoutProvider.set(
'shippingAddress',
$.extend(true, {}, checkoutProvider.get('shippingAddress'), shippingAddressData)
);
}
checkoutProvider.on('shippingAddress', function (shippingAddrsData, changes) {
var isStreetAddressDeleted, isStreetAddressNotEmpty;
/**
* In last modifying operation street address was deleted.
* @return {Boolean}
*/
isStreetAddressDeleted = function () {
var change;
if (!changes || changes.length === 0) {
return false;
}
change = changes.pop();
if (_.isUndefined(change.value) || _.isUndefined(change.oldValue)) {
return false;
}
if (!change.path.startsWith('shippingAddress.street')) {
return false;
}
return change.value.length === 0 && change.oldValue.length > 0;
};
isStreetAddressNotEmpty = shippingAddrsData.street && !_.isEmpty(shippingAddrsData.street[0]);
if (isStreetAddressNotEmpty || isStreetAddressDeleted()) {
checkoutData.setShippingAddressFromData(shippingAddrsData);
}
});
shippingRatesValidator.initFields(fieldsetName);
});
return this;
},
/**
* Navigator change hash handler.
*
* @param {Object} step - navigation step
*/
navigate: function (step) {
step && step.isVisible(true);
},
/**
* @return {*}
*/
getPopUp: function () {
var self = this,
buttons;
if (!popUp) {
buttons = this.popUpForm.options.buttons;
this.popUpForm.options.buttons = [
{
text: buttons.save.text ? buttons.save.text : $t('Save Address'),
class: buttons.save.class ? buttons.save.class : 'action primary action-save-address',
click: self.saveNewAddress.bind(self)
},
{
text: buttons.cancel.text ? buttons.cancel.text : $t('Cancel'),
class: buttons.cancel.class ? buttons.cancel.class : 'action secondary action-hide-popup',
/** @inheritdoc */
click: this.onClosePopUp.bind(this)
}
];
/** @inheritdoc */
this.popUpForm.options.closed = function () {
self.isFormPopUpVisible(false);
};
this.popUpForm.options.modalCloseBtnHandler = this.onClosePopUp.bind(this);
this.popUpForm.options.keyEventHandlers = {
escapeKey: this.onClosePopUp.bind(this)
};
/** @inheritdoc */
this.popUpForm.options.opened = function () {
// Store temporary address for revert action in case when user click cancel action
self.temporaryAddress = $.extend(true, {}, checkoutData.getShippingAddressFromData());
};
popUp = modal(this.popUpForm.options, $(this.popUpForm.element));
}
return popUp;
},
/**
* Revert address and close modal.
*/
onClosePopUp: function () {
checkoutData.setShippingAddressFromData($.extend(true, {}, this.temporaryAddress));
this.getPopUp().closeModal();
},
/**
* Show address form popup
*/
showFormPopUp: function () {
this.isFormPopUpVisible(true);
},
/**
* Save new shipping address
*/
saveNewAddress: function () {
var addressData,
newShippingAddress;
this.source.set('params.invalid', false);
this.triggerShippingDataValidateEvent();
if (!this.source.get('params.invalid')) {
addressData = this.source.get('shippingAddress');
// if user clicked the checkbox, its value is true or false. Need to convert.
addressData['save_in_address_book'] = this.saveInAddressBook ? 1 : 0;
// New address must be selected as a shipping address
newShippingAddress = createShippingAddress(addressData);
selectShippingAddress(newShippingAddress);
checkoutData.setSelectedShippingAddress(newShippingAddress.getKey());
checkoutData.setNewCustomerShippingAddress($.extend(true, {}, addressData));
this.getPopUp().closeModal();
this.isNewAddressAdded(true);
}
},
/**
* Shipping Method View
*/
rates: shippingService.getShippingRates(),
isLoading: shippingService.isLoading,
isSelected: ko.computed(function () {
return checkoutData.getSelectedShippingRate() ? checkoutData.getSelectedShippingRate() :
quote.shippingMethod() ?
quote.shippingMethod()['carrier_code'] + '_' + quote.shippingMethod()['method_code'] :
null;
}),
/**
* @param {Object} shippingMethod
* @return {Boolean}
*/
selectShippingMethod: function (shippingMethod) {
selectShippingMethodAction(shippingMethod);
checkoutData.setSelectedShippingRate(shippingMethod['carrier_code'] + '_' + shippingMethod['method_code']);
return true;
},
/**
* Set shipping information handler
*/
setShippingInformation: function () {
if (this.validateShippingInformation()) {
quote.billingAddress(null);
checkoutDataResolver.resolveBillingAddress();
registry.async('checkoutProvider')(function (checkoutProvider) {
var shippingAddressData = checkoutData.getShippingAddressFromData();
if (shippingAddressData) {
checkoutProvider.set(
'shippingAddress',
$.extend(true, {}, checkoutProvider.get('shippingAddress'), shippingAddressData)
);
}
});
setShippingInformationAction().done(
function () {
stepNavigator.next();
}
);
}
},
/**
* @return {Boolean}
*/
validateShippingInformation: function () {
var shippingAddress,
addressData,
loginFormSelector = 'form[data-role=email-with-possible-login]',
emailValidationResult = customer.isLoggedIn(),
field,
option = _.isObject(this.countryOptions) && this.countryOptions[quote.shippingAddress().countryId],
messageContainer = registry.get('checkout.errors').messageContainer;
if (!quote.shippingMethod()) {
this.errorValidationMessage(
$t('The shipping method is missing. Select the shipping method and try again.')
);
return false;
}
if (!customer.isLoggedIn()) {
$(loginFormSelector).validation();
emailValidationResult = Boolean($(loginFormSelector + ' input[name=username]').valid());
}
if (this.isFormInline) {
this.source.set('params.invalid', false);
this.triggerShippingDataValidateEvent();
if (!quote.shippingMethod()['method_code']) {
this.errorValidationMessage(
$t('The shipping method is missing. Select the shipping method and try again.')
);
}
if (emailValidationResult &&
this.source.get('params.invalid') ||
!quote.shippingMethod()['method_code'] ||
!quote.shippingMethod()['carrier_code']
) {
this.focusInvalid();
return false;
}
shippingAddress = quote.shippingAddress();
addressData = addressConverter.formAddressDataToQuoteAddress(
this.source.get('shippingAddress')
);
//Copy form data to quote shipping address object
for (field in addressData) {
if (addressData.hasOwnProperty(field) && //eslint-disable-line max-depth
shippingAddress.hasOwnProperty(field) &&
typeof addressData[field] != 'function' &&
_.isEqual(shippingAddress[field], addressData[field])
) {
shippingAddress[field] = addressData[field];
} else if (typeof addressData[field] != 'function' &&
!_.isEqual(shippingAddress[field], addressData[field])) {
shippingAddress = addressData;
break;
}
}
if (customer.isLoggedIn()) {
shippingAddress['save_in_address_book'] = 1;
}
selectShippingAddress(shippingAddress);
} else if (customer.isLoggedIn() &&
option &&
option['is_region_required'] &&
!quote.shippingAddress().region
) {
messageContainer.addErrorMessage({
message: $t('Please specify a regionId in shipping address.')
});
return false;
}
if (!emailValidationResult) {
$(loginFormSelector + ' input[name=username]').trigger('focus');
return false;
}
return true;
},
/**
* Trigger Shipping data Validate Event.
*/
triggerShippingDataValidateEvent: function () {
this.source.trigger('shippingAddress.data.validate');
if (this.source.get('shippingAddress.custom_attributes')) {
this.source.trigger('shippingAddress.custom_attributes.data.validate');
}
}
});
});
分析上面js
弹窗管理
/**
* 获取或创建地址编辑弹窗
*/
getPopUp: function () {
if (!popUp) {
// 配置弹窗按钮
buttons = this.popUpForm.options.buttons;
this.popUpForm.options.buttons = [
{
text: buttons.save.text ? buttons.save.text : $t('Save Address'),
class: buttons.save.class ? buttons.save.class : 'action primary action-save-address',
click: self.saveNewAddress.bind(self) // 保存地址
},
{
text: buttons.cancel.text ? buttons.cancel.text : $t('Cancel'),
class: buttons.cancel.class ? buttons.cancel.class : 'action secondary action-hide-popup',
click: this.onClosePopUp.bind(this) // 关闭弹窗
}
];
popUp = modal(this.popUpForm.options, $(this.popUpForm.element));
}
return popUp;
}
保存新地址
/**
* Save new shipping address
*/
saveNewAddress: function () {
var addressData,
newShippingAddress;
this.source.set('params.invalid', false);
this.triggerShippingDataValidateEvent();
if (!this.source.get('params.invalid')) {
addressData = this.source.get('shippingAddress');
// if user clicked the checkbox, its value is true or false. Need to convert.
addressData['save_in_address_book'] = this.saveInAddressBook ? 1 : 0;
// New address must be selected as a shipping address
newShippingAddress = createShippingAddress(addressData);
selectShippingAddress(newShippingAddress);
checkoutData.setSelectedShippingAddress(newShippingAddress.getKey());
checkoutData.setNewCustomerShippingAddress($.extend(true, {}, addressData));
this.getPopUp().closeModal();
this.isNewAddressAdded(true);
}
},

其中createShippingAddress 如下:
newShippingAddress = createShippingAddress(addressData);
selectShippingAddress(newShippingAddress);
checkoutData.setSelectedShippingAddress(newShippingAddress.getKey());
checkoutData.setNewCustomerShippingAddress($.extend(true, {}, addressData));
this.getPopUp().closeModal();
this.isNewAddressAdded(true);
该方法在pub/static/frontend/Ayd/inf/hu_HU/Magento_Checkout/js/action/create-shipping-address.js
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
/**
* @api
*/
define([
'Magento_Customer/js/model/address-list',
'Magento_Checkout/js/model/address-converter'
], function (addressList, addressConverter) {
'use strict';
return function (addressData) {
var address = addressConverter.formAddressDataToQuoteAddress(addressData),
isAddressUpdated = addressList().some(function (currentAddress, index, addresses) {
if (currentAddress.getKey() == address.getKey()) { //eslint-disable-line eqeqeq
addresses[index] = address;
return true;
}
return false;
});
if (!isAddressUpdated) {
addressList.push(address);
} else {
addressList.valueHasMutated();
}
return address;
};
});
这个createShippingAddress
函数负责将表单地址数据转换为报价(Quote)地址对象,并管理地址列表的更新。让我详细分析其工作原理。
addressConverter : Magento_Checkout/js/model/address-converter.js'
var address = addressConverter.formAddressDataToQuoteAddress(addressData);
作用:将UI组件的表单数据转换为Quote地址的标准格式
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
/**
* @api
*/
define([
'jquery',
'Magento_Checkout/js/model/new-customer-address',
'Magento_Customer/js/customer-data',
'mage/utils/objects',
'underscore'
], function ($, address, customerData, mageUtils, _) {
'use strict';
var countryData = customerData.get('directory-data');
return {
/**
* Convert address form data to Address object
*
* @param {Object} formData
* @returns {Object}
*/
formAddressDataToQuoteAddress: function (formData) {
// clone address form data to new object
var addressData = $.extend(true, {}, formData),
region,
regionName = addressData.region,
customAttributes;
if (mageUtils.isObject(addressData.street)) {
addressData.street = this.objectToArray(addressData.street);
}
addressData.region = {
'region_id': addressData['region_id'],
'region_code': addressData['region_code'],
region: regionName
};
if (addressData['region_id'] &&
countryData()[addressData['country_id']] &&
countryData()[addressData['country_id']].regions
) {
region = countryData()[addressData['country_id']].regions[addressData['region_id']];
if (region) {
addressData.region['region_id'] = addressData['region_id'];
addressData.region['region_code'] = region.code;
addressData.region.region = region.name;
}
} else if (
!addressData['region_id'] &&
countryData()[addressData['country_id']] &&
countryData()[addressData['country_id']].regions
) {
addressData.region['region_code'] = '';
addressData.region.region = '';
}
delete addressData['region_id'];
if (addressData['custom_attributes']) {
addressData['custom_attributes'] = _.map(
addressData['custom_attributes'],
function (value, key) {
customAttributes = {
'attribute_code': key,
'value': value
};
if (typeof value === 'boolean') {
customAttributes = {
'attribute_code': key,
'value': value,
'label': value === true ? 'Yes' : 'No'
};
}
return customAttributes;
}
);
}
return address(addressData);
},
/**
* Convert Address object to address form data.
*
* @param {Object} addrs
* @returns {Object}
*/
quoteAddressToFormAddressData: function (addrs) {
var self = this,
output = {},
streetObject,
customAttributesObject;
$.each(addrs, function (key) {
if (addrs.hasOwnProperty(key) && typeof addrs[key] !== 'function') {
output[self.toUnderscore(key)] = addrs[key];
}
});
if (Array.isArray(addrs.street)) {
streetObject = {};
addrs.street.forEach(function (value, index) {
streetObject[index] = value;
});
output.street = streetObject;
}
//jscs:disable requireCamelCaseOrUpperCaseIdentifiers
if (Array.isArray(addrs.customAttributes)) {
customAttributesObject = {};
addrs.customAttributes.forEach(function (value) {
customAttributesObject[value.attribute_code] = value.value;
});
output.custom_attributes = customAttributesObject;
}
//jscs:enable requireCamelCaseOrUpperCaseIdentifiers
return output;
},
/**
* @param {String} string
*/
toUnderscore: function (string) {
return string.replace(/([A-Z])/g, function ($1) {
return '_' + $1.toLowerCase();
});
},
/**
* @param {Object} formProviderData
* @param {String} formIndex
* @return {Object}
*/
formDataProviderToFlatData: function (formProviderData, formIndex) {
var addressData = {};
$.each(formProviderData, function (path, value) {
var pathComponents = path.split('.'),
dataObject = {};
pathComponents.splice(pathComponents.indexOf(formIndex), 1);
pathComponents.reverse();
$.each(pathComponents, function (index, pathPart) {
var parent = {};
if (index == 0) { //eslint-disable-line eqeqeq
dataObject[pathPart] = value;
} else {
parent[pathPart] = dataObject;
dataObject = parent;
}
});
$.extend(true, addressData, dataObject);
});
return addressData;
},
/**
* Convert object to array
* @param {Object} object
* @returns {Array}
*/
objectToArray: function (object) {
var convertedArray = [];
$.each(object, function (key) {
return typeof object[key] === 'string' ? convertedArray.push(object[key]) : false;
});
return convertedArray.slice(0);
},
/**
* @param {Object} addrs
* @return {*|Object}
*/
addressToEstimationAddress: function (addrs) {
var self = this,
estimatedAddressData = {};
$.each(addrs, function (key) {
estimatedAddressData[self.toUnderscore(key)] = addrs[key];
});
return this.formAddressDataToQuoteAddress(estimatedAddressData);
}
};
});
addressList : 'Magento_Customer/js/model/address-list',