Add type champs "Address" plug at the BAN
This commit is contained in:
parent
35a07aec87
commit
23ab25396f
18 changed files with 2804 additions and 13 deletions
|
@ -29,8 +29,7 @@
|
||||||
//= require franceconnect
|
//= require franceconnect
|
||||||
//= require bootstrap-wysihtml5
|
//= require bootstrap-wysihtml5
|
||||||
//= require bootstrap-wysihtml5/locales/fr-FR
|
//= require bootstrap-wysihtml5/locales/fr-FR
|
||||||
|
//= require typeahead.bundle
|
||||||
|
|
||||||
|
|
||||||
$(document).on('page:load', scroll_to);
|
$(document).on('page:load', scroll_to);
|
||||||
$(document).ready(scroll_to);
|
$(document).ready(scroll_to);
|
||||||
|
|
|
@ -13,6 +13,8 @@ function action_type_de_champs() {
|
||||||
|
|
||||||
toggleErrorClass(this, validatePhone(val));
|
toggleErrorClass(this, validatePhone(val));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
address_type_init();
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleErrorClass(node, boolean) {
|
function toggleErrorClass(node, boolean) {
|
||||||
|
@ -35,3 +37,27 @@ function validateEmail(email) {
|
||||||
function validateInput(input, regex) {
|
function validateInput(input, regex) {
|
||||||
return regex.test(input);
|
return regex.test(input);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function address_type_init() {
|
||||||
|
|
||||||
|
display = 'label';
|
||||||
|
|
||||||
|
var bloodhound = new Bloodhound({
|
||||||
|
datumTokenizer: Bloodhound.tokenizers.obj.whitespace(display),
|
||||||
|
queryTokenizer: Bloodhound.tokenizers.whitespace,
|
||||||
|
|
||||||
|
remote: {
|
||||||
|
url: '/ban/search?request=%QUERY',
|
||||||
|
wildcard: '%QUERY'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
bloodhound.initialize();
|
||||||
|
|
||||||
|
$("input[type='address']").typeahead({
|
||||||
|
minLength: 1
|
||||||
|
}, {
|
||||||
|
display: display,
|
||||||
|
source: bloodhound,
|
||||||
|
limit: 5
|
||||||
|
});
|
||||||
|
}
|
|
@ -21,6 +21,15 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.type_champ-address {
|
||||||
|
@extend .col-md-6;
|
||||||
|
@extend .col-lg-6;
|
||||||
|
|
||||||
|
input[type='address'] {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.type_champ-email {
|
.type_champ-email {
|
||||||
@extend .col-md-4;
|
@extend .col-md-4;
|
||||||
@extend .col-lg-4;
|
@extend .col-lg-4;
|
||||||
|
|
35
app/assets/stylesheets/typeahead.scss
Normal file
35
app/assets/stylesheets/typeahead.scss
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
.tt-menu {
|
||||||
|
width: 555px;
|
||||||
|
padding: 8px 0;
|
||||||
|
background-color: #fff;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.2);
|
||||||
|
-webkit-border-radius: 8px;
|
||||||
|
-moz-border-radius: 8px;
|
||||||
|
border-radius: 8px;
|
||||||
|
-webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, .2);
|
||||||
|
-moz-box-shadow: 0 5px 10px rgba(0, 0, 0, .2);
|
||||||
|
box-shadow: 0 5px 10px rgba(0, 0, 0, .2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tt-suggestion {
|
||||||
|
padding: 3px 20px;
|
||||||
|
font-size: 18px;
|
||||||
|
line-height: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.twitter-typeahead {
|
||||||
|
width: 555px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tt-suggestion:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
color: #fff;
|
||||||
|
background-color: #0097cf;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tt-suggestion.tt-cursor {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #0097cf;
|
||||||
|
|
||||||
|
}
|
9
app/controllers/ban/search_controller.rb
Normal file
9
app/controllers/ban/search_controller.rb
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
class Ban::SearchController < ApplicationController
|
||||||
|
def get
|
||||||
|
request = params[:request]
|
||||||
|
|
||||||
|
render json: Carto::Bano::AddressRetriever.new(request).list.inject([]) {
|
||||||
|
|acc, value| acc.push({label: value})
|
||||||
|
}.to_json
|
||||||
|
end
|
||||||
|
end
|
|
@ -7,4 +7,9 @@ class Champ < ActiveRecord::Base
|
||||||
def mandatory?
|
def mandatory?
|
||||||
mandatory
|
mandatory
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def data_provide
|
||||||
|
return 'datepicker' if type_champ == 'datetime'
|
||||||
|
return 'typeahead' if type_champ == 'address'
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
class TypeDeChamp < ActiveRecord::Base
|
class TypeDeChamp < ActiveRecord::Base
|
||||||
enum type_champs: {text: 'text',
|
enum type_champs: {
|
||||||
email: 'email',
|
text: 'text',
|
||||||
phone: 'phone',
|
textarea: 'textarea',
|
||||||
civilite: 'civilite',
|
datetime: 'datetime',
|
||||||
textarea: 'textarea',
|
number: 'number',
|
||||||
datetime: 'datetime',
|
checkbox: 'checkbox',
|
||||||
number: 'number',
|
civilite: 'civilite',
|
||||||
checkbox: 'checkbox'
|
email: 'email',
|
||||||
|
phone: 'phone',
|
||||||
|
address: 'address'
|
||||||
}
|
}
|
||||||
|
|
||||||
belongs_to :procedure
|
belongs_to :procedure
|
||||||
|
|
|
@ -34,5 +34,5 @@
|
||||||
id: "champs_#{champ.id}",
|
id: "champs_#{champ.id}",
|
||||||
value: champ.value,
|
value: champ.value,
|
||||||
type: champ.type_champ,
|
type: champ.type_champ,
|
||||||
'data-provide' => ('datepicker' if champ.type_champ == 'datetime'),
|
'data-provide' => champ.data_provide,
|
||||||
'data-date-format' => ('dd/mm/yyyy' if champ.type_champ == 'datetime')}
|
'data-date-format' => ('dd/mm/yyyy' if champ.type_champ == 'datetime')}
|
|
@ -108,6 +108,10 @@ Rails.application.routes.draw do
|
||||||
resources :gestionnaires, only: [:index, :create, :destroy]
|
resources :gestionnaires, only: [:index, :create, :destroy]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
namespace :ban do
|
||||||
|
get 'search' => 'search#get'
|
||||||
|
end
|
||||||
|
|
||||||
get 'backoffice' => 'backoffice#index'
|
get 'backoffice' => 'backoffice#index'
|
||||||
|
|
||||||
namespace :backoffice do
|
namespace :backoffice do
|
||||||
|
|
36
lib/carto/bano/address_retriever.rb
Normal file
36
lib/carto/bano/address_retriever.rb
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
module Carto
|
||||||
|
module Bano
|
||||||
|
# input : address
|
||||||
|
# output : Array List label address
|
||||||
|
class AddressRetriever
|
||||||
|
def initialize(address)
|
||||||
|
@address = address
|
||||||
|
end
|
||||||
|
|
||||||
|
def list
|
||||||
|
@list ||= convert_driver_result_to_full_address
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def driver
|
||||||
|
@driver ||= Carto::Bano::Driver.new @address, 5
|
||||||
|
end
|
||||||
|
|
||||||
|
def convert_driver_result_to_full_address
|
||||||
|
result = JSON.parse(driver.call)
|
||||||
|
|
||||||
|
if result['features'].size == 0
|
||||||
|
Rails.logger.error "unable to find location for address #{@address}"
|
||||||
|
return []
|
||||||
|
end
|
||||||
|
|
||||||
|
result['features'].inject([]) do |acc, feature|
|
||||||
|
acc.push feature['properties']['label']
|
||||||
|
end
|
||||||
|
rescue TypeError, JSON::ParserError
|
||||||
|
[]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -3,12 +3,15 @@ module Carto
|
||||||
# input : string (address)
|
# input : string (address)
|
||||||
# output : json
|
# output : json
|
||||||
class Driver
|
class Driver
|
||||||
def initialize(address)
|
def initialize(address, limit = 1)
|
||||||
@address = address
|
@address = address
|
||||||
|
@limit = limit
|
||||||
end
|
end
|
||||||
|
|
||||||
def call
|
def call
|
||||||
RestClient.get api_url, params: { q: @address, limit: 1 }
|
RestClient.get api_url, params: { q: @address, limit: @limit }
|
||||||
|
rescue RestClient::ServiceUnavailable
|
||||||
|
nil
|
||||||
end
|
end
|
||||||
|
|
||||||
def api_url
|
def api_url
|
||||||
|
|
16
spec/controllers/ban/search_controller_spec.rb
Normal file
16
spec/controllers/ban/search_controller_spec.rb
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe Ban::SearchController, type: :controller do
|
||||||
|
describe '#GET' do
|
||||||
|
|
||||||
|
let (:request) { '' }
|
||||||
|
|
||||||
|
before do
|
||||||
|
stub_request(:get, "http://api-adresse.data.gouv.fr/search?limit=5&q=").
|
||||||
|
to_return(:status => 200, :body => 'Missing query', :headers => {})
|
||||||
|
get :get, request: request
|
||||||
|
end
|
||||||
|
|
||||||
|
it { expect(response.status).to eq 200 }
|
||||||
|
end
|
||||||
|
end
|
4
spec/factories/champ.rb
Normal file
4
spec/factories/champ.rb
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
FactoryGirl.define do
|
||||||
|
factory :champ do
|
||||||
|
end
|
||||||
|
end
|
44
spec/lib/carto/bano/address_retriever_spec.rb
Normal file
44
spec/lib/carto/bano/address_retriever_spec.rb
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe Carto::Bano::AddressRetriever do
|
||||||
|
describe '#list' do
|
||||||
|
let(:request) { 'Paris' }
|
||||||
|
let(:response) { File.open('spec/support/files/ban_address_search.json') }
|
||||||
|
let(:status) { 200 }
|
||||||
|
|
||||||
|
subject { described_class.new(request).list }
|
||||||
|
|
||||||
|
before do
|
||||||
|
stub_request(:get, "http://api-adresse.data.gouv.fr/search?&q=#{request}&limit=5")
|
||||||
|
.to_return(status: status, body: response, headers: {})
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when address return a list of address' do
|
||||||
|
it { expect(subject.size).to eq 5 }
|
||||||
|
it { is_expected.to be_an_instance_of Array }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when address return an empty list' do
|
||||||
|
let(:response) { File.open('spec/support/files/ban_address_search_no_result.json') }
|
||||||
|
|
||||||
|
it { expect(subject.size).to eq 0 }
|
||||||
|
it { is_expected.to be_an_instance_of Array }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when BAN is unavailable' do
|
||||||
|
let(:status) { 503 }
|
||||||
|
let(:response) { '' }
|
||||||
|
|
||||||
|
it { expect(subject.size).to eq 0 }
|
||||||
|
it { is_expected.to be_an_instance_of Array }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when request is empty' do
|
||||||
|
let(:response) { 'Missing query' }
|
||||||
|
let(:request) { '' }
|
||||||
|
|
||||||
|
it { expect(subject.size).to eq 0 }
|
||||||
|
it { is_expected.to be_an_instance_of Array }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -15,4 +15,26 @@ describe Champ do
|
||||||
it { is_expected.to delegate_method(:type_champ).to(:type_de_champ) }
|
it { is_expected.to delegate_method(:type_champ).to(:type_de_champ) }
|
||||||
it { is_expected.to delegate_method(:order_place).to(:type_de_champ) }
|
it { is_expected.to delegate_method(:order_place).to(:type_de_champ) }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'data_provide' do
|
||||||
|
let(:champ) { create :champ }
|
||||||
|
|
||||||
|
subject { champ.data_provide }
|
||||||
|
|
||||||
|
context 'when type_champ is datetime' do
|
||||||
|
before do
|
||||||
|
champ.type_de_champ = create :type_de_champ, type_champ: 'datetime'
|
||||||
|
end
|
||||||
|
|
||||||
|
it { is_expected.to eq 'datepicker' }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when type_champ is address' do
|
||||||
|
before do
|
||||||
|
champ.type_de_champ = create :type_de_champ, type_champ: 'address'
|
||||||
|
end
|
||||||
|
|
||||||
|
it { is_expected.to eq 'typeahead' }
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
117
spec/support/files/ban_address_search.json
Normal file
117
spec/support/files/ban_address_search.json
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
{
|
||||||
|
"limit": 5,
|
||||||
|
"attribution": "BAN",
|
||||||
|
"version": "draft",
|
||||||
|
"licence": "ODbL 1.0",
|
||||||
|
"query": "Paris",
|
||||||
|
"type": "FeatureCollection",
|
||||||
|
"features": [
|
||||||
|
{
|
||||||
|
"geometry": {
|
||||||
|
"type": "Point",
|
||||||
|
"coordinates": [
|
||||||
|
2.3469,
|
||||||
|
48.8589
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"properties": {
|
||||||
|
"adm_weight": "6",
|
||||||
|
"citycode": "75056",
|
||||||
|
"name": "Paris",
|
||||||
|
"city": "Paris",
|
||||||
|
"postcode": "75000",
|
||||||
|
"context": "75, \u00cele-de-France",
|
||||||
|
"score": 1.0,
|
||||||
|
"label": "Paris",
|
||||||
|
"id": "75056",
|
||||||
|
"type": "city",
|
||||||
|
"population": "2244"
|
||||||
|
},
|
||||||
|
"type": "Feature"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"geometry": {
|
||||||
|
"type": "Point",
|
||||||
|
"coordinates": [
|
||||||
|
4.366801,
|
||||||
|
44.425528
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"properties": {
|
||||||
|
"citycode": "07330",
|
||||||
|
"postcode": "07150",
|
||||||
|
"name": "Paris",
|
||||||
|
"id": "07330_B095_bd3524",
|
||||||
|
"context": "07, Ard\u00e8che, Rh\u00f4ne-Alpes",
|
||||||
|
"score": 0.8291454545454544,
|
||||||
|
"label": "Paris 07150 Vallon-Pont-d'Arc",
|
||||||
|
"city": "Vallon-Pont-d'Arc",
|
||||||
|
"type": "locality"
|
||||||
|
},
|
||||||
|
"type": "Feature"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"geometry": {
|
||||||
|
"type": "Point",
|
||||||
|
"coordinates": [
|
||||||
|
3.564293,
|
||||||
|
45.766413
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"properties": {
|
||||||
|
"citycode": "63125",
|
||||||
|
"postcode": "63120",
|
||||||
|
"name": "Paris",
|
||||||
|
"city": "Courpi\u00e8re",
|
||||||
|
"context": "63, Puy-de-D\u00f4me, Auvergne",
|
||||||
|
"score": 0.8255363636363636,
|
||||||
|
"label": "Paris 63120 Courpi\u00e8re",
|
||||||
|
"id": "63125_B221_03549b",
|
||||||
|
"type": "locality"
|
||||||
|
},
|
||||||
|
"type": "Feature"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"geometry": {
|
||||||
|
"type": "Point",
|
||||||
|
"coordinates": [
|
||||||
|
1.550208,
|
||||||
|
44.673592
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"properties": {
|
||||||
|
"citycode": "46138",
|
||||||
|
"postcode": "46240",
|
||||||
|
"name": "PARIS (Vaillac)",
|
||||||
|
"city": "C\u0153ur de Causse",
|
||||||
|
"context": "46, Lot, Midi-Pyr\u00e9n\u00e9es",
|
||||||
|
"score": 0.824090909090909,
|
||||||
|
"label": "PARIS (Vaillac) 46240 C\u0153ur de Causse",
|
||||||
|
"id": "46138_XXXX_6ee4ec",
|
||||||
|
"type": "street"
|
||||||
|
},
|
||||||
|
"type": "Feature"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"geometry": {
|
||||||
|
"type": "Point",
|
||||||
|
"coordinates": [
|
||||||
|
-0.526884,
|
||||||
|
43.762253
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"properties": {
|
||||||
|
"citycode": "40282",
|
||||||
|
"postcode": "40500",
|
||||||
|
"name": "Paris",
|
||||||
|
"city": "Saint-Sever",
|
||||||
|
"context": "40, Landes, Aquitaine",
|
||||||
|
"score": 0.8236181818181818,
|
||||||
|
"label": "Paris 40500 Saint-Sever",
|
||||||
|
"id": "40282_B237_2364e3",
|
||||||
|
"type": "locality"
|
||||||
|
},
|
||||||
|
"type": "Feature"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
9
spec/support/files/ban_address_search_no_result.json
Normal file
9
spec/support/files/ban_address_search_no_result.json
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"limit": 5,
|
||||||
|
"attribution": "BAN",
|
||||||
|
"version": "draft",
|
||||||
|
"licence": "ODbL 1.0",
|
||||||
|
"query": "Paris",
|
||||||
|
"type": "FeatureCollection",
|
||||||
|
"features": []
|
||||||
|
}
|
2451
vendor/assets/javascripts/typeahead.bundle.js
vendored
Normal file
2451
vendor/assets/javascripts/typeahead.bundle.js
vendored
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue