PublishedMap.vue

This is where the users can see and interact with the maps published by them and other Mapmama users.

import

// Importing Vue components for the NavigationBar and Footer
import NavigationBar from "../reusable-layout/NavigationBar.vue";
import Footer from "../reusable-layout/Footer.vue";
// Importing Axios for making HTTP requests and MarkerClusterer for clustering markers on Google Maps
import axios from "axios";
import { MarkerClusterer } from "@googlemaps/markerclusterer";

Above, we are importing the NavigationBar and Footer component which is a reusable layout because navigation bars and footers are usually same for each page so instead of writing it for each page, it's better to write it in one Vue page and import it in each page as components

Next, is the "axios" package which is used to make API call to the backend (Laravel). It is important because this is like the key that allows us to communicate with the backend.

Finally, is the "MarkerClusterer" package. This package is used to make clusters in the map when there are too many markers in the map. This makes the map less messy and reduces lag.

export default

// Exporting the Vue component
export default {}

Inside this is where we define data, methods, computed properties, and lifecycle hooks.

components

// Register the imported components
components: {
  NavigationBar,
  Footer,
}

The components option is used to register and make Vue external components available for use within this component

data

// Data properties of the component
data() {
  return {
      tableTitle: "",
      domainBackend: "",
      domainFrontend: "",
      map: null,
      allData: [],
      currentData: [],
  };
}

The data method is used to initialize and define the data properties for this component. See the source code to know what each variable is used for.

mounted

// Lifecycle hook: Executed when the component is mounted to the DOM
mounted() {
  // Scroll to the top of the window when the component is mounted
  window.scrollTo(0, 0);
  // Setting frontend domain from the Vuex store
  this.domainFrontend = this.$store.state.domainFrontend;
  // Setting backend domain from the Vuex store
  this.domainBackend = this.$store.state.domainBackend;
  // Initialize the map with published data by fetching it from the backend.
  this.getPublishedMap();
}

This is called mounted lifecycle hook. The mounted hook is called after the component has been added to the DOM, making it a good place to perform initial setup and actions.

To explain what this mounted hook does:

  1. It scrolls the window to the top so the user sees the content at the beginning of the page.

  2. It gets the backend and frontend domain from what is defined in the Vuex.

  3. It will get the JSON representation of the map data array of all the published maps from the backend.

methods

// Methods of the component
methods: {}

The methods option is used to define methods that can be called or triggered within a Vue component. These methods are typically used to define the behavior and functionality of the component

getPublishedMap

// Method to fetch published map data from the backend and initialize the map with markers
getPublishedMap() {
  // Update map buttons (UI elements related to the map)
  this.updateMapButtons();
  // Make an asynchronous POST request to the backend to get published map data
  axios
    .post(this.domainBackend + "/api/getPublishedMap")
    .then((response) => {
      // Check if the backend response indicates success (SUCCESS == 1)
      if (response.data.SUCCESS == 1) {
        // Store the retrieved map data in the component's state
        this.allData = response.data.DATA;
        this.currentData = this.allData[0];
        // Check if there is any map data available
        if (this.allData.length == 0) {
          // If no data is available, initialize the map with default settings
          this.initMap();
          this.updateMapButtons();
          this.tableTitle = "Table";
        } else {
          // If map data is available, add markers to the map, update the table, and set the map title
          this.addMarkers(this.allData[0]);
          this.updateTable(this.allData[0]);
          this.updateMapButtons();
          this.tableTitle = this.allData[0].TITLE;
        }
      }
    });
}

To explain what this getPublishedMap method does:

  1. First, it will update the map entries in the selection section.

  2. Then, it will get the JSON representation of the map data array of all the published maps from the backend.

  3. It initializes the map data array and the currently selected map data.

  4. If the map data array is empty, then it will just initialize an empty map to display, update the map entries in the selection section, and name the table with its default name.

  5. If the map data array is not empty, then it will choose the first map data in the array to add the markers in the map, populate the table with rows, update the map entries in the selection section, and name the table with name of the map.

openShareToSocial

// Method to handle the process of sharing a map to social media platforms
openShareToSocial() {
  // Prepare the request payload containing the map title and data in JSON format
  const request = {
    jsonData: JSON.stringify({
      TITLE: this.currentData.TITLE,
      DATA: this.currentData.DATA,
    }),
  };
  // Make an asynchronous POST request to the backend to store the shared map
  axios
    .post(this.domainBackend + "/api/storeSharedMap", request)
    .then((response) => {
      // Check if the backend response indicates success (SUCCESS == 1)
      if (response.data.SUCCESS == 1) {
        // Retrieve the generated URL for the shared map and update the UI
        const urlLink = document.getElementById("urlLink");
        urlLink.value =
          this.domainFrontend + "/shared-map/" + response.data.FILENAME;
        // Display the container for sharing on social media
        const containerShareToSocial = document.getElementById(
          "containerShareToSocial"
        );
        containerShareToSocial.style.display = "block";
      }
    });
}

To explain what this openShareToSocial method does:

  1. It sends the JSON representation of the currently selected map data to the backend so that the backend can make and store the new shared map file. Then, the backend will return the name of the file which is also the route (URL) to access the shared map file.

  2. After that, a pop-up box will be shown where the user can copy the URL of the shared map.

closeShareToSocial

// Method to close the social media sharing interface
closeShareToSocial() {
  // Retrieve the container element for sharing on social media
  const containerShareToSocial = document.getElementById(
    "containerShareToSocial"
  );
  // Hide the container by setting its display property to "none"
  containerShareToSocial.style.display = "none";
}

To explain what this closeShareToSocial method does:

  1. It will the close the pop-up box.

convertJsonToGoogleSheet

