Countryselect, stateselect, countyselect
- Version
- 3.x
- Updated
Three cascading pickers for the country → state → county hierarchy. Each stores the referenced record's id.
Storage kind: column · Column: int unsigned — but see the caveat below
Use them for any address-like field. They exist as three types rather than one because each knows which of the others it narrows and is narrowed by, and that wiring is what makes the cascade work.
Settings
| Type | Setting | Meaning |
|---|---|---|
countryselect | stateFieldName | The property holding the state picker it drives |
countryselect | defaultlabel | Placeholder, e.g. "Choose Country" |
stateselect | countryFieldName | The country property it narrows by |
countyselect | stateFieldName | The state property it narrows by |
The pointers are what make the cascade work: choosing a country repopulates the state picker named
by stateFieldName, and the state picker knows which country to ask about from countryFieldName.
A typical trio, from admincustomers:
$propDefs['billing_country_id'] = array(…, 'type' => 'countryselect', 'stateFieldName' => 'billing_state_id');
$propDefs['billing_state_id'] = array(…, 'type' => 'stateselect', 'countryFieldName' => 'billing_country_id');
$propDefs['billing_county_id'] = array(…, 'type' => 'countyselect', 'stateFieldName' => 'billing_state_id');
The names must match actual property names on the same model. Nothing validates them, so a typo gives you a picker that never repopulates.
Declare dataType and nullable explicitly
Only stateselect and countyselect declare int unsigned as their default column type.
countryselect does not — it inherits the base varchar(255).
That is why every shipped definition spells both keys out:
'name' => 'billing_country_id', 'nullable' => true, 'dataType' => 'int unsigned',
Do the same. Relying on the type's default gives you a varchar(255) column holding integers for
the country and int unsigned for the state — an inconsistency that only shows up when something
joins or compares them.
They store ids, but they are not number fields
The value is an integer id, and it would be technically accurate to declare these as number with
numberType => 'integer'. Do not: the picker, the cascade and the label lookup are the point, and a
number field is a text input.
This came up when auditing types against their columns — six geography properties sit on
int unsigned columns with a non-numeric type, and that is correct.
Notes
- An empty selection is stored as SQL
NULL, not0.countryselectandcountyselectread the request as an int and map anything empty to NULL;stateselectreads a string and maps the literal'0'. Either way, "nothing chosen" is NULL — declare the columnnullable. - These are heavily nullable in practice: on a live install nearly every customer row had NULL for
shipping_county_idandshipping_state_id, because most stores never collect them. - There is no
cityselect. A city is a plainjoin— seebilling_city_idon the customer model — because there is nothing below it to cascade into.
@see join.md