Browse Source

Fixed front page on mobile and improved city lookup

development
Godwin 8 years ago
parent
commit
e83c2fc464
  1. 76
      Gemfile
  2. 32
      Rakefile
  3. 510
      app/assets/javascripts/main.js
  4. 21
      app/assets/stylesheets/_application.scss
  5. 2
      app/assets/stylesheets/bumbleberry-settings.json
  6. 71
      app/models/city.rb
  7. 2
      app/views/layouts/application.html.haml
  8. 23
      config/initializers/geocoder.rb

76
Gemfile

@ -11,9 +11,9 @@ gem 'haml'
gem 'nokogiri', '~> 1.6.8.rc2' gem 'nokogiri', '~> 1.6.8.rc2'
if Dir.exists?('../lingua_franca') if Dir.exists?('../lingua_franca')
gem 'lingua_franca', path: '../lingua_franca' gem 'lingua_franca', :path => '../lingua_franca'
else else
gem 'lingua_franca', git: 'git://github.com/lingua-franca/lingua_franca.git' gem 'lingua_franca', :git => 'git://github.com/lingua-franca/lingua_franca.git'
end end
gem 'tzinfo-data' gem 'tzinfo-data'
@ -21,15 +21,15 @@ gem 'sass'
gem 'sass-rails' gem 'sass-rails'
if Dir.exists?('../bumbleberry') if Dir.exists?('../bumbleberry')
gem 'bumbleberry', path: "../bumbleberry" gem 'bumbleberry', :path => "../bumbleberry"
else else
gem 'bumbleberry', git: 'git://github.com/bumbleberry/bumbleberry.git' gem 'bumbleberry', :git => 'git://github.com/bumbleberry/bumbleberry.git'
end end
if Dir.exists?('../paypal-express') if Dir.exists?('../paypal-express')
gem 'paypal-express', path: "../paypal-express" gem 'paypal-express', :path => "../paypal-express"
else else
gem 'paypal-express', git: 'git://github.com/bikebike/paypal-express.git' gem 'paypal-express', :git => 'git://github.com/bikebike/paypal-express.git'
end end
gem 'uglifier', '>= 1.3.0' gem 'uglifier', '>= 1.3.0'
@ -48,55 +48,55 @@ gem 'redcarpet'
gem 'sidekiq' gem 'sidekiq'
gem 'letter_opener' gem 'letter_opener'
gem 'launchy' gem 'launchy'
gem 'to_spreadsheet', git: 'git://github.com/glebm/to_spreadsheet.git' gem 'to_spreadsheet', :git => 'git://github.com/glebm/to_spreadsheet.git'
group :test do group :test do
gem 'rspec' gem 'rspec'
gem 'rspec-rails' gem 'rspec-rails'
end end
group :development do group :development do
gem 'better_errors' gem 'better_errors'
gem 'binding_of_caller' gem 'binding_of_caller'
gem 'meta_request' gem 'meta_request'
gem 'capistrano', '~> 3.1' gem 'capistrano', '~> 3.1'
gem 'capistrano-rails', '~> 1.1' gem 'capistrano-rails', '~> 1.1'
gem 'capistrano-faster-assets', '~> 1.0' gem 'capistrano-faster-assets', '~> 1.0'
gem 'eventmachine'#, :github => 'krzcho/eventmachine', :branch => 'master' gem 'eventmachine', :github => 'krzcho/eventmachine', :branch => 'master'
gem 'thin'#, :github => 'krzcho/thin', :branch => 'master' gem 'thin', :github => 'krzcho/thin', :branch => 'master'
end end
group :test do group :test do
gem 'gherkin3', '>= 3.1.0' gem 'gherkin3', '>= 3.1.0'
gem 'cucumber' gem 'cucumber'
gem 'cucumber-core' gem 'cucumber-core'
gem 'cucumber-rails' gem 'cucumber-rails'
gem 'poltergeist' gem 'poltergeist'
gem 'guard-rspec' gem 'guard-rspec'
gem 'factory_girl_rails' gem 'factory_girl_rails'
gem 'coveralls', require: false gem 'coveralls', require: false
gem 'selenium-webdriver' gem 'selenium-webdriver'
gem 'simplecov', require: false gem 'simplecov', require: false
gem 'webmock', require: false gem 'webmock', require: false
gem 'database_cleaner' gem 'database_cleaner'
gem 'mocha' gem 'mocha'
end end
group :staging, :production, :preview do group :staging, :production, :preview do
gem 'rails_12factor' gem 'rails_12factor'
end end
group :production, :preview do group :production, :preview do
gem 'unicorn' gem 'unicorn'
gem 'daemon-spawn' gem 'daemon-spawn'
gem 'daemons' gem 'daemons'
end end
platforms 'mswin', 'mingw' do platforms 'mswin', 'mingw' do
group :test do group :test do
gem 'wdm', '>= 0.1.0' gem 'wdm', '>= 0.1.0'
end end
end end