// Method to convert the current map data into a Google Sheet format
convertJsonToGoogleSheet() {
  // Capture relevant data for the Google Sheet conversion
  const domainBackend = this.domainBackend; // Backend domain URL
  const title = this.currentData.TITLE; // Title of the current map
  const data = JSON.parse(JSON.stringify(this.currentData.DATA)); // Deep copy of map data
  data.forEach((item) => {
    // Remove unnecessary properties for Google Sheet
    delete item.LATITUDE;
    delete item.LONGITUDE;
  });
  const jsonData = JSON.stringify(data); // Convert cleaned data back to JSON
  // Define properties for the Google Sheet authentication window
  const popupProperties = "width=500,height=800,scrollbars=yes";
  let windowPopUp;
  // Open a new window for Google Sheet authentication
  windowPopUp = window.open("", "Google", popupProperties);
  // Initiate authentication by making a GET request to the backend
  axios
    .get(
      this.domainBackend + "/api/authenticateGoogleAccountForGoogleSheet"
    )
    .then((response) => {
      // Check if authentication was successful
      if (response.data.SUCCESS == 1) {
        const url = response.data.AUTH_URL;
        // Redirect the authentication window to the Google authentication URL
        windowPopUp.location.href = url;
        // Monitor changes in the authentication window's URL
        monitorUrlChanges();
      }
    });
  // Function to monitor changes in the authentication window's URL
  function monitorUrlChanges() {
    const interval = setInterval(() => {
      if (windowPopUp && !windowPopUp.closed) {
        const urlPopUp = windowPopUp.location.href;
        // Check if the URL contains the authentication code
        if (urlPopUp.includes("?code=")) {
          const params = new URLSearchParams(urlPopUp.split("?")[1]);
          const code = params.get("code");
          // Send the JSON data and authentication code to the backend
          sendJson(code);
          // Stop monitoring URL changes
          clearInterval(interval);
        }
      } else {
        // Stop monitoring URL changes if the window is closed
        clearInterval(interval);
      }
    }, 1000);
  }
  // Function to send JSON data and authentication code to the backend for Google Sheet conversion
  function sendJson(code) {
    const request = {
      title: title,
      jsonData: jsonData,
      code: code,
    };
    // Make a POST request to the backend for Google Sheet conversion
    axios
      .post(domainBackend + "/api/convertJsonToGoogleSheet", request)
      .then((response) => {
        // Close the authentication window and open the generated Google Sheet link in a new window
        windowPopUp.close();
        window.open(response.data.LINK, "_blank").focus();
      });
  }
}

To explain what this convertJsonToGoogleSheet method does:

  1. It retrieves an authentication URL which is used to authenticate the user's Google account. It is needed because the converted Google Sheet file will be saved in the user's Google Drive.

  2. The authentication URL will be opened in a pop-up window.

  3. Then, the changes that happens in the URL bar of the pop-up window is monitored because after the user has authenticated their account, Google will send a code which is then used to do the Google Sheets conversion.

  4. After the code has been given by Google, the JSON representation of the currently selected map data, the map title, and the code given by Google is sent to the backend.

  5. After converting the JSON data to Google Sheets, the backend will respond with a link to the Google Sheets file.

  6. The link is automatically opened in a new tab.

downloadJson

// Method to download the current map data as a JSON file
downloadJson() {
  // Create a deep copy of the map data and remove latitude and longitude properties
  const data = JSON.parse(JSON.stringify(this.currentData.DATA));
  data.forEach((item) => {
    delete item.LATITUDE;
    delete item.LONGITUDE;
  });
  // Convert the cleaned data back to JSON format with proper indentation
  const jsonData = JSON.stringify(data, null, 2);
  // Create a Blob (Binary Large Object) from the JSON data with MIME type "application/json"
  const blob = new Blob([jsonData], { type: "application/json" });
  // Create a URL for the Blob
  const url = window.URL.createObjectURL(blob);
  // Create an <a> element to represent the download link
  const a = document.createElement("a");
  a.href = url;
  // Set the download attribute to specify the filename for the downloaded file
  a.download = this.currentData.TITLE + ".json";
  // Append the <a> element to the document body
  document.body.appendChild(a);
  // Simulate a click on the <a> element to trigger the download
  a.click();
  // Revoke the object URL to free up resources
  window.URL.revokeObjectURL(url);
  // Remove the <a> element from the document body
  document.body.removeChild(a);
}

To explain what this downloadJson method does:

  1. It will get the currently selected map data and convert it to a BLOB object that encapsulates the JSON data.

  2. Then, it programmatically initiates the download of the file.

convertJsonToCsv

// Method to convert the current map data into CSV format and download it
convertJsonToCsv() {
  // Create a deep copy of the map data and remove latitude and longitude properties
  const data = JSON.parse(JSON.stringify(this.currentData.DATA));
  data.forEach((item) => {
    delete item.LATITUDE;
    delete item.LONGITUDE;
  });
  // Prepare the request object with JSON data
  const request = {
    jsonData: JSON.stringify(data),
  };
  // Make a POST request to the server to convert JSON to CSV
  axios
    .post(this.domainBackend + "/api/convertJsonToCsv", request)
    .then((response) => {
      // Check if the conversion was successful
      if (response.data.SUCCESS == 1) {
        // Create a Blob (Binary Large Object) from the CSV data with MIME type "text/csv"
        const blob = new Blob([response.data.DATA], { type: "text/csv" });
        // Create a URL for the Blob
        const url = window.URL.createObjectURL(blob);
        // Create an <a> element to represent the download link
        const a = document.createElement("a");
        a.href = url;
        // Set the download attribute to specify the filename for the downloaded CSV file
        a.download = this.currentData.TITLE + ".csv";
        // Append the <a> element to the document body
        document.body.appendChild(a);
        // Simulate a click on the <a> element to trigger the download
        a.click();
        // Revoke the object URL to free up resources
        window.URL.revokeObjectURL(url);
        // Remove the <a> element from the document body
        document.body.removeChild(a);
      }
    });
}

