class Workout {
date = new Date();
id = (Date.now() + '').slice(-10);
constructor(coords, distance, duration) {
this.coords = coords; // [lat, lng]
this.distance = distance; // in km
this.duration = duration; // in min
}
_setDescription() {
const months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
this.description = `${this.type[0].toUppercase()}${this.type.slice(1)} on ${months[this.date.getMonth()]} ${this.date.getDate()}`;
}
}
class Running extends Workout {
type = 'running';
constructor(coords, distance, duration, cadance) {
super(coords, distance, duration);
this.cadance = cadance;
this.calcPace();
this._setDescription();
}
calcPace() {
// min/km
this.pace = this.duration / this.distance;
return this.pace;
}
}
class Cycling extends Workout {
type = 'cycling';
constructor(coords, distance, duration, elevationGain) {
super(coords, distance, duration);
this.elevationGain = elevationGain;
this.calcSpeed();
this._setDescription();
}
calcSpeed() {
// km/h
this.speed = this.distance / (this.duration / 60);
return this.speed;
}
}
const run1 = new Running([39, -12], 5.2, 24, 178);
const cycling1 = new Running([39, -12], 27, 95, 523);
// console.log(run1, cycling1);
// APPLICATION ARCHITECTURE
const form = document.querySelector('.form');
const containerWorkouts = document.querySelector('.workouts');
const inputType = document.querySelector('.form__input--type');
const inputDistance = document.querySelector('.form__input--distance');
const inputDuration = document.querySelector('.form__input--duration');
const inputCadence = document.querySelector('.form__input--cadence');
const inputElevation = document.querySelector('.form__input--elevation');
class App {
#map;
#mapEvent;
#workouts = [];
constructor() {
this._getPosition();
form.addEventListener('submit', this._newWorkout.bind(this));
// đổi option sẽ đổi nội dung 1 ô
inputType.addEventListener('change', this._toggleElevationField);
}
_getPosition() {
if (navigator.geolocation) // Check if support on old browser
navigator.geolocation.getCurrentPosition(this._loadMap.bind(this),
function () { // 2nd function triggered when fail to get location
alert('Could not get your position');
});
}
_loadMap(position) { // 1st function triggered when get location success
const { latitude } = position.coords;
const { longitude } = position.coords;
const coords = [latitude, longitude];
this.#map = L.map('map').setView(coords, 13);
L.tileLayer('http://{s}.google.com/vt/lyrs=m&x={x}&y={y}&z={z}', {
maxZoom: 20,
subdomains: ['mt0', 'mt1', 'mt2', 'mt3'],
}).addTo(this.#map);
// Handling click on map
this.#map.on('click', this._showForm.bind(this)); // Click vào 1 điểm sẽ trả về 1 Event
}
_showForm(mapE) {
this.#mapEvent = mapE;
console.log(mapE);
form.classList.remove('hidden');
inputDistance.focus(); // Đặt con trỏ vào form input này
}
_toggleElevationField() {
inputElevation.closest('.form__row').classList.toggle('form__row--hidden');
inputCadence.closest('.form__row').classList.toggle('form__row--hidden');
}
_newWorkout(e) {
const validInput = (...inputs) => inputs.every(inp => Number.isFinite(inp));
const allPositive = (...inputs) => inputs.every(inp => inp > 0);
e.preventDefault();
// Get data from form
const type = inputType.value;
const distance = +inputDistance.value;
const duration = +inputDuration.value;
const { lat, lng } = this.#mapEvent.latlng; // lấy thông tin toạ độ trong event đó
let workout;
// If activity is running, add running Object
if (type === 'running') {
const cadance = +inputCadence.value;
// Check if data is valid
if (!validInput(distance, duration, cadance) ||
!allPositive(distance, duration))
return alert('input has to be positive number!');
workout = new Running([lat, lng], distance, duration, cadance);
}
// If activity is cycling, add running Object
if (type === 'cycling') {
const elevation = +inputElevation.value;
// Check if data is valid
if (!validInput(distance, duration, elevation) ||
!allPositive(distance, duration))
return alert('input has to be positive number!');
workout = new Cycling([lat, lng], distance, duration, elevation);
}
// Add new Object to workout array
this.#workouts.push(workout);
console.log(workout);
// Render workout on map and marker
this._renderWorkoutMarker(workout);
// Render workout on list
this._renderWorkout(workout);
// Hide form + Clear input fields
inputDistance.value = inputCadence.value = inputDuration.value = inputElevation.value = '';
}
_renderWorkoutMarker(workout) {
L.marker(workout.coords).addTo(this.#map) // add marker
.bindPopup( // tạo popup vào Marker này
L.popup({ // Phần popup option
maxWidth: 250, // các option này tìm trong docs của Leaflet
minWidth: 100,
autoClose: false,
closeOnClick: false,
className: `${workout.type}-popup`,
})
)
.setPopupContent('workout') // Thay đổi nội dung cho popup
.openPopup();
}
_renderWorkout(workout) {
const html = `
<li class="workout workout--${workout.type}" data-id="${workout.id}">
<h2 class="workout__title">${workout.description}</h2>
<div class="workout__details">
<span class="workout__icon">${workout.type === 'running' ? '🏃♂️' : '🚴♀️'}</span>
<span class="workout__value">${workout.distance}</span>
<span class="workout__unit">km</span>
</div>
<div class="workout__details">
<span class="workout__icon">⏱</span>
<span class="workout__value">${workout.duration}</span>
<span class="workout__unit">min</span>
</div>
`
if (workout.type == 'running')
html += `
<div class="workout__details">
<span class="workout__icon">⚡️</span>
<span class="workout__value">${workout.pace.toFixed(1)}</span>
<span class="workout__unit">min/km</span>
</div>
<div class="workout__details">
<span class="workout__icon">🦶🏼</span>
<span class="workout__value">${workout.cadance}</span >
<span class="workout__unit">spm</span>
</div >
</li >
`;
if (workout.type == 'running')
html += `
<div class="workout__details">
<span class="workout__icon">⚡️</span>
<span class="workout__value">${workout.speed.toFixed(1)}</span>
<span class="workout__unit">km/h</span>
</div>
<div class="workout__details">
<span class="workout__icon">⛰</span>
<span class="workout__value">${workout.elevationGain}</span>
<span class="workout__unit">m</span>
</div>
</li>
`;
form.insertAdjacentHTML('afterend', html);
}
}
const app = new App();