Alright. I will answer this question to help any other future person who may be facing this same issue.
To begin, we will check for the layout that is responsible for producing the admin order create view. This file is found in magento_root/vendor/magento/module-sales/view/adminhtml/layout/sales_order_create_index.xml
In this file, you will find that the template responsible for generating the address form an order (Shipping and Billing address), which is <block class="Magento\Sales\Block\Adminhtml\Order\Create\Shipping\Address" template="Magento_Sales::order/create/form/address.phtml" name="shipping_address">
for Shipping and something similar for billing address. But ultimately the same template file.
Therefore the template responsible for showing the user address form in the order create form is magento_root/vendor/magento/module-sales/view/adminhtml/templates/order/create/form/address.phtml
.
In this file the code <?= $block->getForm()->toHtml() ?>
is reponsible for outputing the html form. So in order to achieve what we want, we will need to be able to modify the result of getForm
before converting it to html.
The block that is responsible for this template is Magento\Sales\Block\Adminhtml\Order\Create\Form\Address.php
which is located in magento_root/vendor/magento/module-sales/Block/Adminhtml/Order/Create/Form/Address.php
. This class extends the AbstractForm
which is still in the same folder.
The AbstractForm
class has the method getForm()
which has the code below
public function getForm()
{
if ($this->_form === null) {
$this->_form = $this->_formFactory->create();
$this->_prepareForm();
}
return $this->_form;
}
It checks for the form and and creates an instance of a form and prepares the form.
The _prepareForm()
is a method implemented by the class Address
which is the block we saw earlier.
So in the preparation of the form, the most important lines of code are these ones
$fieldset = $this->_form->addFieldset('main', ['no_container' => true]);
$addressForm = $this->_customerFormFactory->create('customer_address', 'adminhtml_customer_address');
$attributes = $addressForm->getAttributes();
$this->_addAttributesToForm($attributes, $fieldset);
The most important line is when we are getting the attributes. The attributes are classes resposible for the declaring the input type for the an input, setting its class, label, etc.
So in order to achieve a dropdown list for city, a plugin has to be created which will modify the output $addressForm->getAttributes()
so that the attributes are exactly what we want before adding them to the form.
I will not be explaining how to create a module here, I assume if you want to do this, then you already know the basics of creating a magento module.
In your module etc/di.xml
file add the line
<type name="Magento\Customer\Model\Metadata\Form">
<plugin name="modify_create_order_fields"
type="Vendor\Module\Plugin\ModifyCreateAddressFields"/>
</type>
And in your Vendor\Module\Plugin\ModifyCreateAddressFields
you will then modify the attributes as you want.
NB: Vendor\Module
refers to your module
Attributes are instances of Magento\Customer\Model\Data\AttributeMetadata
. So you can refer to the class to see the methods that you have access to.
Here is a sample code for me to modify the city attribute to display a drowpdown list instead if an input.
<?php
namespace Vendor\Module\Plugin;
use Vendor\Module\Model\TownFactory;
class ModifyCreateAddressFields
{
protected $townFactory;
public function __construct(
TownFactory $townFactory
)
{
$this->townFactory = $townFactory;
}
public function afterGetAttributes($subject, $result)
{
if(isset($result['city']))
{
$city = $result['city'];
$this->setupCity($city);
}
return $result;
}
private function setupCity($element)
{
$element->setFrontendInput('select');
$options = $this->getTownsCollection();
$element->setOptions($options);
}
private function getTownsCollection()
{
$townModel = $this->townFactory->create();
$townsCollection = $townModel->getCollection()->setOrder('name', 'ASC');
$options = [];
foreach($townsCollection as $item)
{
$name = $item->getData("name");
$option = new \Magento\Customer\Model\Data\Option;
$option->setLabel($name);
$option->setValue($name);
array_push($options, $option);
}
return $options;
}
}
I hope this helps. And if someone finds a better way I would like to know.