/* exported MovingCostCalculator */
/* global AddressSearch, MovingVolumeCalculator, AjaxSender, google, Slickloader */
/**
* The AddressSearch class
* @external AddressSearch
* @see {@link https://zenoo.github.io/address-search/AddressSearch.html}
*/
/**
* The MovingVolumeCalculator class
* @external MovingVolumeCalculator
* @see {@link https://zenoo.github.io/moving-volume-calculator/MovingVolumeCalculator.html}
*/
/**
* The PlaceResult class from Google Places API
* @external PlaceResult
* @see {@link https://developers.google.com/maps/documentation/javascript/reference/3/places-service#PlaceResult}
*/
/**
* Translations object
* @typedef Lang
* @type {Object.<String, String>}
* @memberof MovingCostCalculator
* @example
* {
* title: 'Estimate your moving cost',
* addressesTitle: 'Your addresses'
* }
*/
/**
* Translations object list
* @typedef Dictionary
* @type {Object.<String, MovingCostCalculator.Lang>}
* @memberof MovingCostCalculator
* @example
* {
* en: {
* title: 'Estimate your moving cost',
* addressesTitle: 'Your addresses'
* },
* fr: {
* title: 'Estimez le coût de votre déménagement',
* addressesTitle: 'Vos adresses'
* }
* }
*/
/** MovingCostCalculator Class used to handle the MovingCostCalculator module */
class MovingCostCalculator{
/**
* Creates an instance of MovingCostCalculator
* and checks for invalid parameters
* @param {(Element|String)} target The wrapper for the MovingCostCalculator module
* @param {Object} [parameters] Additional optional parameters
* @param {String} [parameters.lang=en] The lang to use
* @param {Boolean} [parameters.debug=false] Show debugging logs ?
* @param {String} [parameters.remoteCalculator] URL to use for a remote calculation
* @param {String} [parameters.googleAPIKey] Your Google API key (Only needed if the Google API script isn't imported before)
* @param {Boolean} [parameters.askForContact] Should the calculator ask for a mail before giving the results ?
* @param {MovingCostCalculator.Dictionary} [parameters.dictionary] Adds custom translations to the dictionary
* @param {Object} [parameters.options] Options to enable/disable
* @param {Boolean} [parameters.options.floor=true] Ask for floor ?
* @param {Boolean} [parameters.options.lift=true] Ask for lift ?
* @param {Boolean} [parameters.options.porterageDistance=true] Ask for porterage distance ?
* @param {MovingVolumeCalculator.Rooms} [parameters.options.rooms] Custom rooms
*/
constructor(target, parameters){
/**
* Links to available elements
* @property {Element} wrapper The highest module element
* @property {Object} departure Holder for the departure elements
* @property {AddressSearch} departure.address Departure address AddressSearch
* @property {Element} departure.optionToggler Departure address options toggler element
* @property {Object} departure.options Departure address options holder
* @property {Element} departure.options.floor Departure address floor element
* @property {Element} departure.options.lift Departure address lift element
* @property {Element} departure.options.porterageDistance Departure address porterage distance element
* @property {Object} arrival Holder for the arrival elements
* @property {AddressSearch} arrival.address Arrival address AddressSearch
* @property {Element} arrival.optionToggler Arrival address options toggler element
* @property {Object} arrival.options Arrival address options holder
* @property {Element} arrival.options.floor Arrival address floor element
* @property {Element} arrival.options.lift Arrival address lift element
* @property {Element} arrival.options.porterageDistance Arrival address porterage distance element
* @property {MovingVolumeCalculator} volume volume MovingVolumeCalculator
* @property {Element} contact Contact element
* @property {Slickloader} loader Loader element
* @property {Object} validation Holder for the validation buttons
* @property {Object} validation.addresses Addresses validation
* @property {Object} validation.contact Contact validation
* @private
*/
this._elements = {
wrapper: null,
departure: {
address: null,
optionToggler: null,
options: {
floor: null,
lift: null,
porterageDistance: null
}
},
arrival: {
address: null,
optionToggler: null,
options: {
floor: null,
lift: null,
porterageDistance: null
}
},
volume: null,
contact: null,
loader: null,
validation: {
addresses: null,
contact: null
}
};
this._elements.wrapper = target instanceof Element ? target : document.querySelector(target);
//Errors checking
if(!this._elements.wrapper) throw new Error('MovingCostCalculator: '+(typeof target == 'string' ? 'The selector `'+target+'` didn\'t match any element.' : 'The element you provided was undefined'));
if(this._elements.wrapper.classList.contains('mcc-wrapper')) throw new Error('MovingCostCalculator: The element has already been initialized.');
/** @private */
this._parameters = {
lang: 'en',
debug: false,
askForContact: true,
...parameters,
options: {
floor: true,
lift: true,
porterageDistance: true,
...parameters.options
}
};
/**
* The calculator's available data
* @property {Object} addresses Addresses data
* @property {Object} addresses.departure Departure address data
* @property {external:PlaceResult} addresses.departure.value Address value
* @property {Object} addresses.departure.components Address components
* @property {String} addresses.departure.components.streetNumber Address' street number
* @property {String} addresses.departure.components.route Address' route
* @property {String} addresses.departure.components.zipCode Address' zip code
* @property {String} addresses.departure.components.locality Address' locality
* @property {String} addresses.departure.components.country Address' country
* @property {String} addresses.departure.components.countryCode Address' country code
* @property {String} addresses.departure.components.department Address' department
* @property {String} addresses.departure.components.region Address' region
* @property {Number} addresses.departure.location.lat Address' latitude
* @property {Number} addresses.departure.location.lng Address' longitude
* @property {String} addresses.departure.location.placeId Address' place ID
* @property {Object} addresses.departure.options Arrival address options
* @property {Number} addresses.departure.options.floor Address' floor
* @property {Boolean} addresses.departure.options.lift Does the address have a lift ?
* @property {Number} addresses.departure.options.porterageDistance The porterage distance approximate
* @property {Object} addresses.arrival Arrival address data
* @property {external:PlaceResult} addresses.arrival.value Address value
* @property {Object} addresses.arrival.components Address components
* @property {String} addresses.arrival.components.streetNumber Address' street number
* @property {String} addresses.arrival.components.route Address' route
* @property {String} addresses.arrival.components.zipCode Address' zip code
* @property {String} addresses.arrival.components.locality Address' locality
* @property {String} addresses.arrival.components.country Address' country
* @property {String} addresses.arrival.components.countryCode Address' country code
* @property {String} addresses.arrival.components.department Address' department
* @property {String} addresses.arrival.components.region Address' region
* @property {Number} addresses.arrival.location.lat Address' latitude
* @property {Number} addresses.arrival.location.lng Address' longitude
* @property {String} addresses.arrival.location.placeId Address' place ID
* @property {Object} addresses.arrival.options Departure address options
* @property {Number} addresses.arrival.options.floor Address' floor
* @property {Boolean} addresses.arrival.options.lift Does the address have a lift ?
* @property {Number} addresses.arrival.options.porterageDistance The porterage distance approximate
* @property {Number} volume The volume
* @property {Number} volumeData The additional volume data
* @property {String} contact The user's mail
* @example
* {
* addresses: {
* departure: {
* value: '',
* components: {
* streetNumber: '',
* route: '',
* zipCode: '',
* locality: '',
* country: '',
* countryCode: '',
* department: '',
* region: ''
* },
* location:{
* lat: 0,
* lng: 0,
* placeId: ''
* },
* options: {
* floor: 0,
* lift: false,
* porterageDistance: 0
* }
* },
* arrival: {
* value: '',
* components: {
* streetNumber: '',
* route: '',
* zipCode: '',
* locality: '',
* country: '',
* countryCode: '',
* department: '',
* region: ''
* },
* location:{
* lat: 0,
* lng: 0
* placeId: ''
* },
* options: {
* floor: 0,
* lift: false,
* porterageDistance: 0
* }
* }
* },
* volume: 0,
* volumeData: {},
* contact: ''
* };
*/
this.data = {
addresses: {
departure: {
value: '',
components: {
streetNumber: '',
route: '',
zipCode: '',
locality: '',
country: '',
countryCode: '',
department: '',
region: ''
},
location: {
lat: 0,
lng: 0,
placeId: ''
},
options: {
floor: 0,
lift: false,
porterageDistance: 0
}
},
arrival: {
value: '',
components: {
streetNumber: '',
route: '',
zipCode: '',
locality: '',
country: '',
countryCode: '',
department: '',
region: ''
},
location: {
lat: 0,
lng: 0,
placeId: ''
},
options: {
floor: 0,
lift: false,
porterageDistance: 0
}
}
},
volume: 0,
volumeData: {},
contact: ''
};
this._loadDictionary();
this._loadDependencies().then(() => {
if(this._parameters.debug) console.log('MovingCostCalculator: DEPENDENCIES LOADED !');
this._build();
this._listen();
});
}
/**
* Loads the dependencies
* @returns {Promise} A Promise that resolves when all the dependencies are found or loaded
* @private
*/
_loadDependencies(){
if(this._parameters.debug) console.log('MovingCostCalculator: LOADING DEPENDENCIES ...');
// MovingVolumeCalculator
const movingVolumeCalculatorDependency = new Promise(solve => {
if(typeof MovingVolumeCalculator == 'function'){
solve();
}else{
const movingVolumeCalculatorScript = new Promise(resolve => {
this._loadResource('script', 'https://unpkg.com/moving-volume-calculator@0.4.2/MovingVolumeCalculator.min.js', () => {
if(this._parameters.debug) console.log('DEPENDENCIES: MovingVolumeCalculator script LOADED !');
resolve();
});
});
const movingVolumeCalculatorStyle = new Promise(resolve => {
this._loadResource('style', 'https://unpkg.com/moving-volume-calculator@0.4.2/MovingVolumeCalculator.min.css', () => {
if(this._parameters.debug) console.log('DEPENDENCIES: MovingVolumeCalculator style LOADED !');
resolve();
});
});
Promise.all([movingVolumeCalculatorScript, movingVolumeCalculatorStyle]).then(() => {
solve();
});
}
});
// Google Maps API
const googleMapsAPIDependency = new Promise(solve => {
if(window.google && window.google.maps){
solve();
}else{
//Remove old script if it was here
const gMapScripts = document.querySelectorAll('script[src^="https://maps.googleapis.com"]');
if(gMapScripts.length){
gMapScripts.forEach(gMapScript => {
gMapScript.remove();
});
if(google) Reflect.deleteProperty(google, 'maps');
}
//Generate new Google Maps API script
const newAPI = document.createElement('script');
if(!this._parameters.googleAPIKey){
throw new Error('MovingCostCalculator: You didn\'t provide your Google Maps API key. Please either pass it via the options\' googleAPIKey attribute OR import the Google Maps API script on your own.');
}
newAPI.src = 'https://maps.googleapis.com/maps/api/js?libraries=places&key='+this._parameters.googleAPIKey+'&language='+this._parameters.lang+'&callback=__mccGmapApiLoader';
//Callback for the Google Maps API src
window.__mccGmapApiLoader = () => {
if(this._parameters.debug) console.log('DEPENDENCIES: Google Maps API script LOADED !');
solve();
};
//Start the script
document.querySelector('head').appendChild(newAPI);
}
});
// AddressSearch
const addressSearchDependency = new Promise(solve => {
if(typeof AddressSearch == 'function'){
solve();
}else{
const addressSearchScript = new Promise(resolve => {
this._loadResource('script', 'https://unpkg.com/address-searcher@1.11.1/address-search.min.js', () => {
if(this._parameters.debug) console.log('DEPENDENCIES: AddressSearch script LOADED !');
resolve();
});
});
const addressSearchStyle = new Promise(resolve => {
this._loadResource('style', 'https://unpkg.com/address-searcher@1.11.1/address-search.min.css', () => {
if(this._parameters.debug) console.log('DEPENDENCIES: AddressSearch style LOADED !');
resolve();
});
});
Promise.all([addressSearchScript, addressSearchStyle]).then(() => {
solve();
});
}
});
// AjaxSender
const ajaxSenderDependency = new Promise(solve => {
if(typeof AjaxSender == 'function'){
solve();
}else{
const ajaxSenderScript = new Promise(resolve => {
this._loadResource('script', 'https://unpkg.com/ajax-sender@1.2.2/AjaxSender.min.js', () => {
if(this._parameters.debug) console.log('DEPENDENCIES: AjaxSender script LOADED !');
resolve();
});
});
ajaxSenderScript.then(() => {
solve();
});
}
});
// AddressSearch
const slickLoaderDependency = new Promise(solve => {
if(typeof SlickLoader == 'object'){
solve();
}else{
const slickLoaderScript = new Promise(resolve => {
this._loadResource('script', 'https://unpkg.com/slick-loader@1.1.24/slick-loader.min.js', () => {
if(this._parameters.debug) console.log('DEPENDENCIES: SlickLoader script LOADED !');
resolve();
});
});
const slickLoaderStyle = new Promise(resolve => {
this._loadResource('style', 'https://unpkg.com/slick-loader@1.1.24/slick-loader.min.css', () => {
if(this._parameters.debug) console.log('DEPENDENCIES: SlickLoader style LOADED !');
resolve();
});
});
Promise.all([slickLoaderScript, slickLoaderStyle]).then(() => {
solve();
});
}
});
return Promise.all([movingVolumeCalculatorDependency, googleMapsAPIDependency, addressSearchDependency, ajaxSenderDependency, slickLoaderDependency]);
}
/**
* Loads a resource
* @param {String} type
* @param {String} url
* @param {Function} callback
* @private
*/
_loadResource(type, url, callback){
const [head] = document.getElementsByTagName('head');
if(type == 'script'){
const script = document.createElement('script');
script.src = url;
script.onload = callback;
head.appendChild(script);
}else{
const link = document.createElement('link');
link.href = url;
link.rel = 'stylesheet';
link.type = 'text/css';
link.onload = callback;
head.appendChild(link);
}
}
/**
* Loads the dictionary
* @private
*/
_loadDictionary(){
/** @private */
this._dictionary = {
en: {
title: 'Estimate your moving cost',
addressesTitle: 'Your addresses',
volumeTitle: 'Your volume',
estimationsTitle: 'Estimations',
departureAddress: 'Departure address',
arrivalAddress: 'Arrival address',
moreAddressOptions: 'More options',
lessAddressOptions: 'Less options',
floor: 'Floor',
lift: 'Lift',
porterageDistance: 'Porterage Distance',
yes: 'Yes',
no: 'No',
enterContact: 'Please enter your mail to access your estimations',
loading: 'Loading ...',
next: 'Next',
priceFrom: 'From',
services: 'Services'
},
fr: {
title: 'Estimez le coût de votre déménagement',
addressesTitle: 'Vos addresses',
volumeTitle: 'Votre volume',
estimationsTitle: 'Estimations',
departureAddress: 'Adresse de départ',
arrivalAddress: 'Adresse d\'arrivée',
moreAddressOptions: 'Plus d\'options',
lessAddressOptions: 'Moins d\'options',
floor: 'Etage',
lift: 'Ascenseur',
porterageDistance: 'Distance de portage',
yes: 'Oui',
no: 'Non',
enterContact: 'Veuillez renseigner votre adresse mail pour accéder à vos estimations',
loading: 'Chargement ...',
next: 'Suivant',
priceFrom: 'A partir de',
services: 'Services'
}
};
// Add custom translations
this._dictionary = Object.assign(this._dictionary, this._parameters.dictionary || {});
}
/**
* Builds the MovingVolumeCalculator DOM Tree inside the element
* @private
*/
_build(){
this._elements.wrapper.classList.add('mcc-wrapper', 'mcc-stylized');
/*
* Title
*/
const title = document.createElement('h3');
title.innerHTML = this._translated().title;
this._elements.wrapper.appendChild(title);
/*
* Addresses
*/
this._buildAddresses();
/*
* Volume
*/
this._buildVolume();
/*
* Contact
*/
if(this._parameters.askForContact) this._buildContact();
/*
* Loader
*/
this._buildLoader();
/*
* Estimations
*/
this._buildEstimations();
}
/**
* Builds the addresses module
* @private
*/
_buildAddresses(){
let title = document.createElement('h4'),
section = document.createElement('section'),
p = document.createElement('p'),
input = document.createElement('input'),
div = document.createElement('div');
section = document.createElement('section');
section.classList.add('mcc-addresses');
this._elements.wrapper.appendChild(section);
title = document.createElement('h4');
title.innerHTML = this._translated().addressesTitle;
section.appendChild(title);
const addressesWrapper = document.createElement('div');
section.appendChild(addressesWrapper);
addressesWrapper.appendChild(div);
// Departure
p.innerText = this._translated().departureAddress;
div.appendChild(p);
input.classList.add('mcc-address', 'mcc-departure');
div.appendChild(input);
this._elements.departure.address = new AddressSearch(input);
// Departure options toggler
if(Object.values(this._parameters.options).some(option => option == true)){
p = document.createElement('p');
p.classList.add('mcc-address-options-toggler');
div.appendChild(p);
this._elements.departure.optionToggler = document.createElement('a');
this._elements.departure.optionToggler.classList.add('mcc-address-options-enable');
this._elements.departure.optionToggler.innerText = this._translated().moreAddressOptions;
this._elements.departure.optionToggler.href = '';
p.appendChild(this._elements.departure.optionToggler);
// Departure options
const options = document.createElement('div');
options.classList.add('mcc-address-options', 'mcc-departure', 'mcc-hidden');
div.appendChild(options);
this._buildAddressOptions(this._elements.departure.options, options);
}
div = document.createElement('div');
addressesWrapper.appendChild(div);
// Arrival
p = document.createElement('p');
p.innerText = this._translated().arrivalAddress;
div.appendChild(p);
input = document.createElement('input');
input.classList.add('mcc-address', 'mcc-arrival');
div.appendChild(input);
this._elements.arrival.address = new AddressSearch(input);
// Arrival options toggler
if(Object.values(this._parameters.options).some(option => option == true)){
p = document.createElement('p');
p.classList.add('mcc-address-options-toggler');
div.appendChild(p);
this._elements.arrival.optionToggler = document.createElement('a');
this._elements.arrival.optionToggler.classList.add('mcc-address-options-enable');
this._elements.arrival.optionToggler.innerText = this._translated().moreAddressOptions;
this._elements.arrival.optionToggler.href = '';
p.appendChild(this._elements.arrival.optionToggler);
// Arrival options
const options = document.createElement('div');
options.classList.add('mcc-address-options', 'mcc-arrival', 'mcc-hidden');
div.appendChild(options);
this._buildAddressOptions(this._elements.arrival.options, options);
}
// Validation
p = document.createElement('p');
p.classList.add('mcc-validation');
this._elements.validation.addresses = document.createElement('button');
this._elements.validation.addresses.disabled = true;
this._elements.validation.addresses.innerHTML = this._translated().next;
p.appendChild(this._elements.validation.addresses);
section.appendChild(p);
}
/**
* Builds the address' options
* @param {Object} options
* @param {Element} holder
* @private
*/
_buildAddressOptions(options, holder){
Object.keys(options).forEach(option => {
if(this._parameters.options[option]){
const p = document.createElement('p'),
span = document.createElement('span');
p.classList.add('mcc-address-option-'+option);
if(['lift'].includes(option)){
options[option] = document.createElement('select');
let optionTag = document.createElement('option');
optionTag.value = 'false';
optionTag.innerText = this._translated().no;
options[option].appendChild(optionTag);
optionTag = document.createElement('option');
optionTag.value = 'true';
optionTag.innerText = this._translated().yes;
options[option].appendChild(optionTag);
}else{
options[option] = document.createElement('input');
if(option == 'porterageDistance'){
options[option].setAttribute('placeholder', '0 m');
}
}
span.innerText = this._translated()[option];
p.appendChild(span);
p.appendChild(options[option]);
holder.appendChild(p);
}
});
}
/**
* Builds the volume module
* @private
*/
_buildVolume(){
const title = document.createElement('h4'),
section = document.createElement('section'),
div = document.createElement('div');
section.classList.add('mcc-volume', 'mcc-hidden');
this._elements.wrapper.appendChild(section);
title.innerHTML = this._translated().volumeTitle;
section.appendChild(title);
section.appendChild(div);
this._elements.volume = new MovingVolumeCalculator(div, {
lang: this._parameters.lang,
rooms: this._parameters.options.rooms
});
}
/**
* Builds the contact module
* @private
*/
_buildContact(){
if(this._parameters.askForContact){
const title = document.createElement('h4'),
section = document.createElement('section'),
p = document.createElement('p');
section.classList.add('mcc-contact', 'mcc-hidden');
this._elements.wrapper.appendChild(section);
title.innerHTML = this._translated().enterContact;
section.appendChild(title);
this._elements.contact = document.createElement('input');
section.appendChild(this._elements.contact);
// Validation
p.classList.add('mcc-validation');
this._elements.validation.contact = document.createElement('button');
this._elements.validation.contact.disabled = true;
this._elements.validation.contact.innerHTML = this._translated().next;
p.appendChild(this._elements.validation.contact);
section.appendChild(p);
}
}
/**
* Builds the loader module
* @private
*/
_buildLoader(){
const section = document.createElement('section');
section.classList.add('mcc-loader', 'mcc-hidden');
this._elements.wrapper.appendChild(section);
}
/**
* Builds the estimations module
* @private
*/
_buildEstimations(){
let title = document.createElement('title'),
section = document.createElement('section');
section = document.createElement('section');
section.classList.add('mcc-estimations', 'mcc-hidden');
this._elements.wrapper.appendChild(section);
title = document.createElement('h4');
title.innerHTML = this._translated().estimationsTitle;
section.appendChild(title);
}
/**
* Creates event listeners
* @private
*/
_listen(){
/*
* Departure address
*/
this._createDepartureAddressListeners();
/*
* Arrival address
*/
this._createArrivalAddressListeners();
/**
* Addresses validation
*/
this._elements.validation.addresses.addEventListener('click', () => {
this._elements.wrapper.querySelector('section.mcc-volume').classList.remove('mcc-hidden');
this._elements.wrapper.querySelector('section.mcc-addresses').classList.add('mcc-hidden');
});
/*
* Volume
*/
this._elements.volume.onChange(value => {
if(this._elements.volume.isValid()){
this.data.volume = value;
this.data.volumeData = this._elements.volume.data;
}
}).onValidate(data => {
this.data.volume = this._elements.volume.volume;
this.data.volumeData = data;
// Go to next step
if(this._parameters.askForContact){
this._elements.wrapper.querySelector('section.mcc-contact').classList.remove('mcc-hidden');
this._elements.wrapper.querySelector('section.mcc-volume').classList.add('mcc-hidden');
}else{
this._toggleLoader(true);
this._elements.wrapper.querySelector('section.mcc-loader').classList.remove('mcc-hidden');
this._elements.wrapper.querySelector('section.mcc-volume').classList.add('mcc-hidden');
this.validate();
}
});
/*
* Contact
*/
if(this._parameters.askForContact){
this._elements.contact.addEventListener('input', () => {
const emailValidator = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
if(emailValidator.test(this._elements.contact.value)){
this.data.contact = this._elements.contact.value;
// Enable next step
this._elements.validation.contact.disabled = false;
}else{
// Disable next step
this._elements.validation.contact.disabled = true;
}
});
this._elements.contact.addEventListener('keyup', e => {
if(e.key == 'Enter'){
this._toggleLoader(true);
this._elements.wrapper.querySelector('section.mcc-loader').classList.remove('mcc-hidden');
this._elements.wrapper.querySelector('section.mcc-contact').classList.add('mcc-hidden');
this.validate();
}
});
/**
* Contact validation
*/
this._elements.validation.contact.addEventListener('click', () => {
this._toggleLoader(true);
this._elements.wrapper.querySelector('section.mcc-loader').classList.remove('mcc-hidden');
this._elements.wrapper.querySelector('section.mcc-contact').classList.add('mcc-hidden');
this.validate();
});
}
}
/**
* Creates the departure address' listeners
* @private
*/
_createDepartureAddressListeners(){
// Address
this._elements.departure.address.onSelect(address => {
this.data.addresses.departure.value = address.formatted_address;
this.data.addresses.departure.components.streetNumber = this._getAddressComponent(address, 'street_number');
this.data.addresses.departure.components.route = this._getAddressComponent(address, 'route');
this.data.addresses.departure.components.zipCode = this._getAddressComponent(address, 'postal_code');
this.data.addresses.departure.components.locality = this._getAddressComponent(address, 'locality');
this.data.addresses.departure.components.country = this._getAddressComponent(address, 'country');
this.data.addresses.departure.components.countryCode = this._getAddressComponent(address, 'country', true);
this.data.addresses.departure.components.department = this._getAddressComponent(address, 'administrative_area_level_2');
this.data.addresses.departure.components.region = this._getAddressComponent(address, 'administrative_area_level_1');
this.data.addresses.departure.location.lat = address.geometry.location.lat();
this.data.addresses.departure.location.lng = address.geometry.location.lng();
this.data.addresses.departure.location.placeId = address.place_id;
// Enable next step if both addresses are filled
if(this._elements.departure.address.value.formatted_address && this._elements.arrival.address.value.formatted_address){
this._elements.validation.addresses.disabled = false;
}else{
this._elements.validation.addresses.disabled = true;
}
});
// Address options toggler
if(Object.values(this._parameters.options).some(option => option == true)){
this._elements.departure.optionToggler.addEventListener('click', e => {
e.preventDefault();
if(this._elements.departure.optionToggler.classList.contains('mcc-address-options-enable')){
this._elements.departure.optionToggler.classList.remove('mcc-address-options-enable');
this._elements.departure.optionToggler.classList.add('mcc-address-options-disable');
this._elements.departure.optionToggler.innerText = this._translated().lessAddressOptions;
this._elements.wrapper.querySelector('.mcc-departure.mcc-address-options').classList.remove('mcc-hidden');
}else{
this._elements.departure.optionToggler.classList.remove('mcc-address-options-disable');
this._elements.departure.optionToggler.classList.add('mcc-address-options-enable');
this._elements.departure.optionToggler.innerText = this._translated().moreAddressOptions;
this._elements.wrapper.querySelector('.mcc-departure.mcc-address-options').classList.add('mcc-hidden');
}
});
// Address options
if(this._parameters.options.floor){
this._elements.departure.options.floor.addEventListener('change', () => {
this.data.addresses.departure.options.floor = +this._elements.departure.options.floor.value;
});
}
if(this._parameters.options.lift){
this._elements.departure.options.lift.addEventListener('change', () => {
this.data.addresses.departure.options.lift = this._elements.departure.options.lift.value == 'true';
});
}
if(this._parameters.options.porterageDistance){
this._elements.departure.options.porterageDistance.addEventListener('change', () => {
this.data.addresses.departure.options.porterageDistance = +this._elements.departure.options.porterageDistance.value;
});
}
}
}
/**
* Creates the arrival address' listeners
* @private
*/
_createArrivalAddressListeners(){
this._elements.arrival.address.onSelect(address => {
// Address
this.data.addresses.arrival.value = address.formatted_address;
this.data.addresses.arrival.components.streetNumber = this._getAddressComponent(address, 'street_number');
this.data.addresses.arrival.components.route = this._getAddressComponent(address, 'route');
this.data.addresses.arrival.components.zipCode = this._getAddressComponent(address, 'postal_code');
this.data.addresses.arrival.components.locality = this._getAddressComponent(address, 'locality');
this.data.addresses.arrival.components.country = this._getAddressComponent(address, 'country');
this.data.addresses.arrival.components.countryCode = this._getAddressComponent(address, 'country', true);
this.data.addresses.arrival.components.department = this._getAddressComponent(address, 'administrative_area_level_2');
this.data.addresses.arrival.components.region = this._getAddressComponent(address, 'administrative_area_level_1');
this.data.addresses.arrival.location.lat = address.geometry.location.lat();
this.data.addresses.arrival.location.lng = address.geometry.location.lng();
this.data.addresses.arrival.location.placeId = address.place_id;
// Enable next step if both addresses are filled
if(this._elements.departure.address.value.formatted_address && this._elements.arrival.address.value.formatted_address){
this._elements.validation.addresses.disabled = false;
}else{
this._elements.validation.addresses.disabled = true;
}
});
// Address options toggler
if(Object.values(this._parameters.options).some(option => option == true)){
this._elements.arrival.optionToggler.addEventListener('click', e => {
e.preventDefault();
if(this._elements.arrival.optionToggler.classList.contains('mcc-address-options-enable')){
this._elements.arrival.optionToggler.classList.remove('mcc-address-options-enable');
this._elements.arrival.optionToggler.classList.add('mcc-address-options-disable');
this._elements.arrival.optionToggler.innerText = this._translated().lessAddressOptions;
this._elements.wrapper.querySelector('.mcc-arrival.mcc-address-options').classList.remove('mcc-hidden');
}else{
this._elements.arrival.optionToggler.classList.remove('mcc-address-options-disable');
this._elements.arrival.optionToggler.classList.add('mcc-address-options-enable');
this._elements.arrival.optionToggler.innerText = this._translated().moreAddressOptions;
this._elements.wrapper.querySelector('.mcc-arrival.mcc-address-options').classList.add('mcc-hidden');
}
});
// Address options
if(this._parameters.options.floor){
this._elements.arrival.options.floor.addEventListener('change', () => {
this.data.addresses.arrival.options.floor = +this._elements.arrival.options.floor.value;
});
}
if(this._parameters.options.lift){
this._elements.arrival.options.lift.addEventListener('change', () => {
this.data.addresses.arrival.options.lift = this._elements.arrival.options.lift.value == 'true';
});
}
if(this._parameters.options.porterageDistance){
this._elements.arrival.options.porterageDistance.addEventListener('change', () => {
this.data.addresses.arrival.options.porterageDistance = +this._elements.arrival.options.porterageDistance.value;
});
}
}
}
/**
* Toggles the estimations loader
* @param {Boolean?} force Force the display ?
*/
_toggleLoader(force){
if(!this._elements.loader){
this._elements.loader = new Slickloader(this._elements.wrapper.querySelector('.mcc-loader'));
}
const toToggle = typeof force === 'undefined' ? !this._elements.loader.element.classList.contains('active') : force;
if(toToggle) this._elements.loader.enable();
else this._elements.loader.disable();
}
/**
* Returns the dictionnary for the current lang
* @returns {Object} The dictionnary for the current lang
* @private
*/
_translated(){
return this._dictionary[this._parameters.lang];
}
/**
* Get a component for a given address
* @param {external:PlaceResult} address The address to search in
* @param {String} component The component's name
* @param {Boolean} isShort Get the short name ?
* @private
*/
_getAddressComponent(address, component, isShort){
const target = address.address_components.find(c => c.types.includes(component));
return target ? isShort ? target.short_name : target.long_name : '';
}
/**
* Sets the lang
* @param {String} lang The lang to set
* @returns {MovingCostCalculator} The current MovingCostCalculator
*/
setLang(lang){
this._parameters.lang = lang || 'en';
return this;
}
/**
* Validates the calculator data & calculates the estimations
* @returns {MovingCostCalculator} The current MovingCostCalculator
*/
validate(){
if(this._parameters.remoteCalculator){
new AjaxSender(this._parameters.remoteCalculator, {
headers: {
'Content-Type': 'application/json'
},
data: this.data,
method: 'POST',
responseType: 'json',
load: response => {
this._elements.wrapper.querySelector('section.mcc-estimations').innerHTML = `
<ul>
${response.map(offer => `<li>
<p class="mcc-offer-name">${offer.name}</p>
<p>${this._translated().priceFrom} <span class="mcc-offer-price">${offer.price} €</span></p>
<p>${this._translated().services}:</p>
${offer.services.map(service => `
<p class="mcc-offer-service">${service}</p>
`).join('')}
</li>`).join('')}
</ul>
`;
// TEMPORARY FOR DEBUGGING
this._elements.wrapper.querySelector('section.mcc-estimations').innerHTML += `
<div class="mcc-logs">${response[0].logs.map(log => `${log}<br />`).join('')}</div>
`;
const keyListener = e => {
if(e.keyCode == 76){
this._elements.wrapper.querySelector('.mcc-logs').classList.toggle('debug');
}
};
window.removeEventListener('keyup', keyListener);
window.addEventListener('keyup', keyListener);
// END OF TEMPORARY
// Do stuff with the estimations here
this._elements.wrapper.querySelector('section.mcc-loader').classList.add('mcc-hidden');
this._elements.wrapper.querySelector('section.mcc-estimations').classList.remove('mcc-hidden');
},
error: error => {
console.log(error);
console.log(error.response);
}
});
}else{
// Calculate data locally here
this._elements.wrapper.querySelector('section.mcc-loader').classList.add('mcc-hidden');
this._elements.wrapper.querySelector('section.mcc-estimations').classList.remove('mcc-hidden');
}
return this;
}
/**
* Removes any MovingCostCalculator mutation from the DOM
*/
destroy(){
this._elements.wrapper.innerHTML = '';
this._elements.wrapper.classList.remove('mcc-wrapper');
}
/**
* Removes any MovingCostCalculator mutation from the DOM
* @param {String} selector The MovingCostCalculator wrapper selector
* @static
*/
static destroy(selector){
const element = document.querySelector(selector);
if(element && element.classList.contains('mcc-wrapper')){
element.innerHTML = '';
element.classList.remove('mcc-wrapper');
}
}
}