32
Rakefile

@ -24,15 +24,25 @@ end
task update_cities: :environment do task update_cities: :environment do
Location.all.each do |l| Location.all.each do |l|
city = City.search(([l.city, l.territory, l.country] - [nil, '']).join(', ')) s = ([l.city, l.territory, l.country] - [nil, '']).join(', ')
l.city_id = city.id unless l.city_id.present?
l.save! begin
puts "Searching for #{s}"
city = City.search(s)
l.city_id = city.id
l.save!
rescue
puts "Error searching for #{s}"
end
end
end end
City.all.each do |c| unless c.place_id.present?
location = Geocoder.search(c.address, language: 'en').first City.all.each do |c|
c.place_id = location.data['place_id'] location = Geocoder.search(c.address, language: 'en').first
c.save! c.place_id = location.data['place_id']
c.save!
end
end end
end end
@ -43,3 +53,11 @@ task update_cities_es: :environment do
c.save! c.save!
end end
end end
task update_cities_fr: :environment do
City.all.each do |c|
city = c.get_translation(:fr)
c.set_column_for_locale(:city, :fr, city, 0) unless city.blank? || city == c.get_column_for_locale(:city, :fr)
c.save!
end
end

510
app/assets/javascripts/main.js

@ -1,244 +1,290 @@
(function() { (function() {
window.onerror = function(message, url, lineNumber) { window.onerror = function(message, url, lineNumber) {
//save error and send to server for example. //save error and send to server for example.
var request = new XMLHttpRequest(); var request = new XMLHttpRequest();
request.open('POST', '/js_error', true); request.open('POST', '/js_error', true);
request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8'); request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
request.send( request.send(
'message=' + encodeURI(message) + 'message=' + encodeURI(message) +
'&url=' + encodeURI(url) + '&url=' + encodeURI(url) +
'&lineNumber=' + encodeURI(lineNumber) + '&lineNumber=' + encodeURI(lineNumber) +
'&location=' + encodeURI(window.location.href) '&location=' + encodeURI(window.location.href)
); );
return false; return false;
}; };
window.forEach = function(a, f) { Array.prototype.forEach.call(a, f) }; window.forEach = function(a, f) { Array.prototype.forEach.call(a, f) };
window.forEachElement = function(s, f, p) { forEach((p || document).querySelectorAll(s), f) }; window.forEachElement = function(s, f, p) { forEach((p || document).querySelectorAll(s), f) };
var overlay = document.getElementById('content-overlay'); var overlay = document.getElementById('content-overlay');
if (overlay) { if (overlay) {
var body = document.querySelector('body'); var body = document.querySelector('body');
var primaryContent = document.getElementById('primary-content'); var primaryContent = document.getElementById('primary-content');
primaryContent.addEventListener('keydown', function(event) { primaryContent.addEventListener('keydown', function(event) {
if (body.classList.contains('has-overlay')) { if (body.classList.contains('has-overlay')) {
event.stopPropagation(); event.stopPropagation();
return false; return false;
} }
}); });
document.addEventListener('focus', function(event) { document.addEventListener('focus', function(event) {
if (overlay.querySelector('.dlg.open') && !overlay.querySelector('.dlg.open :focus')) { if (overlay.querySelector('.dlg.open') && !overlay.querySelector('.dlg.open :focus')) {
overlay.querySelector('.dlg.open').focus(); overlay.querySelector('.dlg.open').focus();
} }
}, true); }, true);
window.openOverlay = function(dlg, primaryContent, body) { window.openOverlay = function(dlg, primaryContent, body) {
primaryContent.setAttribute('aria-hidden', 'true'); primaryContent.setAttribute('aria-hidden', 'true');
body.classList.add('has-overlay'); body.classList.add('has-overlay');
var type = dlg.getAttribute('data-type'); var type = dlg.getAttribute('data-type');
if (type) { if (type) {
body.classList.add('is-' + type + '-dlg'); body.classList.add('is-' + type + '-dlg');
} }
dlg.removeAttribute('aria-hidden'); dlg.removeAttribute('aria-hidden');
dlg.setAttribute('role', 'alertdialog'); dlg.setAttribute('role', 'alertdialog');
dlg.setAttribute('tabindex', '0'); dlg.setAttribute('tabindex', '0');
dlg.focus(); dlg.focus();
setTimeout(function() { dlg.classList.add('open'); }, 100); setTimeout(function() { dlg.classList.add('open'); }, 100);
} }
window.closeOverlay = function(dlg, primaryContent, body) { window.closeOverlay = function(dlg, primaryContent, body) {
setTimeout(function() { setTimeout(function() {
body.classList.remove('has-overlay'); body.classList.remove('has-overlay');
body.removeAttribute('style'); body.removeAttribute('style');
}, 250); }, 250);
var type = dlg.getAttribute('data-type'); var type = dlg.getAttribute('data-type');
if (type) { if (type) {
body.classList.remove('is-' + type + '-dlg'); body.classList.remove('is-' + type + '-dlg');
} }
primaryContent.removeAttribute('aria-hidden'); primaryContent.removeAttribute('aria-hidden');
dlg.setAttribute('aria-hidden', 'true'); dlg.setAttribute('aria-hidden', 'true');
dlg.removeAttribute('tabindex'); dlg.removeAttribute('tabindex');
dlg.classList.remove('open'); dlg.classList.remove('open');
dlg.removeAttribute('role'); dlg.removeAttribute('role');
} }
function openDlg(dlg, link) { function openDlg(dlg, link) {
document.getElementById('overlay').onclick = document.getElementById('overlay').onclick =
dlg.querySelector('.close').onclick = function() { closeDlg(dlg); }; dlg.querySelector('.close').onclick = function() { closeDlg(dlg); };
body.setAttribute('style', 'width: ' + body.clientWidth + 'px'); body.setAttribute('style', 'width: ' + body.clientWidth + 'px');
var msg = link.querySelector('.message'); var msg = link.querySelector('.message');
if (msg) { if (msg) {
dlg.querySelector('.message').innerHTML = msg.innerHTML dlg.querySelector('.message').innerHTML = msg.innerHTML
} }
if (link.dataset.infoTitle) { if (link.dataset.infoTitle) {
dlg.querySelector('.title').innerHTML = decodeURI(link.dataset.infoTitle); dlg.querySelector('.title').innerHTML = decodeURI(link.dataset.infoTitle);
} }
confirmBtn = dlg.querySelector('.confirm'); confirmBtn = dlg.querySelector('.confirm');
if (confirmBtn) { if (confirmBtn) {
confirmBtn.addEventListener('click', function(event) { confirmBtn.addEventListener('click', function(event) {
event.preventDefault(); event.preventDefault();
if (link.tagName == 'BUTTON') { if (link.tagName == 'BUTTON') {
var form = link.parentElement var form = link.parentElement
while (form && form.tagName != 'FORM') { while (form && form.tagName != 'FORM') {
var form = form.parentElement var form = form.parentElement
} }
if (form) { if (form) {
var input = document.createElement('input'); var input = document.createElement('input');
input.type = 'hidden'; input.type = 'hidden';
input.name = 'button'; input.name = 'button';
input.value = link.value; input.value = link.value;
form.appendChild(input); form.appendChild(input);
form.submit(); form.submit();
} }
} else { } else {
window.location.href = link.getAttribute('href'); window.location.href = link.getAttribute('href');
} }
}); });
} }
window.openOverlay(dlg, primaryContent, body); window.openOverlay(dlg, primaryContent, body);
} }
function closeDlg(dlg) { function closeDlg(dlg) {
window.closeOverlay(dlg, primaryContent, body); window.closeOverlay(dlg, primaryContent, body);
} }
var confirmationDlg = document.getElementById('confirmation-dlg'); var confirmationDlg = document.getElementById('confirmation-dlg');
forEachElement('[data-confirmation]', function(link) { forEachElement('[data-confirmation]', function(link) {
link.addEventListener('click', function(event) { link.addEventListener('click', function(event) {
event.preventDefault(); event.preventDefault();
openDlg(confirmationDlg, link); openDlg(confirmationDlg, link);
return false; return false;
}); });
}); });
var infoDlg = document.getElementById('info-dlg'); var infoDlg = document.getElementById('info-dlg');
forEachElement('[data-info-text]', function(link) { forEachElement('[data-info-text]', function(link) {
link.addEventListener('click', function(event) { link.addEventListener('click', function(event) {
event.preventDefault(); event.preventDefault();
openDlg(infoDlg, link); openDlg(infoDlg, link);
return false; return false;
}); });
}); });
var loginDlg = document.getElementById('login-dlg'); var loginDlg = document.getElementById('login-dlg');
forEachElement('[data-sign-in]', function(link) { forEachElement('[data-sign-in]', function(link) {
link.addEventListener('click', function(event) { link.addEventListener('click', function(event) {
event.preventDefault(); event.preventDefault();
openDlg(loginDlg, link); openDlg(loginDlg, link);
return false; return false;
}); });
}); });
var contactDlg = document.getElementById('contact-dlg'); var contactDlg = document.getElementById('contact-dlg');
var contactLink = document.getElementById('contact-link'); var contactLink = document.getElementById('contact-link');
contactLink.addEventListener('click', function(event) { contactLink.addEventListener('click', function(event) {
event.preventDefault(); event.preventDefault();
openDlg(contactDlg, contactLink); openDlg(contactDlg, contactLink);
return false; return false;
}); });
} }
var htmlNode = document.documentElement; var htmlNode = document.documentElement;
document.addEventListener('keydown', function(event) { document.addEventListener('keydown', function(event) {
if (htmlNode.dataset.input != 'kb' && if (htmlNode.dataset.input != 'kb' &&
((["input", "textarea", "select", "option"].indexOf(event.target.nodeName.toLowerCase()) < 0 && ((["input", "textarea", "select", "option"].indexOf(event.target.nodeName.toLowerCase()) < 0 &&
!event.target.attributes.contenteditable) || event.key == "Tab")) { !event.target.attributes.contenteditable) || event.key == "Tab")) {
htmlNode.setAttribute('data-input', 'kb'); htmlNode.setAttribute('data-input', 'kb');
} }
}); });
document.addEventListener('mousemove', function(event) { document.addEventListener('mousemove', function(event) {
if (htmlNode.dataset.input != 'mouse' && (event.movementX || event.movementY)) { if (htmlNode.dataset.input != 'mouse' && (event.movementX || event.movementY)) {
htmlNode.setAttribute('data-input', 'mouse'); htmlNode.setAttribute('data-input', 'mouse');
} }
}); });
var errorField = document.querySelector('.input-field.has-error input, .input-field.has-error textarea'); var errorField = document.querySelector('.input-field.has-error input, .input-field.has-error textarea');
if (errorField) { if (errorField) {
errorField.focus(); errorField.focus();
} }
window.initNodeFunctions = [ function(node) { window.initNodeFunctions = [ function(node) {
forEachElement('.number-field,.email-field,.text-field,.password-field,.search-field', function(field) { forEachElement('.number-field,.email-field,.text-field,.password-field,.search-field', function(field) {
var input = field.querySelector('input'); var input = field.querySelector('input');
var positionLabel = function(input) { var positionLabel = function(input) {
field.classList[input.value ? 'remove' : 'add']('empty'); field.classList[input.value ? 'remove' : 'add']('empty');
} }
positionLabel(input); positionLabel(input);
input.addEventListener('keyup', function(event) { input.addEventListener('keyup', function(event) {
positionLabel(event.target); positionLabel(event.target);
}); });
input.addEventListener('blur', function(event) { input.addEventListener('blur', function(event) {
positionLabel(event.target); positionLabel(event.target);
field.classList.remove('focused'); field.classList.remove('focused');
}); });
input.addEventListener('focus', function(event) { input.addEventListener('focus', function(event) {
field.classList.add('focused'); field.classList.add('focused');
}); });
}, node || document); }, node || document);
forEachElement('form.js-xhr', function(form) { forEachElement('form.js-xhr', function(form) {
if (form.addEventListener) { if (form.addEventListener) {
form.addEventListener('submit', function(event) { form.addEventListener('submit', function(event) {
event.preventDefault(); event.preventDefault();
form.classList.add('requesting'); form.classList.add('requesting');
var data = new FormData(form); var data = new FormData(form);
var request = new XMLHttpRequest(); var request = new XMLHttpRequest();
request.onreadystatechange = function() { request.onreadystatechange = function() {
if (request.readyState == 4) { if (request.readyState == 4) {
form.classList.remove('requesting'); form.classList.remove('requesting');
if (request.status == 200) { if (request.status == 200) {
var response = JSON.parse(request.responseText); var response = JSON.parse(request.responseText);
for (var i = 0; i < response.length; i++) { for (var i = 0; i < response.length; i++) {
var element; var element;
if (response[i].selector) { if (response[i].selector) {
element = form.querySelector(response[i].selector); element = form.querySelector(response[i].selector);
} }
if (response[i].globalSelector) { if (response[i].globalSelector) {
element = document.querySelector(response[i].globalSelector); element = document.querySelector(response[i].globalSelector);
} }
if (response[i].html) { if (response[i].html) {
element.innerHTML = response[i].html; element.innerHTML = response[i].html;
window.initNode(element); window.initNode(element);
} }
if (response[i].className) { if (response[i].className) {
element.className = response[i].className; element.className = response[i].className;
} }
} }
} }
} }
} }
request.open('POST', form.getAttribute('action'), true); request.open('POST', form.getAttribute('action'), true);
request.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); request.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
request.send(data); request.send(data);
}, false); }, false);
} }
}, node || document); }, node || document);
forEachElement('[data-opens]', function(control) { forEachElement('[data-opens]', function(control) {
control.addEventListener('click', function(event) { control.addEventListener('click', function(event) {
var opens = document.querySelector(control.getAttribute('data-opens')); var opens = document.querySelector(control.getAttribute('data-opens'));
if (!opens.classList.contains('open')){ if (!opens.classList.contains('open')){
event.preventDefault(); event.preventDefault();
opens.className += ' open'; opens.className += ' open';
if (control.getAttribute('data-focus')) { if (control.getAttribute('data-focus')) {
var input = opens.querySelector(control.getAttribute('data-focus')); var input = opens.querySelector(control.getAttribute('data-focus'));
if (input) { if (input) {
input.focus(); input.focus();
} }
} }
return false; return false;
} }
}); });
}); });
} ]; } ];
window.initNode = function(node) { window.initNode = function(node) {
forEach(initNodeFunctions, function(fn) { forEach(initNodeFunctions, function(fn) {
fn(node); fn(node);
}); });
}; };
initNode(); initNode();
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
var errors = document.getElementsByClassName('has-error'); var errors = document.getElementsByClassName('has-error');
if (errors.length <= 0) { if (errors.length <= 0) {
errors = document.getElementsByClassName('info-message'); errors = document.getElementsByClassName('info-message');
} }
if (errors.length > 0) { if (errors.length > 0) {
errors[0].scrollIntoView(); errors[0].scrollIntoView();
} }
}); });
})(); })();
function generateScreenshot() {
var css = '';
var svgs = document.getElementsByTagName('svg');
for (var i = 0; i < document.styleSheets.length; i++) {
if (document.styleSheets[i].href && !document.styleSheets[i].href.match(/web\-fonts/)) {
var rules = document.styleSheets[i].rules || document.styleSheets[i].cssRules;
for (var j = 0; j < rules.length; j++) {
cssText = rules[j].cssText;
if (rules[j].selectorText) {
if (rules[j].selectorText.match(/(^|\s)svg[^\w]/) || cssText.match(/\s(fill|stroke(\-width)?):/)) {
css += cssText;
}
}
}
}
}
for (var i = 0; i < svgs.length; i++) {
var svg = svgs[i];
// svg.innerHTML = '<style type="text/css"><![CDATA[' + css + ']]></style>' + svg.innerHTML;
svg.innerHTML = '<style type="text/css"><![CDATA[' + css + ']]></style>' + svg.innerHTML;
svg.setAttribute('height', svg.clientHeight);
svg.setAttribute('width', svg.clientWidth);
var canvas = document.createElement('canvas');
canvg(canvas, svg.outerHTML);
console.log(svg.outerHTML);
svg.style.backgroundImage = 'url(' + encodeURI(canvas.toDataURL('image/png')) + ')';
}
html2canvas(document.body, {
logging: true,
profile: true,
useCORS: true}).then(function(canvas) {
var data = canvas.toDataURL('image/jpeg', 0.9);
var src = encodeURI(data);
window.open(src, '_blank');
// reset the svg height and width
for (var i = 0; i < svgs.length; i++) {
var svg = svgs[i];
svg.removeAttribute('height');
svg.removeAttribute('width');
svg.removeAttribute('style');
}
});
}

