Skip to content

Commit

Permalink
Implement dropdown of regions for large countries.
Browse files Browse the repository at this point in the history
  • Loading branch information
aljones15 authored and gannan08 committed Feb 26, 2019
1 parent 15ef7bb commit 02606f1
Show file tree
Hide file tree
Showing 12 changed files with 2,330 additions and 2,282 deletions.
8 changes: 8 additions & 0 deletions .eslintrc.js
@@ -0,0 +1,8 @@
module.exports = {
env: {
browser: true
},
extends: [
'eslint-config-digitalbazaar/vue'
]
};
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -8,3 +8,4 @@ coverage
node_modules
reports
.cache
bin/config.json
122 changes: 98 additions & 24 deletions BrAddressForm.vue
Expand Up @@ -6,6 +6,7 @@
v-model="streetAddress.value"
:float-label="streetAddress.label"
class="q-pa-sm q-mt-md"
autocomplete="address-line1"
@blur="$v.streetAddress.$touch"
@keyup="$v.streetAddress.$touch" />
</q-field>
Expand All @@ -19,6 +20,7 @@
v-model="addressLocality.value"
:float-label="addressLocality.label"
class="q-pa-sm q-mt-md"
autocomplete="address-level2"
@blur="$v.addressLocality.$touch"
@keyup="$v.addressLocality.$touch" />
</q-field>
Expand All @@ -29,6 +31,7 @@
v-model="postalCode.value"
:float-label="postalCode.label"
class="q-pa-sm q-mt-md"
autocomplete="postal-code"
@blur="$v.postalCode.$touch"
@keyup="$v.postalCode.$touch" />
</q-field>
Expand All @@ -42,6 +45,7 @@
v-model="addressLocality.value"
:float-label="addressLocality.label"
class="q-pa-sm q-mt-md"
autocomplete="address-level2"
@blur="$v.addressLocality.$touch"
@keyup="$v.addressLocality.$touch" />
</q-field>
Expand All @@ -50,11 +54,27 @@
v-if="addressCountryExists"
class="row justify-between q-mt-md">
<div class="col-sm-6 q-pr-md">
<q-field :error="$v.addressRegion.$error">
<q-field
v-if="regions.length > 0"
:error="$v.addressRegion.$error">
<q-select
v-model="addressRegion.value"
:float-label="addressRegion.label"
:options="regions"
filter
autofocus-filter
class="q-pa-sm q-mt-md fast-open"
@blur="$v.addressRegion.$touch"
@keyup="$v.addressRegion.$touch" />
</q-field>
<q-field
v-else
:error="$v.addressRegion.$error">
<q-input
v-model="addressRegion.value"
:float-label="addressRegion.label"
class="q-pa-sm q-mt-md"
autocomplete="address-level1"
@blur="$v.addressRegion.$touch"
@keyup="$v.addressRegion.$touch" />
</q-field>
Expand All @@ -66,7 +86,8 @@
:float-label="addressCountry.label"
:options="countries"
filter
class="q-pa-sm q-mt-md"
autofocus-filter
class="q-pa-sm q-mt-md fast-open"
@blur="$v.addressCountry.$touch"
@keyup="$v.addressCountry.$touch" />
</q-field>
Expand All @@ -80,9 +101,10 @@
<q-select
v-model="addressRegion.value"
:float-label="addressRegion.label"
:options="states"
:options="regions"
filter
class="q-pa-sm q-mt-md"
autofocus-filter
class="q-pa-sm q-mt-md fast-open"
@blur="$v.addressRegion.$touch"
@keyup="$v.addressRegion.$touch" />
</q-field>
Expand All @@ -93,6 +115,7 @@
v-model="postalCode.value"
:float-label="postalCode.label"
class="q-pa-sm q-mt-md"
autocomplete="postal-code"
@blur="$v.postalCode.$touch"
@keyup="$v.postalCode.$touch" />
</q-field>
Expand All @@ -107,9 +130,7 @@
'use strict';
import {minLength, required} from 'vuelidate/lib/validators';
import rawCountryCodes from './countryCodes';
import states from './states';
import countryOptions from './countries';
export default {
name: 'BrAddressForm',
Expand All @@ -125,12 +146,6 @@ export default {
default: () => ([])
}
},
data() {
return {
rawCountryCodes,
states
};
},
validations() {
if(this.addressCountryExists) {
return {
Expand Down Expand Up @@ -194,26 +209,39 @@ export default {
};
},
computed: {
regions() {
if(!this.addressCountryExists) {
return countryOptions
.find(c => c.value === 'US')
.children.map(region => ({label: region, value: region}));
}
const prefix = this.addressCountry.value;
const country = countryOptions.find(c => c.value === prefix);
if(!country) {
return [];
}
const regions = country.children;
return regions.map(region => ({label: region, value: region}));
},
addressCountry() {
return this.value.addressCountry || {};
},
addressCountryExists() {
return this.isString(this.addressCountry.value);
},
addressCountryValue() {
return this.addressCountry.value;
},
addressLocality() {
return this.value.addressLocality;
},
countries() {
let countries = this.rawCountryCodes.map(val => ({
label: val.country,
value: val.alpha2Code
}));
if(this.restrictCountry.length > 0) {
countries = countries.filter(
return countryOptions.filter(
({value}) => this.restrictCountry.includes(value)
);
}
return countries;
return countryOptions.sort((a, b) => a.label.localeCompare(b.label));
},
addressRegion() {
return this.value.addressRegion;
Expand All @@ -228,8 +256,42 @@ export default {
return !this.$v.$invalid;
}
},
watch: {
addressCountryValue(val) {
let updatedLabels = this.value;
if(val === 'US') {
updatedLabels = _applyLabels({
data: this.value,
force: ['addressRegion', 'postalCode'],
labels: {
addressRegion: 'State',
postalCode: 'ZIP'
}
});
} else if(val === 'CA') {
updatedLabels = _applyLabels({
data: this.value,
force: ['addressRegion', 'postalCode'],
labels: {
addressRegion: 'Province',
postalCode: 'Postal Code'
}
});
} else {
updatedLabels = _applyLabels({
data: this.value,
force: ['addressRegion', 'postalCode'],
labels: {
addressRegion: 'State/Province/Region',
postalCode: 'ZIP/Postal Code',
}
});
}
this.$emit('input', updatedLabels);
}
},
created() {
const updatedLabels = _applyDefaultLabels({
const updatedLabels = _applyLabels({
data: this.value,
labels: {
addressCountry: 'Country',
Expand All @@ -248,13 +310,22 @@ export default {
}
};
// TODO: Move to bedrock-web-forms
/**
* @function isString
* @param {String} str
* @description checks if something is a string.
*
* @returns {Boolean}
*/
function isString(str) {
return str && typeof str === 'string';
return typeof str === 'string';
}
function _applyDefaultLabels({data, labels}) {
// TODO: Move to bedrock-web-forms
function _applyLabels({data, labels, force = []}) {
return Object.keys(data).reduce((acc, key) => {
acc[key] = isString(data[key].label) ? data[key] :
console.log(isString(data[key].label), !force.includes(key), key);
acc[key] = isString(data[key].label) && !force.includes(key) ? data[key] :
{
...data[key],
label: labels[key]
Expand All @@ -263,5 +334,8 @@ function _applyDefaultLabels({data, labels}) {
}, {});
}
</script>
<style>
<style scoped>
div.fast-open {
transition-duration: 0.10s;
}
</style>
26 changes: 26 additions & 0 deletions README.md
@@ -1,2 +1,28 @@
# bedrock-vue-address-form

A Vue.js address form component for Bedrock Web apps

### View the component in the test harness
```
npm i
cd test/
npm i
npm start
```

### Re-Build countries.js

1. Create an account on http://www.geonames.org/
2. Navigate to the `bin/` directory
3. Update `username` in `config.json` with your geoname's username
4. Use the created JSON file to update `countries.js`
```
cd bin/
node index.js
```

### Additional Information

Geonames Country Data
* Source: http://www.geonames.org/
* License: https://creativecommons.org/licenses/by/4.0/
5 changes: 5 additions & 0 deletions bin/.eslintrc.js
@@ -0,0 +1,5 @@
module.exports = {
env: {
node: true
}
}
5 changes: 5 additions & 0 deletions bin/config.json
@@ -0,0 +1,5 @@
{
"username": "demo",
"include": ["iso", "children", "name"],
"filename": "countries.json"
}
88 changes: 88 additions & 0 deletions bin/index.js
@@ -0,0 +1,88 @@
const fetch = require('node-fetch');
const fs = require('fs');
const path = require('path');

const config = require('./config.json');

const COUNTRIES = 'http://download.geonames.org/export/dump/countryInfo.txt';

function requestChildren(id, username = 'demo') {
const base = 'http://api.geonames.org/childrenJSON';
const request = encodeURI(`${base}?geonameId=${id}&username=${username}`);
return fetch(request).then(r => r.json());
}

class Country {
constructor(data) {
this.line = data;
this.iso = data[0],
this.iso3 = data[1],
this.iso_numeric = data[2],
this.fips = data[3],
this.name = data[4],
this.capital = data[5],
this.area = Number(data[6]),
this.population = data[7],
this.continent = data[8],
this.tld = data[9],
this.currency_code = data[10],
this.currency_name = data[11],
this.phone = data[12],
this.postal_code_format = data[13],
this.postal_code_regex = data[14],
this.languages = data[15],
this.geoname_id = data[16],
this.neighbours = data[17],
this.children = [];
this.getChildren = this.getChildren.bind(this);
}
getChildren() {
if(this.area >= 1000000) {
return requestChildren(this.geoname_id, config.username)
.then(children => {
if(children && children.geonames) {
this.children = children.geonames.map(gn => gn.name);
}
return this;
});
}
return this;
}
}

function parseCountries(text) {
const lines = text.split('\n');
const notCommentedOut = lines.filter(l => !l.startsWith('#'));
const cells = notCommentedOut.map(l => l.split('\t'));
const countries = cells.map(c => new Country(c));
return Promise.all(countries.map(c => c.getChildren()));
}

function saveCountries(data) {
const countries = data.map(country => {
for(const key in country) {
if(!config.include.includes(key)) {
delete country[key];
}
}
country.label = country.name || '';
country.value = country.iso || '';
delete country.name;
delete country.iso;
return country;
})
.filter(c => c.label.length)
.sort((a, b) => a.label.localeCompare(b.label));
const filepath = path.join(__dirname, `${config.filename}`);
fs.writeFile(
filepath, JSON.stringify(countries, null, 2), 'utf-8', console.error);
}

function getCountries() {
return fetch(COUNTRIES)
.then(r => r.text())
.then(parseCountries)
.then(saveCountries);
}

getCountries().then(c => console.log('countries', c));

0 comments on commit 02606f1

Please sign in to comment.