To explain what this convertJsonToCsv method does:

  1. It sends the JSON representation of the currently selected map data to the backend.

  2. The backend will respond with a CSV representation of the currently selected map data then it converts it to a BLOB object that encapsulates the CSV data.

  3. Then, it programmatically initiates the download of the file.

openExportMap

// Method to display the export map interface
openExportMap() {
  // Get the HTML element with the id "containerExportMap"
  const containerExportMap = document.getElementById("containerExportMap");
  // Set the display style of the container to "block" to make it visible
  containerExportMap.style.display = "block";
}

To explain what this openExportMap method does:

  1. It shows a pop-up box where the user can choose whether to export the currently selected map data to CSV file or Google Sheets file.

closeExportMap

// Method to close the export map interface
closeExportMap() {
  // Get the HTML element with the id "containerExportMap"
  const containerExportMap = document.getElementById("containerExportMap");
  // Set the display style of the container to "none" to hide it
  containerExportMap.style.display = "none";
}

To explain what this closeExportMap method does:

  1. It will the close the pop-up box.

deleteMap

// Method to delete a specific map from the list of published maps
deleteMap(index) {
  // Loop through allData array to find the map with the specified index
  for (let i = 0; i < this.allData.length; i++) {
    // Check if the current iteration matches the specified index
    if (i == index) {
      // Delete the map by calling the deletePublishedMap method with the map's ID
      this.deletePublishedMap(this.allData[i].ID);
      // Remove the map from the allData array
      this.allData.splice(i, 1);
      // Set the currentData to the first map in the updated list
      this.currentData = this.allData[0];
      // Break out of the loop once the map is deleted
      break;
    }
  }
  // Update the map buttons based on the modified list of maps
  this.updateMapButtons();
  // Check if there are remaining maps
  if (this.allData.length == 0) {
    // If no maps are left, initialize the map, update the table, and set the table title
    this.initMap();
    this.updateTable([]);
    this.tableTitle = "Table";
  } else {
    // If there are remaining maps, add markers, update the table, and set the table title
    this.addMarkers(this.currentData);
    this.updateTable(this.currentData);
    this.tableTitle = this.currentData.TITLE;
  }
}

To explain what this deleteMap method does:

  1. When the user clicks on the delete icon in the map selection section to delete that particular map data, the index of the map data that resides in the map data array goes to this function.

  2. Then, the map data array is looped until the index matches.

  3. Once the index matches, the map data will be removed from the map data array.

  4. If the map data array is empty, then it will just initialize an empty map to display, populate the table with rows, update the map entries in the selection section, and name the table with its default name.

  5. If the map data array is not empty, then it will choose the first map data in the array to set it as the map data to be used initially when the page loads, add the markers in the map, populate the table with rows, update the map entries in the selection section, and name the table with name of the map.

  6. if the user is logged in, the JSON representation of the map data array is sent to the backend so that the backend can write the JSON data to the map file of the user.

  7. If the user is not logged in, then the map data array is saved to the cookie.

deletePublishedMap

// Method to remove a published map from the backend
deletePublishedMap(id) {
  // Create a request object with the fileName property set to the provided map ID
  const request = {
    fileName: id,
  };
  // Send a POST request to the backend API to delete the published map
  axios
    .post(this.domainBackend + "/api/deletePublishedMap", request)
    .then((response) => {
      // Check if the deletion on the backend was successful
      if (response.data.SUCCESS == 1) {
        // If successful, do nothing (no further action required)
      }
    });
}

To explain what this deletePublishedMap method does:

  1. It will send the ID of the published map which is also the file name of it to the backend.

  2. The backend will do the deletion of the particular published map file.

updatePublishedMap

// Method to update a published map with new data on the backend
updatePublishedMap() {
  // Variable to store a duplicate of the map data for the currently logged-in user
  var duplicate_allData = null;
  // Iterate through all published maps
  for (let i = 0; i < this.allData.length; i++) {
    // Check if the current map belongs to the logged-in user
    if (this.allData[i].USER_ID == this.$cookie.getCookie("L_userId")) {
      // Create a deep copy (duplicate) of the user's map data
      duplicate_allData = JSON.parse(JSON.stringify(this.allData[i]));
    }
  }
  // Create a request object with the duplicated map data converted to a JSON string
  const request = {
    jsonData: JSON.stringify(duplicate_allData),
  };
  // Send a POST request to the backend API to update the published map
  axios
    .post(this.domainBackend + "/api/storePublishedMap", request)
    .then((response) => {
      // Check if the update on the backend was successful
      if (response.data.SUCCESS == 1) {
        // If successful, do nothing (no further action required)
      }
    });
}

To explain what this updatePublishedMap method does:

  1. It sends the JSON representation of the map data array to the backend.

  2. The backend will write the JSON onto the particular published map file of the user.

updateMapButtons

