Bookmark.vue
This is where the users can see and interact with their bookmarked maps.
import
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
export default// Exporting the Vue component
export default {}Inside this is where we define data, methods, computed properties, and lifecycle hooks.
components
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// Data properties of the component
data() {
return {
domainBackend: "",
domainFrontend: "",
map: null,
allData: [],
};
}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
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);
// Checking if the user is logged in by checking the availability of a cookie
if (!this.$cookie.isCookieAvailable("L_userId")) {
alert("LOGIN REQUIRED.");
// Redirecting to the login page if not logged in
location.replace(this.domainFrontend + "/login");
} else {
// Setting backend domain from the Vuex store
this.domainBackend = this.$store.state.domainBackend;
// Fetching and initializing bookmarks when the component is mounted
this.getBookmark();
}
}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:
It scrolls the window to the top so the user sees the content at the beginning of the page.
If not logged in, then it will alert the user to log in using an account.
If logged in, it gets the backend domain from what is defined in the Vuex and get the JSON representation of the map data array of all the bookmarked maps from the backend.
methods
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
storeBookmark
storeBookmark// Method to store bookmarks on the backend server
storeBookmark() {
// Prepare the data to be sent in the request
const request = {
jsonData: JSON.stringify({
USER_ID: this.$cookie.getCookie("L_userId"), // Get the user ID from cookies
DATA: this.allData, // Include the bookmark data to be stored
}),
};
// Send a POST request to the backend API endpoint for storing bookmarks
axios
.post(this.domainBackend + "/api/storeBookmark", request)
.then((response) => {
// Upon a successful response from the server
if (response.data.SUCCESS == 1) {
// Do nothing or handle success as needed
}
});
}To explain what this storeBookmark method does:
It will send the JSON representation of the map data array along with the user's ID to the backend.
The backend will write the JSON onto the user's bookmark file.
getBookmark
getBookmark// Method to retrieve bookmarks from the backend server
getBookmark() {
// Prepare the request object containing the user ID
const request = {
userId: this.$cookie.getCookie("L_userId"), // Get the user ID from cookies
};
// Send a POST request to the backend API endpoint for retrieving bookmarks
axios
.post(this.domainBackend + "/api/getBookmark", request)
.then((response) => {
// Upon a successful response from the server
if (response.data.SUCCESS == 1) {
// Set the received bookmark data to the component's 'allData' property
this.allData = response.data.DATA;
// Check if there are no bookmarks
if (this.allData.length == 0) {
// If no bookmarks, initialize the map
this.initMap();
} else {
// If bookmarks exist, add markers to the map based on the data
this.addMarkers(this.allData);
}
// Update the table with the bookmark data
this.updateTable(this.allData);
}
});
}To explain what this getBookmark method does:
It will send the user's ID to the backend.
The backend will respond with the JSON representation of the map data array of all the bookmarked maps from the backend.
If the map data array is empty, then it will just initialize an empty map to display, and populate the table with rows.
If the map data array is not empty, then it will add the markers in the map, and populate the table with rows.
initMap
initMap// Method to initialize the map
initMap() {
// Create a new Google Map and assign it to the component's 'map' property
this.map = new google.maps.Map(document.getElementById("map"), {
center: { lat: 0, lng: 0 }, // Set the initial center of the map to latitude 0, longitude 0
zoom: 2, // Set the initial zoom level of the map
markers: [], // Initialize an empty array to store markers on the map
});
}To explain what this initMap method does:
It initializes a Google Map on the page.
addMarkers
addMarkers// Method to add markers to the map based on the provided data
addMarkers(data) {
// Check if there is data to display
if (data.length != 0) {
// Reset the map by setting it to null and initializing a new map
this.map = null;
this.initMap();
// Initialize an array to store markers
const markers = [];
// Variable to keep track of the current open info window
let currentInfoWindow = null;
// Loop through each data point (place) to add markers to the map
data.forEach((place) => {
// Create a new Google Maps Marker for each place
const marker = new google.maps.Marker({
position: {
lat: parseFloat(place.LATITUDE),
lng: parseFloat(place.LONGITUDE),
},
map: this.map, // Set the marker on the map
title: place.NAME,
});
// Add a click event listener to each marker
marker.addListener("click", () => {
// Close the current info window if one is open
if (currentInfoWindow) {
currentInfoWindow.close();
}
// Create a div element for the info window content
const mainDiv = document.createElement("div");
// Create div elements for the 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 buttons for sharing, locating, and editing
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>';
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>';
// Append the buttons to the icons holder
iconsHolderDiv.appendChild(shareButtonDiv);
iconsHolderDiv.appendChild(locationButtonDiv);
iconsHolderDiv.appendChild(editButtonDiv);
// Append the title, coordinates, and icons holders to the main div
mainDiv.appendChild(titleHolderDiv);
mainDiv.appendChild(coordinateHolderDiv);
mainDiv.appendChild(iconsHolderDiv);
// Create a new Google Maps InfoWindow with the main div as content
const infowindow = new google.maps.InfoWindow({
content: mainDiv,
});
// Open the info window on the map for the clicked marker
infowindow.open(this.ETM_map, marker);
// Update the currentInfoWindow variable
currentInfoWindow = infowindow;
});
// Store the marker in the markers array
markers.push(marker);
// Update the map property in the component
const map = this.map;
this.map = map;
});
// Update the map center and zoom level based on the first data point
const map = this.map;
map.setCenter({
lat: parseFloat(data[0].LATITUDE),
lng: parseFloat(data[0].LONGITUDE),
});
map.setZoom(10);
// Create a MarkerClusterer to cluster the markers on the map
const markerCluster = new MarkerClusterer({
map: map,
});
// Add each marker to the marker cluster
markers.forEach((marker) => {
markerCluster.addMarker(marker);
});
// Set the markers property on the map with the array of markers
map.markers = markers;
// Update the map property in the component
this.map = map;
} else {
// If there is no data, reset the map
this.map = null;
this.initMap();
}
}To explain what this addMarkers method does:
if the map data is not empty, it proceeds with the following steps; otherwise, it initializes the map and sets it to null.
It initializes a Google Map and creates an empty array to hold markers.
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.
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.
The markers are clustered together for better visualization when there are multiple markers in close proximity.
updateTable
updateTable// Method to update the table with data
updateTable(data) {
// Create a deep copy of the data to avoid modifying the original data
const duplicate_data = JSON.parse(JSON.stringify(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 copied data
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 in the HTML
const tableHeadRow = document.getElementById("table-tr");
const tableBody = document.getElementById("mtsTbody");
// Clear the existing content of the table head row and table body
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 in the data to collect unique property names
proxy_data.forEach((item) => {
Object.keys(item).forEach((propertyName) => {
allProperties.add(propertyName);
});
});
// Create table header cells for each unique property name
allProperties.forEach((propertyName) => {
const th = document.createElement("th");
th.textContent = propertyName;
tableHeadRow.appendChild(th);
});
// Add "EDIT" and "OTHER ACTION" columns to the table header
let th = document.createElement("th");
th.textContent = "EDIT";
tableHeadRow.appendChild(th);
th = document.createElement("th");
th.textContent = "OTHER ACTION";
tableHeadRow.appendChild(th);
// Iterate through each item in the data to populate the table rows
proxy_data.forEach((item) => {
const row = document.createElement("tr");
// Create table cells for each property in the item
allProperties.forEach((propertyName) => {
const cell = document.createElement("td");
cell.style.textAlign = "center";
const propertyValue = item[propertyName];
// Display the property value in the table cell
if (propertyValue !== undefined && propertyValue !== null) {
cell.textContent = propertyValue;
} else {
// Handle the case where the property value is undefined or null
// Optionally, you can set a background color or add other styling
// cell.style.backgroundColor = "#adadad";
}
// Append the cell to the table row
row.appendChild(cell);
});
// Create an "EDIT" cell with an edit button for each item
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, map, and share buttons
const otherCell = document.createElement("td");
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 button to open Google Maps for the item's coordinates
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 button to share the item's information
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 the "OTHER ACTION" cell to the table row
row.appendChild(otherCell);
// Append the table row to the table body
tableBody.appendChild(row);
});
}
}To explain what this updateTable method does:
It creates a deep copy of the map data using JSON serialization and parsing. This is done to avoid modifying the original data.
It removes the "WKT" property from each object in map data if it exists.
It creates a Proxy object of the map data that allows getting and setting properties while ensuring they are always set successfully.
It selects the table's header row and body elements in the HTML document.
It clears the existing content of the table's header row and body.
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.
It iterates over the properties of the objects to create table header cells and appends them to the header row.
For each object in the Proxy object, it generates a new row in the table body, filling in cells with property values.
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.
deleteRow
deleteRow// Method to delete a row (item) from the data, update the map and table, and store the changes
deleteRow(item) {
// Iterate through allData to find the index of the item to be deleted
for (let i = 0; i < this.allData.length; i++) {
// Check if the ID of the current item matches the ID of the item to be deleted
if (this.allData[i].ID == item.ID) {
// Remove the item from the allData array
this.allData.splice(i, 1);
// Exit the loop since the item has been found and removed
break;
}
}
// Check if allData is now empty
if (this.allData.length == 0) {
// If allData is empty, initialize the map with default settings
this.initMap();
} else {
// If allData is not empty, update the map with the remaining data
this.addMarkers(this.allData);
}
// Update the table with the modified allData
this.updateTable(this.allData);
// Store the updated data (allData) on the backend
this.storeBookmark();
}To explain what this deleteRow method does:
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.
Once the matching item is found, it removes it from the currently selected map data array.
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.
If there are no more items left in the currently selected map data, it sets the map to null and initializes a new map.
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.
The map data array and currently selected map data are saved to the cookie.
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
shareRow// Method to prepare and display a shareable link for a specific item
shareRow(item) {
// Get the HTML element with the id "urlLink"
const urlLink = document.getElementById("urlLink");
// Create a shareable link using the item's latitude and longitude
urlLink.value =
"https://www.google.com/maps/search/?api=1&query=" +
item["LATITUDE"] +
"%2C" +
item["LONGITUDE"];
// Get the HTML element with the id "containerShareToSocial"
const containerShareToSocial = document.getElementById(
"containerShareToSocial"
);
// Display the container for sharing options (e.g., social media)
containerShareToSocial.style.display = "block";
}To explain what this shareRow method does:
A pop-up box will be shown where the user can copy the Google Maps link of the map item.
editRow
editRow// Method to display and handle the editing of a row (item) in the map and table
editRow(item) {
// Get the HTML element with the class "editMap"
const editMap = document.querySelector(".editMap");
// Toggle the "show" class to display or hide the editing interface
editMap.classList.toggle("show");
// Set the title of the editing interface to the name of the item
document.getElementById("editMap_Title").innerHTML =
"<strong>" + item["NAME"] + "</strong>";
// Get the HTML element with the class "editMap_table"
const editMap_table = document.querySelector(".editMap_table");
// Clear the content of the editing table
editMap_table.innerHTML = "";
// Get the HTML element with the id "labelClassesContainerEdit"
const labelClassesContainerEdit = document.getElementById(
"labelClassesContainerEdit"
);
// Clear the content of the label classes container
labelClassesContainerEdit.innerHTML = "";
// Create a deep copy of the item to avoid modifying the original data directly
const modifiedItem = JSON.parse(JSON.stringify(item));
// Get the list of property names in the modifiedItem
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 div element to hold each form element
const divElement = document.createElement("div");
divElement.className = "form-group";
// Create a label element 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);
// Check if the property is one of the restricted properties (ID, NAME, LATITUDE, LONGITUDE)
if (
propertyName == "ID" ||
propertyName == "NAME" ||
propertyName == "LATITUDE" ||
propertyName == "LONGITUDE"
) {
// Do nothing for these properties
} else {
// For other properties, create a delete button and attach an event listener to remove the form element
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 the property value
const inputElement = document.createElement("input");
inputElement.id = propertyName;
inputElement.type = "text";
inputElement.className = "form-control";
inputElement.value = propertyValue;
// Set readOnly property for certain properties
if (
propertyName == "ID" ||
propertyName == "LATITUDE" ||
propertyName == "LONGITUDE"
) {
inputElement.readOnly = true;
} else {
inputElement.readOnly = false;
}
// Append the input element to the div element
divElement.appendChild(inputElement);
// Append the div element to the editing table
editMap_table.appendChild(divElement);
}
// Get the HTML element with the id "saveChanges"
const saveButton = document.getElementById("saveChanges");
// Add an event listener to the save button to handle changes
saveButton.addEventListener("click", () => {
// Get the current state of the editing table
var editMap_table = document.getElementById("editMap_table");
var labelList = editMap_table.querySelectorAll("label");
var item = {};
// Iterate through each label (property) in the editing table
for (let i = 0; i < labelList.length; i++) {
const label = labelList[i];
const labelText = label.getAttribute("name");
// Get the value from the corresponding input element
const inputName = document.getElementById(labelText).value;
item[labelText] = inputName;
}
// Get the label classes container
var labelClassesContainerEdit = document.getElementById(
"labelClassesContainerEdit"
);
// Iterate through label classes and update the item
labelList = labelClassesContainerEdit.querySelectorAll("label");
for (let i = 0; i < labelList.length; i++) {
const label = labelList[i];
const labelText = label.getAttribute("name");
// Get the value from the corresponding input element
const inputName = document.getElementById(labelText).value;
item[labelText] = inputName;
}
// Create a proxy for the item to intercept and handle property changes
const proxy_item = new Proxy(item, {
get: function (target, prop) {
return target[prop];
},
set: function (target, prop, value) {
target[prop] = value;
return true;
},
});
// Update the corresponding item in the allData array with the edited item
for (let i = 0; i < this.allData.length; i++) {
if (this.allData[i].ID == proxy_item.ID) {
this.allData[i] = proxy_item;
}
}
// Update the map with the modified data
this.addMarkers(this.allData);
// Update the table with the modified data
this.updateTable(this.allData);
// Store the changes to the backend
this.storeBookmark();
});
}To explain what this editRow method does:
It toggles the visibility of the pop-up box for editing the row.
It updates the title of the pop-up box to display the name of the map item being edited.
It retrieves the list of property names from map item and iterates through them.
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.
When the "Save Changes" button is clicked, it retrieves the updated values of the properties from the input fields.
It uses a Proxy object to allow getting and setting properties in the item.
It updates the corresponding map item in the currently selected map data with the edited map item.
It also updated the map data array with the modified currently selected data.
It add the markers in the map, populates the table, and updates the map entries in the selection section.
The map data array and currently selected map data are saved to the cookie.
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
addLabelClassToEdit// Method to add a label class to the editing interface
addLabelClassToEdit() {
// Prompt the user for a label title
var labelTitle = prompt("LABEL:");
// Check if the user entered a non-empty label title
if (labelTitle !== null && labelTitle.trim() !== "") {
// Get the container for label classes in the editing interface
var labelClassesContainerEdit = document.getElementById(
"labelClassesContainerEdit"
);
// Create a container div for the new label class
var labelClassContainer = document.createElement("div");
labelClassContainer.classList.add("form-group");
// Create a label element for the new label class
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 of 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 the 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:
It prompts the user for a label title, and it stores the user's.
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.
It creates a label element for the title.
It creates an input field for the label's value, setting its initial value to an empty string.
It creates a "DELETE" button for removing the label, which triggers the removal of the label when clicked.
closeEditMap
closeEditMap// Method to close the editing map interface
closeEditMap() {
// Select the editing map container using its class
const editMap = document.querySelector(".editMap");
// Remove the "show" class to hide the editing map interface
editMap.classList.remove("show");
}To explain what this closeEditMap method does:
It will the close the pop-up box for editing the row.
Last updated