尝试使用 Leaflet API 提交编辑的锻炼时出错:未捕获的类型错误:this[#mapEvent] 未定义

Error when trying to submit an edited workout using Leaflet API: Uncaught TypeError: this[#mapEvent] is undefined

提问人:George Cosmin 提问时间:11/1/2023 最后编辑:VLAZGeorge Cosmin 更新时间:11/1/2023 访问量:19

问:

所以我有一个地图应用程序,一个基于 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:
        '&copy; <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();
JavaScript 类型错误 未定义

评论


答: 暂无答案