// Method to dynamically update the map buttons based on available data
updateMapButtons() {
  // Get the container element for map buttons from the DOM
  const mapButtonsContainer = document.getElementById(
    "map-buttons-container"
  );
  // Clear the existing content of the container
  mapButtonsContainer.innerHTML = "";
  // Iterate through all available map data
  for (let i = 0; i < this.allData.length; i++) {
    // Create the first container div for each map
    const firstDiv = document.createElement("div");
    firstDiv.classList.add("d-flex", "justify-content-between");
    firstDiv.style.display = "flex";
    firstDiv.style.borderLeft = "3px solid white";
    firstDiv.style.borderBottom = "3px solid white";
    // Create a strong tag to display the map title
    const strongTag = document.createElement("strong");
    strongTag.innerText = this.allData[i].TITLE;
    // Create an anchor tag inside the second div for map details
    const aTagInsideSecondDiv = document.createElement("a");
    aTagInsideSecondDiv.classList.add("nav-item", "nav-link", "text-white");
    aTagInsideSecondDiv.innerHTML =
      "<strong style='color:white'>" +
      this.allData[i].TITLE +
      "</strong>" +
      " by " +
      this.allData[i].USERNAME;
    // Add a click event listener to the anchor tag to update the UI when a map is selected
    aTagInsideSecondDiv.addEventListener("click", () => {
      this.tableTitle = this.allData[i].TITLE;
      this.addMarkers(this.allData[i]);
      this.updateTable(this.allData[i]);
      this.currentData = this.allData[i];
    });
    // Set cursor style to pointer for better user interaction
    aTagInsideSecondDiv.style.cursor = "pointer";
    // Create the second div and append the anchor tag to it
    const secondDiv = document.createElement("div");
    secondDiv.style.width = "70%";
    secondDiv.appendChild(aTagInsideSecondDiv);
    // Append the strong tag to the first div
    firstDiv.appendChild(secondDiv);
    // Create the third div for map deletion option
    const thirdDiv = document.createElement("div");
    // Create an icon element for the delete option
    const iconElement = document.createElement("i");
    iconElement.classList.add("fa", "fa-trash", "text-white");
    iconElement.style.cursor = "pointer";
    iconElement.style.marginRight = "10px";
    iconElement.style.marginTop = "10px";
    // Add a click event listener to the icon to delete the corresponding map
    iconElement.addEventListener("click", () => {
      this.deleteMap(i);
    });
    // Check if the user is the owner of the map, and only then show the delete option
    if (
      this.$cookie.isCookieAvailable("L_userId") == true &&
      this.$cookie.getCookie("L_userId") == this.allData[i].USER_ID
    ) {
      thirdDiv.appendChild(iconElement);
    } else {
      // Placeholder for non-owner user (can be customized based on requirements)
      const iconElement = document.createElement("i");
      thirdDiv.appendChild(iconElement);
    }
    // Append the third div to the first div
    firstDiv.appendChild(thirdDiv);
    // Append the first div to the map buttons container in the DOM
    mapButtonsContainer.appendChild(firstDiv);
  }
}

To explain what this updateMapButtons method does:

  1. It updates the map selection section with a list of buttons. Each button represents a map data from the map data array. Each button is created dynamically and includes a title, a link that triggers various actions when clicked (which is populating the table with rows, adding markers to the map, and setting cookies), and a delete icon that allows removing the associated map data from the array.

initMap

// Method to initialize the Google Map with default settings
initMap() {
  // Create a new instance of the Google Map and set its properties
  this.map = new google.maps.Map(document.getElementById("map"), {
    // Set the initial center of the map to (0, 0)
    center: { lat: 0, lng: 0 },
    // Set the initial zoom level of the map to 2
    zoom: 2,
    // Custom property to store markers associated with the map (initially an empty array)
    markers: [],
  });
}

To explain what this initMap method does:

  1. It initializes a Google Map on the page.

addMarkers

