lechowski / 13.11.2021

Add dropdown menu to Cities in PrestaShop

In this tutorial we will see step by step how to add the city field as a drop-down menu when creating or modifying an address.

The first step is to create a table with the cities. We will call the table city, and it will have this structure

ta1.jpg.647f9d6d4ba1e60e4ef4c44457d02dac.jpg

The id_states must be given by each state that has your prestashop store. The table would look like this:

ta2.jpg.2156faee70522bad1a8fba9291eb3c10.jpg

The next step is to modify the ps_address table by adding an id_city field.

ta3.thumb.jpg.c7b808f1601c55e6b813479c33731879.jpg

Finally, we will modify the address_format table defining for which countries we will use the droptown format in the city.

 

Just edit the format field, which generally when it contains states looks like this:

ta4.jpg.eac9a8a73023df6822cf98edf19ff310.jpg

And we will change the field city to city: name. Name would add the dropdown:

ta5.jpg.095f9408af2899655b329bc4ba8e1612.jpg

At this point the store will give us errors since classes are missing, so now we must modify the files so that all this works.

First we will create a City.php file in the classes folder that will allow us to work with cities. The file will contain:


<?php

class CityCore extends ObjectModel

{

 

   public $id_city;

   public $id_state;

   public $name;

 

  

                protected           $fieldsRequired = array('id_city', 'name');

                protected           $fieldsSize = array('name' => 128, 'id_state' => 10);

                protected           $fieldsValidate = array('name' => 'isGenericName', 'id_state' => 'isUnsignedId', );

 

                protected           $table = 'city';

                protected           $identifier = 'id_city';

  

                private static $_cache_get_cities = array();

  

  

                public function getFields()

                {

                               parent::validateFields();

                               $fields['id_city'] = (int)($this->id_city);

                               $fields['name'] = pSQL($this->name);

                               $fields['id_state'] = pSQL($this->id_state);     

                               return $fields;

                }

  

 

                public function delete()

                {

                               $id = $this->id;

                               parent::delete();

                }  

  

  

                public static function getCities($id_state)

                {

 

                                               $id_city = Db::getInstance()->ExecuteS('

                                               SELECT * FROM `'._DB_PREFIX_.'city`

                                               WHERE `id_state` = '.(int)$id_state.'

         ORDER BY `name`;'

                                               );

                              

 

                               return $id_city;

                }

  

  

   public static function getCityName($id_city)

   {

                               return Db::getInstance()->getValue('

                               SELECT `name` FROM `'._DB_PREFIX_.'city`

                               WHERE `id_city` = '.(int)$id_city

                               ); 

   }

 

}