21
app/assets/stylesheets/_application.scss

@ -12,6 +12,7 @@ html, body {
} }
body { body {
background-color: $white;
padding-bottom: 20vw; padding-bottom: 20vw;
} }
@ -1807,6 +1808,7 @@ ul.warnings {
a { a {
color: inherit; color: inherit;
padding: 0 0.333em; padding: 0 0.333em;
display: inline-block;
&:hover { &:hover {
@include after { @include after {
@ -2946,6 +2948,9 @@ a.logo {
.conference-banner { .conference-banner {
text-align: center; text-align: center;
padding: 0;
margin: 0 -1em 2em;
width: auto;
.title { .title {
font-size: 5vw; font-size: 5vw;
@ -2964,6 +2969,10 @@ a.logo {
font-size: 0.85em; font-size: 0.85em;
line-height: 2em; line-height: 2em;
} }
img {
max-width: 100%;
}
} }
.conference-details { .conference-details {
@ -4653,15 +4662,6 @@ html[data-ontop] {
} }
} }
} }
// .nav a.register, .nav button {
// @include before-and-after {
// background-color: $colour-2;
// }
// &:focus, &:active {
// @include _(transform, none);
// }
// }
} }
#banner { #banner {
@ -4674,6 +4674,9 @@ html[data-ontop] {
} }
.conference-banner { .conference-banner {
margin: 0 auto;
width: 100%;
.title { .title {
font-size: 1.9em; font-size: 1.9em;
margin: 0 0 1em; margin: 0 0 1em;

2
app/assets/stylesheets/bumbleberry-settings.json

@ -8,7 +8,7 @@
"and_chr": ["54"], "and_chr": ["54"],
"chrome": ["54"], "chrome": ["54"],
"edge": ["13"], "edge": ["13"],
"firefox": ["44"], "firefox": ["48"],
"ie": ["11"], "ie": ["11"],
"ios_saf": ["8", "9"] "ios_saf": ["8", "9"]
} }

71
app/models/city.rb

@ -18,19 +18,30 @@ class City < ActiveRecord::Base
def get_translation(locale) def get_translation(locale)
location = Geocoder.search(address, language: locale.to_s).first location = Geocoder.search(address, language: locale.to_s).first
# if the service lets us down, return nil
return nil unless location.present?
searched_component = false
location.data['address_components'].each do | component | location.data['address_components'].each do | component |
# city is usually labelled a 'locality' but sometimes this is missing and only 'colloquial_area' is present # city is usually labeled a 'locality' but sometimes this is missing and only 'colloquial_area' is present
if component['types'].first == 'locality' || component['types'].first == 'colloquial_area' if component['types'].first == 'locality'
return component['short_name'] return component['short_name']
end end
if component['types'] == location.data['types']
searched_component = component['short_name']
end
end end
return nil # return the type we searched for but it's still possible that it will be false
searched_component
end end
# this method will get called automatically if a translation is asked for but not found
def translate_city(locale) def translate_city(locale)
translation = get_translation(locale) translation = get_translation(locale)
# if we found it, set it
if translation.present? if translation.present?
set_column_for_locale(:city, locale, translation) set_column_for_locale(:city, locale, translation)
save! save!
@ -48,16 +59,20 @@ class City < ActiveRecord::Base
# look up the city in the geocoder # look up the city in the geocoder
location = Geocoder.search(str, language: 'en').first location = Geocoder.search(str, language: 'en').first
# return nil to indicate that the service is down
return nil unless location.present?
# see if the city is already present in our database # see if the city is already present in our database
city = City.find_by_place_id(location.data['place_id']) city = City.find_by_place_id(location.data['place_id'])
# return the city if we found it in the db already # return the city if we found it in the db already
return city if city.present? if city.present?
CityCache.create(city_id: city.id, search: str)
return city
end
# otherwise build a new city # otherwise build a new city
component_alises = { component_alises = {
'locality' => :city, 'locality' => :city,
'colloquial_area' => :city,
'administrative_area_level_1' => :territory, 'administrative_area_level_1' => :territory,
'country' => :country 'country' => :country
} }
@ -67,15 +82,61 @@ class City < ActiveRecord::Base
longitude: location.data['geometry']['location']['lng'], longitude: location.data['geometry']['location']['lng'],
place_id: location.data['place_id'] place_id: location.data['place_id']
} }
# these things are definitely not cities, make sure we don't think they're one
not_a_city = [
'administrative_area_level_1',
'country',
'street_address',
'street_number',
'postal_code',
'postal_code_prefix',
'route',
'intersection',
'premise',
'subpremise',
'natural_feature',
'airport',
'park',
'point_of_interest',
'bus_station',
'train_station',
'transit_station',
'room',
'post_box',
'parking',
'establishment',
'floor'
]
searched_component = nil
location.data['address_components'].each do | component | location.data['address_components'].each do | component |
property = component_alises[component['types'].first] property = component_alises[component['types'].first]
city_data[property] = component['short_name'] if property.present? city_data[property] = component['short_name'] if property.present?
# ideally we will find the component that is labeled a locality but
# if that fails we will select what was searched for, hopefully they searched for a city
# and not an address or country
# some places are not labeled 'locality', search for 'Halifax NS' for example and you will
# get 'administrative_area_level_2' since Halifax is a municipality
if component['types'] == location.data['types'] && not_a_city.include?(component['types'].first)
searched_component = component['short_name']
end
end end
# fall back to the searched component
city_data[:city] ||= searched_component
# we need to have the city and country at least
return false unless city_data[:city].present? && city_data[:country].present?
# save the new city # save the new city
city = City.new(city_data) city = City.new(city_data)
city.save! city.save!
# save this to our cache
CityCache.create(city_id: city.id, search: str)
# and return it # and return it
return city return city
end end

2
app/views/layouts/application.html.haml

@ -20,6 +20,8 @@
%meta{property: 'og:image', content: og_image} %meta{property: 'og:image', content: og_image}
%meta{name: "theme-color", content: @theme_colour} %meta{name: "theme-color", content: @theme_colour}
= yield :head = yield :head
= javascript_include_tag 'https://rawgit.com/niklasvh/html2canvas/master/dist/html2canvas.js'
= javascript_include_tag 'https://cdnjs.cloudflare.com/ajax/libs/amcharts/3.13.0/exporting/canvg.js'
%body{ class: page_style } %body{ class: page_style }
#primary-content #primary-content

23
config/initializers/geocoder.rb

@ -1,21 +1,24 @@
# config/initializers/geocoder.rb # config/initializers/geocoder.rb
Geocoder.configure( config = {
# geocoding service (see below for supported options): # geocoding service (see below for supported options):
:lookup => :google, lookup: :google,
# IP address geocoding service (see below for supported options): # IP address geocoding service (see below for supported options):
:ip_lookup => :freegeoip, ip_lookup: :freegeoip,
# to use an API key: # to use an API key:
# :api_key => "AIzaSyDitM1lyVWkrumteDvSkje6GiIKYyHlAXM",
# geocoding service request timeout, in seconds (default 3): # geocoding service request timeout, in seconds (default 3):
:timeout => 5, timeout: 5,
# set default units to kilometers: # set default units to kilometers:
:units => :km, units: :km
}
# caching (see below for details): # use our api key on the server
#:cache => Redis.new, if Rails.env.preview? || Rails.env.production?
#:cache_prefix => "..." config[:api_key] = "AIzaSyDurfjX9f_NgYsJLyUuGqwdKuI745CE_OE"
) config[:use_https] = true
end
Geocoder.configure(config)

Loading…
Cancel
Save