// Method to add markers to the map based on the provided data
addMarkers(data) {
  // Check if there is data to display
  if (data.DATA.length != 0) {
    // Create a deep copy of the data to avoid unintentional modification
    const duplicate_data = JSON.parse(JSON.stringify(data.DATA));
    // Remove the "WKT" property from each item in the copied data
    duplicate_data.forEach((item) => {
      if (item.hasOwnProperty("WKT")) {
        delete item.WKT;
      }
    });
    // Create a proxy for the duplicated data to handle getter and setter functions
    const proxy_data = new Proxy(duplicate_data, {
      get: function (target, prop) {
        return target[prop];
      },
      set: function (target, prop, value) {
        target[prop] = value;
        return true;
      },
    });
    // Reset the map and initialize a new one
    this.map = null;
    this.initMap();
    // Create an array to store markers and initialize an info window
    const markers = [];
    let currentInfoWindow = null;
    // Loop through each place in the data to create markers
    proxy_data.forEach((place) => {
      // Create a new marker for each place
      const marker = new google.maps.Marker({
        position: {
          lat: parseFloat(place.LATITUDE),
          lng: parseFloat(place.LONGITUDE),
        },
        map: this.map,
        title: place.NAME,
      });
      // Add a click event listener to each marker
      marker.addListener("click", () => {
        // Close the current info window if it exists
        if (currentInfoWindow) {
          currentInfoWindow.close();
        }
        // Create a content div for the info window
        const mainDiv = document.createElement("div");
        // Create divs for title, coordinates, and icons
        const titleHolderDiv = document.createElement("div");
        titleHolderDiv.setAttribute("class", "titleHolder");
        titleHolderDiv.innerHTML =
          '<strong style="color:black;">' + place.NAME + "</strong>";
        const coordinateHolderDiv = document.createElement("div");
        coordinateHolderDiv.setAttribute("class", "coordinateHolder");
        coordinateHolderDiv.innerHTML =
          "<p><b>LATITUDE:</b>" +
          place.LATITUDE +
          "</p><p><b>LONGITUDE:</b>" +
          place.LONGITUDE +
          "</p>";
        const iconsHolderDiv = document.createElement("div");
        iconsHolderDiv.setAttribute("class", "iconsHolder");
        // Create share, locate, edit, and bookmark buttons
        const shareButtonDiv = document.createElement("div");
        shareButtonDiv.setAttribute("class", "icon");
        shareButtonDiv.style.backgroundColor = "white";
        shareButtonDiv.style.borderColor = "white";
        shareButtonDiv.addEventListener("click", () =>
          this.shareRow(place)
        );
        shareButtonDiv.innerHTML =
          '<i class="fas fa-share-nodes" aria-hidden="true"></i><span class="tooltiptext">Share</span>';
        const locationButtonDiv = document.createElement("div");
        locationButtonDiv.setAttribute("class", "icon");
        locationButtonDiv.style.backgroundColor = "white";
        locationButtonDiv.style.borderColor = "white";
        locationButtonDiv.addEventListener("click", () => {
          const url =
            "https://www.google.com/maps/search/?api=1&query=" +
            place.LATITUDE +
            "%2C" +
            place.LONGITUDE;
          window.open(url, "_blank");
        });
        locationButtonDiv.innerHTML =
          '<i class="fas fa-location-dot" aria-hidden="true"></i><span class="tooltiptext">Locate</span>';
        if (
          this.$cookie.isCookieAvailable("L_userId") == true &&
          this.$cookie.getCookie("L_userId") == data.USER_ID
        ) {
          const editButtonDiv = document.createElement("div");
          editButtonDiv.setAttribute("class", "icon");
          editButtonDiv.style.backgroundColor = "white";
          editButtonDiv.style.borderColor = "white";
          editButtonDiv.addEventListener("click", () =>
            this.editRow(place)
          );
          editButtonDiv.innerHTML =
            '<i class="fas fa-edit" aria-hidden="true"></i><span class="tooltiptext">Edit</span>';
          iconsHolderDiv.appendChild(editButtonDiv);
        }
        const bookmarkButtonDiv = document.createElement("div");
        bookmarkButtonDiv.setAttribute("class", "icon");
        bookmarkButtonDiv.style.backgroundColor = "white";
        bookmarkButtonDiv.style.borderColor = "white";
        bookmarkButtonDiv.addEventListener("click", () =>
          this.bookmarkRow(place)
        );
        bookmarkButtonDiv.innerHTML =
          '<i id="myBookmark" class="fa fas fa-bookmark" aria-hidden="true"></i><span class="tooltiptext">Bookmark</span>';
        iconsHolderDiv.appendChild(shareButtonDiv);
        iconsHolderDiv.appendChild(locationButtonDiv);
        iconsHolderDiv.appendChild(bookmarkButtonDiv);
        // Append created elements to the main div
        mainDiv.appendChild(titleHolderDiv);
        mainDiv.appendChild(coordinateHolderDiv);
        mainDiv.appendChild(iconsHolderDiv);
        // Create an info window and open it with the main div content
        const infowindow = new google.maps.InfoWindow({
          content: mainDiv,
        });
        infowindow.open(this.ETM_map, marker);
        currentInfoWindow = infowindow;
      });
      const map = this.map;
      // Store the marker in the markers array
      markers.push(marker);
      this.map = map;
    });
    // Set the map center and zoom level based on the first place's coordinates
    const map = this.map;
    map.setCenter({
      lat: parseFloat(proxy_data[0].LATITUDE),
      lng: parseFloat(proxy_data[0].LONGITUDE),
    });
    map.setZoom(10);
    // Create a marker cluster for better visualization
    const markerCluster = new MarkerClusterer({
      map: map,
    });
    // Add each marker to the cluster
    markers.forEach((marker) => {
      markerCluster.addMarker(marker);
    });
    // Store the markers array in the map property
    map.markers = markers;
    this.map = map;
  } else {
    // If there is no data, reset the map
    this.map = null;
    this.initMap();
  }
}

To explain what this addMarkers method does:

  1. if the map data is not empty, it proceeds with the following steps; otherwise, it initializes the map and sets it to null.

  2. It initializes a Google Map and creates an empty array to hold markers.

  3. It iterates through each item in the map data, creating a marker on the map for each place listed in the map data. Each marker has a title, a click event listener, and an info window that displays additional information about the place when clicked.

  4. It sets up icons and event listeners for buttons within the info window, allowing actions like sharing, locating on Google Maps, editing, and bookmarking the place.

  5. The markers are clustered together for better visualization when there are multiple markers in close proximity.

updateTable