Then we replace this function in  classes/form/customeraddressform.php :

    public function submit()

    {

        if (!$this->validate()) {

            return false;

        }

 

        $address = new Address(

            $this->getValue('id_address'),

            $this->language->id

        );

 

        

        

        foreach ($this->formFields as $formField) {

        if ($formField->getName() == 'id_city'){

                        $id_city = Db::getInstance()->getRow('

                                   SELECT * FROM `'._DB_PREFIX_.'city`

                                   WHERE `id_city` = '.$formField->getValue().';');

            $address->city = $id_city['name'];

            $address->{$formField->getName()} = $formField->getValue();

        } else {

            $address->{$formField->getName()} = $formField->getValue();

 

        }

      

        }

 

        if (!isset($this->formFields['id_state'])) {

            $address->id_state = 0;

        }

 

        if (empty($address->alias)) {

            $address->alias = $this->translator->trans('My Address', [], 'Shop.Theme.Checkout');

        }

 

 

                                 

        Hook::exec('actionSubmitCustomerAddressForm', array('address' => &$address));

        $this->setAddress($address);

        $this->getPersister()->save(

            $address,

            $this->getValue('token'));

        Db::getInstance()->Execute('UPDATE `'._DB_PREFIX_.'address` SET `city` = ''.$address->city.'' WHERE id_address = '.$address->id);

        return true;

    }

The following file is the classes / form / CustomerAddressFormatter.php

We will look for this line of states

elseif ($entity === 'State') {

                    if ($this->country->contains_states) {

                        $states = State::getStatesByIdCountry($this->country->id, true);

                        foreach ($states as $state) {

                            $formField->addAvailableValue(

                                $state['id_state'],

                                $state[$entityField]

                            );

                        }

                        $formField->setRequired(true);

                    }

                }

 

and we add this to the end of these lines

 

elseif ($entity === 'city') {

                    $formField->setType('select');

                    $formField->setName('id_' . strtolower($entity));

                        $cities =  State::getCities(315);

                        foreach ($cities as $city) {

                            $formField->addAvailableValue(

                                $city['id_city'],

                                $city[$entityField]

                            );

                        }

                        $formField->setRequired(false);

                   

                }

 

And we add this function to the clases/State.php file

 

                public static function getCities($id_state)

                {

 

                                               $id_city = Db::getInstance()->ExecuteS('

                                               SELECT * FROM `'._DB_PREFIX_.'city`

                                               WHERE `id_state` = '.(int)$id_state.'

         ORDER BY `name`;'

                                               );

                              

 

                               return $id_city;

                }

For these already created files we can always use an override to avoid losing changes when updating PrestaShop

The next file is the classes / Address.php

We will add public variables at the beginning:

 

    public  $id_city;

    public  $cityName;

 

in the function  public static $definition = array(

 

We will add at the end the created field id_city for the address table

'id_city' => 'isUnsignedId',

 

And we add this new function

public function getFields()

    {

        if (isset($this->id))

        $sql = 'select * from '. _DB_PREFIX_ . 'city

        WHERE  id_city= ' . $this->id_city;

        $sql2 = Db::getInstance()->getRow($sql);

        $fields['id_address'] = (int)($this->id);

        $fields['id_customer']     = is_null($this->id_customer) ? 0 : (int)($this->id_customer);

        $fields['id_manufacturer'] = is_null($this->id_manufacturer) ? 0 : (int)($this->id_manufacturer);

        $fields['id_supplier']     = is_null($this->id_supplier) ? 0 : (int)($this->id_supplier);

        $fields['id_country']      = (int)($this->id_country);

        $fields['id_state']        = (int)($this->id_state);

        $fields['alias']           = pSQL($this->alias);

        $fields['company']         = pSQL($this->company);

        $fields['lastname']        = pSQL($this->lastname);

        $fields['firstname']       = pSQL($this->firstname);

        $fields['address1']        = pSQL($this->address1);

        $fields['address2']        = pSQL($this->address2);

        $fields['postcode']        = pSQL($this->postcode);

        $fields['city']            = pSQL($sql2['name']);

        $fields['other']           = pSQL($this->other);

        $fields['phone']           = pSQL($this->phone);

        $fields['phone_mobile']    = pSQL($this->phone_mobile);

        $fields['vat_number']      = pSQL($this->vat_number);

        $fields['dni']             = pSQL($this->dni);

        $fields['deleted']         = (int)($this->deleted);

        $fields['date_add']        = pSQL($this->date_add);

        $fields['date_upd']        = pSQL($this->date_upd);

       

        $fields['id_city']   = is_null($this->id_city) ? 0 : (int)($this->id_city);

        return $fields;

    }

 

And change this function at the end

public static function initialize($id_address = null, $with_geoloc = false) 

 

where this elseif we add the city field

 

            } elseif ($with_geoloc && isset($context->customer->geoloc_id_country)) {

                $address = new Address();

                $address->id_country = (int) $context->customer->geoloc_id_country;

                $address->id_state = (int) $context->customer->id_state;

                $address->id_city = (int) $context->customer->id_city;

                $address->postcode = $context->customer->postcode;

            }

 

 

The next step is to add the javascript code so that it takes the cities when changing state:

For this we will modify the file themes / ourtheme / templates / _partials / javascript.tpl of our theme and we will add this code at the end:

<!-- direcciones -->

 

 

{literal}

<script>

(function(){"use strict";var c=[],f={},a,e,d,b;if(!window.jQuery){a=function(g){c.push(g)};f.ready=function(g){a(g)};e=window.jQuery=window.$=function(g){if(typeof g=="function"){a(g)}return f};window.checkJQ=function(){if(!d()){b=setTimeout(checkJQ,100)}};b=setTimeout(checkJQ,100);d=function(){if(window.jQuery!==e){clearTimeout(b);var g=c.shift();while(g){jQuery(g);g=c.shift()}b=f=a=e=d=window.checkJQ=null;return true}return false}}})();

</script>

{/literal}

 

 

 

{if $page.page_name == "address" or $page.page_name == "order" or  $page.page_name == "checkout"}

                <script type="text/javascript">

$(document).ready(function(){

$(".form-control-select .js-city").last().val();

 

 

 

 

                                                               var mi_ajaxurl                  = '{$urls.base_url}modules/';

                                                               var aux_id_state             = {if isset($smarty.post.id_state) and $smarty.post.id_state <> null}{$smarty.post.id_state}{else}{if isset($customer.addresses[$smarty.get.id_address].id_state)}{$customer.addresses[$smarty.get.id_address].id_state}{else}0{/if}{/if};

                                                               var aux_id_city = {if isset($smarty.post.id_city) and $smarty.post.id_city <> null}{$smarty.post.id_city}{else}{if isset($customer.addresses[$smarty.get.id_address].id_city)}{$customer.addresses[$smarty.get.id_address].id_city}{else}0{/if}{/if};

                                                               var aux_city       = '{if isset($smarty.post.city) and $smarty.post.city <> null}{$smarty.post.city}{else}{if isset($customer.addresses[$smarty.get.id_address].city)}{$customer.addresses[$smarty.get.id_address].city}{else}0{/if}{/if}';

 

 

                                                               {literal}

                                                                                              $(document).ready(function(){

 

              $.urlParam = function(name){

    var results = new RegExp('[?&]' + name + '=([^&#]*)').exec(window.location.href);

    if (results==null) {

       return null;

    }

    return decodeURI(results[1]) || 0;

}

 

                                                                                                              ajaxCity();

 

                                                                                                              $("[name='id_state']").change(function() {

                                                                                                              ajaxCity();

                                                                                                              });

                                                                                                             

                                                                                                              $("[name='id_city']").change(function() {

 

                                                                                                              });

                                                                                                             

 

                                                                                                              function ajaxCity(valueaaa){

                                                                                                                             $.ajax({

                                                                                                                                             type: "GET",

                                                                                                                                             url: mi_ajaxurl+"ajax_addresses/ajax.php?ajaxCity=1&id_state="+$("[name='id_state']").val()+"&aux_id_state="+aux_id_state+"&aux_city="+aux_city,

                                                                                                                                             success: function(r){

                                                                                                                                                             if( r == 'false' ){

                                                                                                                                                                             $("[name='id_city']").fadeOut();

                                                                                                                                                                            $("[name='id_city'] option[value=0]").attr("selected", "selected");

                                                                                                                                                             }else{

                                                                                                                                                                            $("[name='id_city']").html(r);

                                                                                                                                                                            $("[name='id_city']").fadeIn();

                                                                                                                                                                            //$('#id_city option[value=0]').attr("selected", "selected");                                                                                                                                            

                                                                                                                                                                            $("[name='id_city'] option[value='+aux_id_city+']").attr("selected", "selected");

                                                                                                                                                             }

                                                                                                                                                             $("[name='id_city']").trigger('click');

                                                                                                                                                             //$("#id_street").trigger('click');

                                                                                                                                                             /*/***ajaxStreet();*/

                                                                                                                                             }

                                                                                                                             });

                                                                                                              };

                                                                                                                                                                                                                                          

 

                                                                                                             

                                                                                              });

 

                                                               {/literal}

                                                               </script>

{/if}

 

Finally, we will create a pseudo module for Ajax functions. This can be done in other ways, this is just a simple one: In the modules folder, we create a folder called ajax_addresses and within it a file called ajax.php with this content:
 

<?php
include(dirname(__FILE__). '/../../config/config.inc.php');
include(dirname(__FILE__). '/../../init.php');


// obtengo city
if (isset($_GET['ajaxCity']) AND isset($_GET['id_state']))
{

$idTemp = ( (int)(Tools::getValue('id_state')) == 0 ? (int)(Tools::getValue('aux_id_state')) : (int)(Tools::getValue('id_state')) );
$idcitym = Tools::getValue('aux_city');
$states = Db::getInstance()->ExecuteS('
SELECT C.id_city, C.name
FROM '._DB_PREFIX_.'city C
WHERE C.id_state = '.$idTemp.'
ORDER BY C.`name` ASC');
$states2 = Db::getInstance()->getRow('
SELECT C.id_city, C.name
FROM '._DB_PREFIX_.'city C
WHERE C.name = ''.$idcity.''
ORDER BY C.`name` ASC');

if (is_array($states) AND !empty($states))
{
$list = '';
if (Tools::getValue('aux_id_state') != true){
if($idcitym != null){
$list .= '<option value="'.$states2['name'].'" class="showme" >'.($idcitym == 0 ? "---" : $idcitym) .'</option>'."n";
}

}

foreach ($states AS $state)
if($idcitym != null or $idcitym != 0) {
$list .= '<option value="'.(int)($state['id_city']).'"'.((isset($idcitym) AND $idcitym == $state['name']) ? ' selected="selected"' : '').'>'.$state['name'].'</option>'."n";
} else{
$list .= '<option value="'.(int)($state['id_city']).'"'.((isset($_GET['id_city']) AND $_GET['id_city'] == $state['id_city']) ? ' selected="selected"' : '').'>'.$state['name'].'</option>'."n";

}
}
else
$list = 'false';

die($list);
}

 

 

ACter that, just clear cache and you are done

ta6.jpg.48e48500d93444a5a66e2b8e06730b56.jpg

 

Link to files

https://github.com/shacker2/citiesasdropdownprestashop