123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371 |
- 'use strict';
- 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');
- const btnEdit = document.querySelector('.btn__edit');
- const btnRemoveAll = document.querySelector('.remover');
- const btnSort = document.querySelector('.btn--sort');
- class App {
- #map;
- #mapZoomLevel = 13;
- #mapEvent;
- #workouts = [];
- #sorted = false;
- constructor() {
- // Get user's position
- this._getPosition();
- // Get data from local storage
- this._getLocalStorage();
- // Attach event handlers
- form.addEventListener('submit', this._newWorkout.bind(this));
- inputType.addEventListener('change', this._toggleElevationField);
- containerWorkouts.addEventListener('click', this._moveToPopup.bind(this));
- containerWorkouts.addEventListener('click', this._editWorkout.bind(this));
- containerWorkouts.addEventListener('click', this._removeWorkout.bind(this));
- btnRemoveAll.addEventListener('click', this._removeAllWorkouts.bind(this));
- if (this.#workouts.length > 0) btnRemoveAll.classList.remove('btn--hidden');
- btnSort.addEventListener('click', this._sort.bind(this));
- }
- _getPosition() {
- if (navigator.geolocation)
- navigator.geolocation.getCurrentPosition(
- this._loadMap.bind(this),
- function () {
- alert(`Could not get your position`);
- }
- );
- }
- _loadMap(position) {
- const { latitude } = position.coords;
- const { longitude } = position.coords;
- const coords = [latitude, longitude];
- this.#map = L.map('map').setView(coords, this.#mapZoomLevel);
- L.tileLayer('https://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png', {
- attribution: `© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>
- contributors`,
- }).addTo(this.#map);
- this.#map.on('click', this._showForm.bind(this));
- this.#workouts.forEach(work => this._renderWorkoutMarker(work));
- }
- _showForm(mapE) {
- this.#mapEvent = mapE;
- form.classList.remove('hidden');
- inputDistance.focus();
- }
- _hideForm() {
- // Empty inputs
- inputCadence.value =
- inputDistance.value =
- inputDuration.value =
- inputElevation.value =
- '';
- form.style.display = 'none';
- form.classList.add('hidden');
- setTimeout(() => (form.style.display = 'grid'));
- }
- _toggleElevationField() {
- inputCadence.closest('.form__row').classList.toggle('form__row--hidden');
- inputElevation.closest('.form__row').classList.toggle('form__row--hidden');
- }
- _newWorkout(e) {
- const validInputs = (...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;
- let workout;
- // If workout running, create running object
- if (type === 'running') {
- const cadence = +inputCadence.value;
- // Check if data is valid
- if (
- !validInputs(distance, duration, cadence) ||
- !allPositive(distance, duration, cadence)
- )
- return alert('Inputs have to be positive numbers!');
- workout = new Running([lat, lng], distance, duration, cadence);
- }
- // If workout cycling, create cycling object
- if (type === 'cycling') {
- const elevation = +inputElevation.value;
- if (
- !validInputs(distance, duration, elevation) ||
- !allPositive(distance, duration)
- )
- return alert('Inputs have to be positive numbers!');
- workout = new Cycling([lat, lng], distance, duration, elevation);
- }
- // Add new object to array
- this.#workouts.push(workout);
- // Rednder workout on map as marker
- this._renderWorkoutMarker(workout);
- // Render workout on list
- this._renderWorkout(workout);
- // Clear input fields and hide form
- this._hideForm();
- // Set local storage to all workouts
- this._setLocalStorage();
- btnRemoveAll.classList.remove('btn--hidden');
- }
- _renderWorkoutMarker(workout) {
- L.marker(workout.coords)
- .addTo(this.#map)
- .bindPopup(
- L.popup({
- maxWidth: 250,
- minWidth: 100,
- autoClose: false,
- closeOnClick: false,
- className: `${workout.type}-popup`,
- })
- )
- .setPopupContent(
- `${workout.type === 'running' ? '🏃♂️' : '🚴♂️'} ${workout.description}`
- )
- .openPopup();
- }
- _renderWorkout(workout) {
- let 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.cadence}</span>
- <span class="workout__unit">spm</span>
- </div>
- `;
- if (workout.type === 'cycling')
- 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>
- `;
- html += `
- <div class="buttons">
- <button class="btn__edit">🖊</button>
- <button class="btn__submit btn--hidden">✔</button>
- <button class="btn__remove">✖</button>
- </div>
- </li>
- `;
- form.insertAdjacentHTML('afterend', html);
- }
- _moveToPopup(e) {
- const workoutEl = e.target.closest('.workout');
- if (!workoutEl) return;
- const workout = this.#workouts.find(
- work => work.id === workoutEl.dataset.id
- );
- this.#map.setView(workout.coords, this.#mapZoomLevel, {
- animate: true,
- pan: {
- duration: 1,
- },
- });
- }
- _setLocalStorage() {
- localStorage.setItem('workouts', JSON.stringify(this.#workouts));
- }
- _getLocalStorage(sorted) {
- const data = JSON.parse(localStorage.getItem('workouts'));
- if (!data) return;
- this.#workouts = data;
- while (form.nextSibling) {
- const htmlEl = form.nextSibling;
- htmlEl.remove();
- }
- const works = sorted
- ? this.#workouts.slice().sort((a, b) => a.distance - b.distance)
- : this.#workouts;
- works.forEach(work => this._renderWorkout(work));
- }
- reset() {
- localStorage.removeItem('workouts');
- location.reload();
- }
- _removeAllWorkouts() {
- while (form.nextSibling) {
- const htmlEl = form.nextSibling;
- htmlEl.remove();
- }
- btnRemoveAll.classList.add('btn--hidden');
- this.reset();
- }
- _editWorkout(e) {
- const btn = e.target;
- if (!btn.classList.contains('btn__edit')) return;
- const workoutEl = btn.closest('.workout');
- const workout = this.#workouts.find(
- work => work.id === workoutEl.dataset.id
- );
- console.log(workout);
- inputDistance.value = workout.distance;
- inputDuration.value = workout.duration;
- workout.type == 'running'
- ? (inputCadence.value = workout.cadence)
- : (inputElevation.value = workout.elevation);
- this._showForm();
- }
- _removeWorkout(e) {
- if (!e.target.classList.contains('btn__remove')) return;
- const workoutEl = e.target.closest('.workout');
- console.log(workoutEl);
- const workout = this.#workouts.find(
- work => work.id === workoutEl.dataset.id
- );
- this.#workouts.splice(this.#workouts.indexOf(workout), 1);
- localStorage.removeItem('workouts');
- workoutEl.remove();
- localStorage.removeItem('workouts');
- this._setLocalStorage();
- location.reload();
- }
- _sort(e) {
- e.preventDefault();
- console.log('start sorting');
- this.#sorted = !this.#sorted;
- this._getLocalStorage(this.#sorted);
- }
- }
- class Workout {
- date = new Date();
- id = (Date.now() + '').slice(-10); // unique identiofier
- constructor(coords, distance, duration) {
- this.coords = coords;
- this.distance = distance; // in km
- this.duration = duration; // in min
- }
- _setDescription() {
- // prettier-ignore
- 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, cadence) {
- super(coords, distance, duration);
- this.cadence = cadence;
- this._calcPace();
- this._setDescription();
- }
- _calcPace() {
- // min/km
- return (this.pace = this.duration / this.distance);
- }
- }
- class Cycling extends Workout {
- type = 'cycling';
- constructor(coords, distance, duration, elevationGain) {
- super(coords, distance, duration);
- this.elevationGain = elevationGain;
- this._calcSpeed();
- this._setDescription();
- }
- _calcSpeed() {
- return (this.speed = this.distance / this.duration);
- }
- }
- const app = new App();
|