// Method to update the data table based on the current map data
updateTable(data) {
  // Create a deep copy of the data to avoid unintentional modification
  const duplicate_data = JSON.parse(JSON.stringify(data.DATA));
  // Remove the "WKT" property from each item in the copied data
  duplicate_data.forEach((item) => {
    if (item.hasOwnProperty("WKT")) {
      delete item.WKT;
    }
  });
  // Create a proxy for the duplicated data to handle getter and setter functions
  const proxy_data = new Proxy(duplicate_data, {
    get: function (target, prop) {
      return target[prop];
    },
    set: function (target, prop, value) {
      target[prop] = value;
      return true;
    },
  });
  // Get references to the table head row and table body
  const tableHeadRow = document.getElementById("table-tr");
  const tableBody = document.getElementById("mtsTbody");
  // Clear the existing content of the table
  tableHeadRow.innerHTML = "";
  tableBody.innerHTML = "";
  // Check if there is data to display
  if (proxy_data.length != 0) {
    // Create a set to store all unique property names across the data
    const allProperties = new Set();
    // Iterate through each item to collect all unique property names
    proxy_data.forEach((item) => {
      Object.keys(item).forEach((propertyName) => {
        allProperties.add(propertyName);
      });
    });
    // Create table header cells based on unique property names
    allProperties.forEach((propertyName) => {
      const th = document.createElement("th");
      th.textContent = propertyName;
      tableHeadRow.appendChild(th);
    });
    // Create "EDIT" and "OTHER ACTION" columns in the table header
    let th = document.createElement("th");
    if (
      this.$cookie.isCookieAvailable("L_userId") == true &&
      this.$cookie.getCookie("L_userId") == data.USER_ID
    ) {
      th.textContent = "EDIT";
      tableHeadRow.appendChild(th);
    }
    th = document.createElement("th");
    th.textContent = "OTHER ACTION";
    tableHeadRow.appendChild(th);
    // Iterate through each item to create table rows
    proxy_data.forEach((item) => {
      const row = document.createElement("tr");
      // Create table cells for each property
      allProperties.forEach((propertyName) => {
        const cell = document.createElement("td");
        cell.style.textAlign = "center";
        const propertyValue = item[propertyName];
        if (propertyValue !== undefined && propertyValue !== null) {
          cell.textContent = propertyValue;
        } else {
          // Handle null or undefined values if needed
          // cell.style.backgroundColor = "#adadad";
        }
        row.appendChild(cell);
      });
      // If user has editing permissions, create an "EDIT" button cell
      if (
        this.$cookie.isCookieAvailable("L_userId") == true &&
        this.$cookie.getCookie("L_userId") == data.USER_ID
      ) {
        const editCell = document.createElement("td");
        const editButton = document.createElement("button");
        editButton.setAttribute("class", "button");
        editButton.innerHTML = "<i class='fas fa-edit'></i>";
        editButton.addEventListener("click", () => this.editRow(item));
        editCell.appendChild(editButton);
        row.appendChild(editCell);
      }
      // Create an "OTHER ACTION" cell with delete, locate, and share buttons
      const otherCell = document.createElement("td");
      // If user has deleting permissions, create a "DELETE" button
      if (
        this.$cookie.isCookieAvailable("L_userId") == true &&
        this.$cookie.getCookie("L_userId") == data.USER_ID
      ) {
        const deleteButton = document.createElement("button");
        deleteButton.classList.add("button");
        deleteButton.innerHTML = "<i class='fa fa-trash'></i>";
        deleteButton.addEventListener("click", () => this.deleteRow(item));
        otherCell.appendChild(deleteButton);
      }
      // Create a "LOCATE" button with a link to Google Maps
      const markerButton = document.createElement("a");
      markerButton.href =
        "https://www.google.com/maps/search/?api=1&query=" +
        item["LATITUDE"] +
        "%2C" +
        item["LONGITUDE"];
      markerButton.target = "_blank";
      markerButton.innerHTML =
        "<button class='button'><i class='fa fa-map-marker'></i></button>";
      otherCell.appendChild(markerButton);
      // Create a "SHARE" button with a callback to the shareRow method
      const shareButton = document.createElement("button");
      shareButton.classList.add("button");
      shareButton.innerHTML = "<i class='fa fa-share-nodes'></i>";
      shareButton.addEventListener("click", () => this.shareRow(item));
      otherCell.appendChild(shareButton);
      // Append "OTHER ACTION" cell to the row
      row.appendChild(otherCell);
      // Append the row to the table body
      tableBody.appendChild(row);
    });
  }
}

To explain what this updateTable method does:

  1. It creates a deep copy of the map data using JSON serialization and parsing. This is done to avoid modifying the original data.

  2. It removes the "WKT" property from each object in map data if it exists.

  3. It creates a Proxy object of the map data that allows getting and setting properties while ensuring they are always set successfully.

  4. It selects the table's header row and body elements in the HTML document.

  5. It clears the existing content of the table's header row and body.

  6. If Proxy object is not empty, it proceeds to dynamically generate the table's header based on the properties of the objects in the Proxy object.

  7. It iterates over the properties of the objects to create table header cells and appends them to the header row.

  8. For each object in the Proxy object, it generates a new row in the table body, filling in cells with property values.

  9. It adds buttons to each row in the "EDIT" and "OTHER ACTION" column. Clicking these buttons triggers specific actions, such as editing, deleting, opening the location on Google Maps, or sharing the row.

bookmarkRow

// Method to bookmark a specific row/item in the map data
bookmarkRow(itemForBookmark) {
  // Check if the user is logged in by verifying the presence of a user ID cookie
  if (!this.$cookie.isCookieAvailable("L_userId")) {
    // Display an alert if the user is not logged in
    alert("LOGIN REQUIRED.");
  } else {
    // Prepare a request object with the user ID to check existing bookmarks
    const request = {
      userId: this.$cookie.getCookie("L_userId"),
    };
    // Send a request to the backend to retrieve the user's bookmarks
    axios
      .post(this.domainBackend + "/api/getBookmark", request)
      .then((response) => {
        // Check if the request was successful
        if (response.data.SUCCESS == 1) {
          // Extract bookmark data from the response
          const data = response.data.DATA;
          // Variable to track if the bookmark already exists for the provided item
          var isExist = false;
          // Iterate through existing bookmarks to check for a match
          data.forEach((item) => {
            if (
              item.LATITUDE.toString() ===
                itemForBookmark.LATITUDE.toString() &&
              item.LONGITUDE.toString() ===
                itemForBookmark.LONGITUDE.toString()
            ) {
              // Set the flag to true if a matching bookmark is found
              isExist = true;
              return; // Exit the loop early since the match is already found
            }
          });
          // Check if the bookmark already exists
          if (isExist == true) {
            // Display an alert if the bookmark already exists
            alert("BOOKMARK ALREADY EXISTS.");
          } else {
            // Add the provided item to the list of bookmarks
            data.push(itemForBookmark);
            // Prepare a new request to update the bookmarks with the new data
            const updateRequest = {
              jsonData: JSON.stringify({
                USER_ID: this.$cookie.getCookie("L_userId"),
                DATA: data,
              }),
            };
            // Send a request to the backend to store the updated bookmarks
            axios
              .post(
                this.domainBackend + "/api/storeBookmark",
                updateRequest
              )
              .then((response) => {
                // Check if the update was successful
                if (response.data.SUCCESS == 1) {
                  // Display an alert indicating that the bookmark was updated
                  alert("BOOKMARK UPDATED.");
                }
              });
          }
        }
      });
  }
}

