提问人:George Cosmin 提问时间:11/1/2023 最后编辑:VLAZGeorge Cosmin 更新时间:11/1/2023 访问量:19
尝试使用 Leaflet API 提交编辑的锻炼时出错:未捕获的类型错误:this[#mapEvent] 未定义
Error when trying to submit an edited workout using Leaflet API: Uncaught TypeError: this[#mapEvent] is undefined
问:
所以我有一个地图应用程序,一个基于 Leaflet API 的地图更具体地说,所以我创建了一个应用程序,它允许我查看地图、在其上指向标记、创建和提交锻炼(键入“跑步”或“骑自行车”),我还可以删除锻炼,并创建了一个带有单击事件的编辑按钮,这将允许我通过输入表单编辑已经存在的锻炼的值,但一旦我提交了编辑,它只会创建一个新的锻炼作为原始锻炼的克隆,并会给我以下错误:
当我通过单击forEach()方法时,通过事件侦听器调用“_editWorkout()”方法作为对“btnEdit”的回调时,就会发生这种情况。
似乎在提交后,“form.addEventListener('submit', this._newWorkout.bind(this));” 在执行上下文中再次调用并再次链接,就像尝试通过“_newWorkout()”方法创建新锻炼时一样。
请告知我做错了什么或我错过了什么。
尝试通过编辑按钮编辑已经存在的锻炼,一旦编辑并提交锻炼,它应该用表单输入上新创建的值替换原始锻炼。
这是我的完整代码:
class Workout {
date = new Date();
id = (Date.now() + '').slice(-10);
clicks = 0;
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()}`;
}
click() {
this.clicks++;
}
}
class Running extends Workout {
type = 'running';
constructor(coords, distance, duration, cadence) {
super(coords, distance, duration);
this.cadence = cadence;
this.calcPace();
this._setDescription(); // Sets Workout description
}
calcPace() {
// Calculated in 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.type = 'cycling';
this.calcSpeed();
this._setDescription(); // Sets Workout description
}
calcSpeed() {
// Calculate in Km/h
this.speed = this.distance / (this.duration / 60);
return this speed;
}
}
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 btnClear = document.querySelector('.btn__clearAll');
const btnPositive = document.querySelector('.btn--positive');
const btnNegative = document.querySelector('.btn--negative');
const alertEditMessage = document.querySelector('.alert--edit');
const alertMessage = document.querySelector('.alert--deletion');
class App {
// Constructor loads when the page loads.
#map;
#mapZoomLevel = 13;
#mapEvent;
#workouts = [];
// Constructor loads when the page loads.
constructor() {
// Find user position
this._getPosition();
// Get data from local storage
this._getLocalStorage();
// Attach event handlers
// Add new Workout. Bind the class App`s keyword `this`
form.addEventListener('submit', this._newWorkout.bind(this));
// Swap between the inputs Elevation & Candence
inputType.addEventListener('change', this._toggleElevationField);
// Moves map to the selected marker`s position
containerWorkouts.addEventListener('click', this._moveToPopup.bind(this));
// Deletes Workout
const btnDelete = document.querySelectorAll('.workout__delete');
btnDelete.forEach(btn => {
btn.addEventListener('click', this._deleteWorkout.bind(this));
});
// Deletes all Workouts
btnClear.addEventListener('click', this._clearAll.bind(this));
// Edit Workout
const btnEdit = document.querySelectorAll('.workout__edit');
btnEdit.forEach(btn => {
btn.addEventListener('click', this._editWorkout.bind(this));
});
// btnPositive.addEventListener('click', () => this._saveEditedWorkout(this));
}
// Gets User's Position
_getPosition() {
if (navigator.geolocation)
navigator.geolocation.getCurrentPosition(
this._loadMap.bind(this),
function () {
alert('Could not get your position');
}
);
}
// Loads map with Leaflet API
_loadMap(position) {
// Finds the coordinates
const { latitude } = position.coords;
const { longitude } = position.coords;
// console.log(`https://www.google.pt/maps/@${latitude},${longitude}`);
// Seperates the coordinates
const coords = [latitude, longitude];
// Places the coordinates within the map
this.#map = L.map('map').setView(coords, this.#mapZoomLevel);
// Displays the map (via Leaflet API)
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);
// Handling clicks on map
this.#map.on('click', this._showForm.bind(this));
// Renders saved workout markers
this.#workouts.forEach(work => {
this._renderWorkoutMarker(work);
});
}
// Reveals form
_showForm(mapE) {
this.#mapEvent = mapE;
form.classList.remove('hidden');
inputDistance.focus();
}
// Empty inputs + Hides form
_hideForm() {
// Empty inputs
inputDistance.value =
inputDuration.value =
inputCadence.value =
inputElevation.value =
'';
form.style.display = 'none';
form.classList.add('hidden');
setTimeout(() => (form.style.display = 'grid'), 1000);
}
// Swaps between the Elevation & Cadence inputs
_toggleElevationField() {
inputElevation.closest('.form__row').classList.toggle('form__row--hidden');
inputCadence.closest('.form__row').classList.toggle('form__row--hidden');
}
// Main method - creates workout
_newWorkout(e) {
e.preventDefault();
const validInputs = (...inputs) =>
inputs.every(inp => Number.isFinite(inp));
const allPositive = (...inputs) => inputs.every(inp => inp > 0);
// Retrieve data for workout array
const type = inputType.value;
const distance = +inputDistance.value;
const duration = +inputDuration.value;
const { lat, lng } = this.#mapEvent.latlng;
let workout;
// If workout is "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;
// Check if data is valid
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 workout array
this.#workouts.push(workout);
// Render workout on map as marker
this._renderWorkoutMarker(workout);
// Render workout on list
this._renderWorkout(workout);
// Hide form + clear input fields
this._hideForm();
// Set local storage to all workouts
this._setLocalStorage();
}
// Deletes all workouts
_deleteAll() {
this._renderAlert();
let paragraph = alertMessage.children;
paragraph[0].innerText =
'Are you sure you want to delete all your workouts?';
btnPositive.addEventListener('click', () => {
this._clearAll();
});
}
// Deletes Workout
_deleteWorkout(e) {
// Select workout and it's ID
let target = e.target.parentNode;
let id = target.dataset.id;
let element; // Workout to be deleted
// Looks through each of the workouts for matching IDs
let list = this.#workouts;
list.forEach(workout => {
let listId = workout.id;
if (listId === id) {
element = workout;
}
}); // Locates workout to be deleted and fills the element variable
// Locates workout within Storage
const isElement = sample => sample === element; // findIndex Condition
const index = list.findIndex(isElement); // Locate index of workout
this._renderAlert();
let paragraph = alertMessage.children;
paragraph[0].innerText = 'Are you sure you want to delete this workout?';
btnPositive.addEventListener('click', () => {
this.#workouts.splice(index, 1); // Delete workout
this._setLocalStorage(); // Save deletion
location.reload(); // Reload page
});
}
// Render the delete workout alert
_renderAlert() {
// Add alert message and remove the clear all button
alertMessage.classList.add('alert--deletion_edit--active');
btnClear.style.display = 'none';
// If btn-Negative, remove the alert and add back the Clear All btn.
btnNegative.addEventListener('click', () => {
alertMessage.classList.remove('alert--deletion_edit--active');
btnClear.style.display = 'unset';
return false;
});
btnPositive.addEventListener('click', () => {
alertMessage.classList.remove('alert--deletion_edit--active');
btnClear.style.display = 'unset';
return true;
});
}
// Edit Workout
_editWorkout(e) {
// Select workout and its ID
let target = e.target.closest('.workout');
let id = target.dataset.id;
let element; // Workout to be edited
// Find the workout to be edited
const workoutToEdit = this.#workouts.find(workout => workout.id === id);
if (!workoutToEdit) {
return;
}
// Populate the form with the workout data for editing
inputType.value = workoutToEdit.type;
inputDistance.value = workoutToEdit.distance;
inputDuration.value = workoutToEdit.duration;
inputCadence.value = workoutToEdit.cadence || '';
inputElevation.value = workoutToEdit.elevationGain || '';
// Show the form for editing
form.classList.remove('hidden');
// Save the ID of the workout being edited to a data attribute on the form
form.dataset.editId = id;
// Focus on the first input field for better user experience
inputDistance.focus();
// Add an event listener to the Save button within the form
const btnSave = document.querySelector('.form__btn');
btnSave.addEventListener('click', () => this._saveEditedWorkout(id));
}
_saveEditedWorkout(id) {
// Get the workout element being edited
const editedWorkout = this.#workouts.find(workout => workout.id === id);
if (!editedWorkout) {
return;
}
// Update the workout data with the edited values from the form
editedWorkout.type = inputType.value;
editedWorkout.distance = +inputDistance.value;
editedWorkout.duration = +inputDuration.value;
editedWorkout.cadence = +inputCadence.value;
editedWorkout.elevationGain = +inputElevation.value;
// Update the UI with the edited workout
this._renderWorkout(editedWorkout);
// Clear the form and the edit ID
this._hideForm();
}
// Render Leaflet based map marker
_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();
}
// Render workout using Template literals and HTML
_renderWorkout(workout) {
let html = `
<li class="workout workout--${workout.type}" data-id="${workout.id}">
<h2 class="workout__title">${workout.description}</h2>
<button class="workout__edit">Edit</button>
<button class="workout__delete">X</button>
<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>
</li>
`;
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>
</li>
`;
form.insertAdjacentHTML('afterend', html);
}
// Drifts map over to the selected workout
_moveToPopup(e) {
if (!this.#map) return;
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,
},
});
// using the public interface
// workout.click();
}
_setLocalStorage() {
localStorage.setItem('workouts', JSON.stringify(this.#workouts));
}
_getLocalStorage() {
const data = JSON.parse(localStorage.getItem('workouts'));
if (!data) return;
this.#workouts = data;
this.#workouts.forEach(work => {
this._renderWorkout(work);
});
}
_clearAll() {
localStorage.removeItem('workouts');
location.reload();
}
}
const app = new App();
答: 暂无答案
评论