To explain what this bookmarkRow method does:

  1. If the user is not logged in, it displays an alert, indicating that the user must be logged in to use this feature.

  2. it sends the user's ID to the backend to retrieve the data in the user's bookmark file.

  3. It then checks if the going-to-be-added bookmark item already exists in the user's bookmark data.

  4. If the item already exists in the user's bookmark data, displays an alert, indicating that the bookmark already exists.

  5. If the item does not exist in the user's bookmark data, it adds the item to the user's bookmark data and sends the bookmark data to update the user's bookmark file.

deleteRow

// Method to delete a specific row/item from the current map data
deleteRow(item) {
  // Iterate through the current data to find and remove the specified item
  for (let i = 0; i < this.currentData.DATA.length; i++) {
    if (this.currentData.DATA[i].ID == item.ID) {
      // Remove the item from the current data array
      this.currentData.DATA.splice(i, 1);
      break; // Exit the loop after the item is removed
    }
  }
  // Iterate through all data to update the current data in the main data set
  for (let i = 0; i < this.allData.length; i++) {
    if (this.allData[i].ID == this.currentData.ID) {
      // Update the corresponding entry in the main data set with the modified current data
      this.allData[i] = this.currentData;
      break; // Exit the loop after the update
    }
  }
  // Check if the current data is now empty
  if (this.currentData.DATA.length == 0) {
    // Reset the map and initialize it if the current data is empty
    this.map = null;
    this.initMap();
  } else {
    // Add markers to the map for the modified current data
    this.addMarkers(this.currentData);
  }
  // Update the table with the modified current data
  this.updateTable(this.currentData);
  // Update map buttons based on the modified data set
  this.updateMapButtons();
  // Update the published map with the modified data
  this.updatePublishedMap();
}

To explain what this deleteRow method does:

  1. It loops through the currently selected map data array to find the item with an ID that matches the ID of the item that is passed in the method.

  2. Once the matching item is found, it removes it from the currently selected map data array.

  3. It then iterates through the map data array and its nested DATA arrays to find and update the item with the same ID as the ID of the currently selected map data with the modified currently selected map data.

  4. If there are no more items left in the currently selected map data, it sets the map to null and initializes a new map.

  5. If there are still items in the currently selected map data, it add the markers in the map, populates the table, and updates the map entries in the selection section.

  6. The map data array and currently selected map data are saved to the cookie.

  7. If the user is logged in, then the JSON representation of the map data array is sent to the backend so that the backend can write the JSON data to the map file of the user.

shareRow

// Method to handle the sharing of a specific row/item in the map data
shareRow(item) {
  // Get the URL link for the location based on its latitude and longitude
  const urlLink = document.getElementById("urlLink");
  urlLink.value =
    "https://www.google.com/maps/search/?api=1&query=" +
    item["LATITUDE"] +
    "%2C" +
    item["LONGITUDE"];
  // Display the container for sharing on social media
  const containerShareToSocial = document.getElementById(
    "containerShareToSocial"
  );
  containerShareToSocial.style.display = "block";
}

To explain what this shareRow method does:

  1. A pop-up box will be shown where the user can copy the Google Maps link of the map item.

editRow

// Method to open and populate the map editing interface with an item's data
editRow(item) {
  // Toggle the visibility of the edit map container
  const editMap = document.querySelector(".editMap");
  editMap.classList.toggle("show");
  // Set the title of the edit map container to the item's name
  document.getElementById("editMap_Title").innerHTML =
    "<strong>" + item["NAME"] + "</strong>";
  // Clear the existing content of the edit map table and label classes container
  const editMap_table = document.querySelector(".editMap_table");
  editMap_table.innerHTML = "";
  const labelClassesContainerEdit = document.getElementById(
    "labelClassesContainerEdit"
  );
  labelClassesContainerEdit.innerHTML = "";
  // Create a deep copy of the item for modifications
  const modifiedItem = JSON.parse(JSON.stringify(item));
  const propertyList = Object.keys(modifiedItem);
  // Iterate through each property of the item
  for (let i = 0; i < propertyList.length; i++) {
    const propertyName = propertyList[i];
    const propertyValue = modifiedItem[propertyName];
    // Create a form group div for each property
    const divElement = document.createElement("div");
    divElement.className = "form-group";
    // Create a label for the property
    const labelElement = document.createElement("label");
    labelElement.className = "form-label";
    labelElement.innerHTML =
      "<div style='padding:5px;display:inline-block'>" +
      propertyName +
      "</div>";
    labelElement.setAttribute("name", propertyName);
    divElement.appendChild(labelElement);
    // If the property is not an ID or coordinates, allow deletion
    if (
      propertyName == "ID" ||
      propertyName == "NAME" ||
      propertyName == "LATITUDE" ||
      propertyName == "LONGITUDE"
    ) {
      // Do nothing for these properties
    } else {
      // Create a delete button for other properties
      var deleteButton = document.createElement("div");
      deleteButton.setAttribute("class", "buttonFormElementDelete");
      deleteButton.textContent = "DELETE";
      deleteButton.addEventListener("click", () => {
        divElement.remove();
      });
      divElement.appendChild(deleteButton);
    }
    // Create an input element for editing the property value
    const inputElement = document.createElement("input");
    inputElement.id = propertyName;
    inputElement.type = "text";
    inputElement.className = "form-control";
    inputElement.value = propertyValue;
    // Make ID and coordinates read-only
    if (
      propertyName == "ID" ||
      propertyName == "LATITUDE" ||
      propertyName == "LONGITUDE"
    ) {
      inputElement.readOnly = true;
    } else {
      inputElement.readOnly = false;
    }
    divElement.appendChild(inputElement);
    // Append the form group to the edit map table
    editMap_table.appendChild(divElement);
  }
  // Add an event listener to the "Save Changes" button
  const saveChanges = document.getElementById("saveChanges");
  saveChanges.addEventListener("click", () => {
    // Retrieve edited values from the form and update the item
    var editMap_table = document.getElementById("editMap_table");
    var labelList = editMap_table.querySelectorAll("label");
    var item = {};
    // Iterate through each label and update the item with the corresponding input value
    for (let i = 0; i < labelList.length; i++) {
      const label = labelList[i];
      const labelText = label.getAttribute("name");
      const inputName = document.getElementById(labelText).value;
      item[labelText] = inputName;
    }
    // Update the currentData.DATA array with the modified item
    for (let i = 0; i < this.currentData.DATA.length; i++) {
      if (this.currentData.DATA[i].ID == item.ID) {
        this.currentData.DATA[i] = item;
      }
    }
    // Update the allData array with the modified currentData
    for (let i = 0; i < this.allData.length; i++) {
      for (let j = 0; j < this.allData[i].DATA.length; j++) {
        if (this.allData[i].DATA[j].ID == this.currentData.DATA.ID) {
          this.allData[i] = this.currentData;
        }
      }
    }
    // Update the map, table, map buttons, and the published map
    this.addMarkers(this.currentData);
    this.updateTable(this.currentData);
    this.updateMapButtons();
    this.updatePublishedMap();
  });
}

To explain what this editRow method does:

  1. It toggles the visibility of the pop-up box for editing the row.

  2. It updates the title of the pop-up box to display the name of the map item being edited.

  3. It retrieves the list of property names from map item and iterates through them.

  4. For each property, it creates an input field in the editing interface and populates it with the property's value. It also adds a "DELETE" button for properties other than "ID", "NAME", "LATITUDE", and "LONGITUDE." The "DELETE" button allows removing properties from the item.

  5. When the "Save Changes" button is clicked, it retrieves the updated values of the properties from the input fields.

  6. It uses a Proxy object to allow getting and setting properties in the item.

  7. It updates the corresponding map item in the currently selected map data with the edited map item.

  8. It also updated the map data array with the modified currently selected data.

  9. It add the markers in the map, populates the table, and updates the map entries in the selection section.

  10. The map data array and currently selected map data are saved to the cookie.

  11. If the user is logged in, then the JSON representation of the map data array is sent to the backend so that the backend can write the JSON data to the map file of the user.

addLabelClassToEdit

// Method to add a new label class to the map editing interface
addLabelClassToEdit() {
  // Prompt the user for a label title
  var labelTitle = prompt("LABEL:");
  // Check if a valid label title is provided
  if (labelTitle !== null && labelTitle.trim() !== "") {
    // Get the container for label classes in the edit map form
    var labelClassesContainerEdit = document.getElementById(
      "labelClassesContainerEdit"
    );
    // Create a container for the new label class
    var labelClassContainer = document.createElement("div");
    labelClassContainer.classList.add("form-group");
    // Create a label element for displaying the label title
    var titleLabel = document.createElement("label");
    titleLabel.classList.add("form-label");
    titleLabel.innerHTML =
      "<div style='padding:5px;display:inline-block'>" +
      labelTitle.toUpperCase() +
      "</div>";
    titleLabel.textContent = labelTitle.toUpperCase();
    titleLabel.setAttribute("name", labelTitle.toUpperCase());
    // Create a delete button for removing the label class
    var deleteButton = document.createElement("div");
    deleteButton.setAttribute("class", "buttonFormElementDelete");
    deleteButton.textContent = "DELETE";
    deleteButton.addEventListener("click", () => {
      // Remove the label class container when the delete button is clicked
      labelClassContainer.remove();
    });
    // Create an input element for the value associated with the label class
    var valueInput = document.createElement("input");
    valueInput.setAttribute("type", "text");
    valueInput.setAttribute("class", "form-control");
    valueInput.setAttribute("id", labelTitle.toUpperCase());
    valueInput.setAttribute("value", "");
    // Append elements to the label class container
    labelClassContainer.appendChild(titleLabel);
    labelClassContainer.appendChild(deleteButton);
    labelClassContainer.appendChild(valueInput);
    // Append the label class container to the label classes container
    labelClassesContainerEdit.appendChild(labelClassContainer);
  }
}

To explain what this addLabelClassToEdit method does:

  1. It prompts the user for a label title, and it stores the user's.

  2. If the label title is not null and is not just whitespace, it proceeds to create and add an HTML element for this label in the pop-up box for editing the row.

  3. It creates a label element for the title.

  4. It creates an input field for the label's value, setting its initial value to an empty string.

  5. It creates a "DELETE" button for removing the label, which triggers the removal of the label when clicked.

closeEditMap

// Method to close the map editing interface
closeEditMap() {
  // Select the edit map form using its class
  const editMap = document.querySelector(".editMap");
  // Remove the "show" class to hide the edit map form
  editMap.classList.remove("show");
}

To explain what this closeEditMap method does:

  1. It will the close the pop-up box for editing the row.

Last updated