Parkplatzverwaltungssystem in JavaScript

Oct 20 2020

Hypothese

Es gibt einen kostenpflichtigen Parkplatz mit folgenden Preisen: $1 for the first hour and $0,5 für jede weitere Stunde. Die Parkkapazität beträgt 10 Stellplätze.

Erforderlich

Erstellen Sie ein Aktivitätsmanagementsystem für das Parken mit den folgenden Merkmalen:

  • Jedes Parkintervall wird auf die nächste Stunde aufgerundet.
  • Wenn ein neues Auto auf den Parkplatz fährt, nimmt das Parksystem die Zulassungsnummer des Autos auf.
  • Wenn ein Auto den Parkplatz verlässt, gibt das System abhängig von der Dauer des Aufenthalts eine Zusammenfassung aus. Welche Informationen halten Sie für wesentlich als Teil der Zusammenfassung?
  • Jeder Kunde kann die Liste der zu einem bestimmten Zeitpunkt auf dem Parkplatz geparkten Autos einsehen.

Technische Voraussetzungen

  • Die Implementierung erfolgt mit JavaScript, wobei alle Daten im Speicher gespeichert werden, ohne dass eine permanente Speicherung erforderlich ist.
  • Schreiben Sie sauberen Code.
  • Erstellen Sie eine einfache Benutzeroberfläche, über die Sie Daten vom Computer eingeben und lesen können. Zeigen Sie die Daten auf derselben Seite an.

Lösung

Ich habe Bootstrap 4 für die Ästhetik verwendet. Das Skript selbst verwendet nur JavaScript. jQuery ist für die korrekte Funktion von Bootstrap enthalten.

let cars = [];
const addCarButton = document.querySelector('#carButton');
const minLicenseeLength = 7;
const payPerHour = 0.5;
const payFirstHour = 1;
const totalPlaces = 10;

const formatDate = (date) => {
  var hours = date.getHours();
  var minutes = date.getMinutes();
  var ampm = hours >= 12 ? 'PM' : 'AM';
  hours = hours % 12;
  hours = hours ? hours : 12;
  minutes = minutes < 10 ? '0' + minutes : minutes;
  var strTime = hours + ':' + minutes + ' ' + ampm;
  return date.getDate() + "/" + (date.getMonth() + 1) + "/" + date.getFullYear() + " " + strTime;
}

const secondsToHours = (d) => {
  d = Number(d);
  let h = Math.ceil(d / 3600);
  return h;
}

const renterTable = () => {
  let results = '';
  for (var i = 0; i < cars.length; i++) {
    let licensee = cars[i].licensee;
    let arrival = formatDate(cars[i].arrival);
    let leave = cars[i].leave === '-' ? '-' : formatDate(cars[i].leave);

    results += `<tr>
          <td>${licensee}</td> <td>${arrival}</td>
          <td>${leave}</td> <td>${showStatus(cars[i])}</td>
          <td class="text-right">${makeBill(cars[i])}</td> <td class="text-right"> <button data-row="${i}" onclick="showSummary(event)" data-toggle="modal" data-target="#myModal" class="btn btn-sm btn-success">Summary</button>
                    </td>
        </tr>`;
  }
  document.querySelector("#parking tbody").innerHTML = results;
}

const showStatus = (car) => {
  return car.isParked ? "Parked" : "Has left";
}

const changeStatus = (event) => {
  cars[event.target.dataset.row].isParked = false;
}

const setLeaveTime = (event) => {
  cars[event.target.dataset.row].leave = new Date(Date.now());
}

const countAvailablePlaces = (event) => {
  document.querySelector('#placesCount').innerHTML = totalPlaces - cars.length;
}

const setClassForBadge = () => {
  let badgeClassName = cars.length == totalPlaces ? 'badge badge-danger' : 'badge badge-success';
  document.querySelector('#placesCount').setAttribute('class', badgeClassName);
}

const calculateHoursBilled = (car) => {
  let arrivedAt = new Date(car.arrival).getTime();
  let leftAt = new Date(car.leave).getTime();
  return secondsToHours((leftAt - arrivedAt) / 1000); //duration in seconds
}

const makeBill = (car) => {
  let hoursBilled = calculateHoursBilled(car);
  let billValue = car.isParked ? "-" : "$" + (payFirstHour + (hoursBilled - 1) * payPerHour); return billValue; } const printSummary = (event) => { let car = cars[event.target.dataset.row]; let sumarryTable = `<table class="table table-bordered m-0"> <tr> <td class="font-weight-bold">Registration number</td> <td>${car.licensee}</td>
        </tr>
        <tr>
          <td class="font-weight-bold">Arrival</td>
          <td>${formatDate(car.arrival)}</td> </tr> <tr> <td class="font-weight-bold">Departure</td> <td>${formatDate(car.leave)}</td>
        </tr>
        <tr>
          <td class="font-weight-bold">Billable hours</td>
          <td>${calculateHoursBilled(car)}</td> </tr> <tr> <td class="font-weight-bold">Bill value</td> <td>${makeBill(car)}</td>
      </tr></table>`;

  document.querySelector('#modalBody').innerHTML = sumarryTable;
}

const showSummary = (event) => {
  changeStatus(event);
  setLeaveTime(event);
  renterTable();
  printSummary(event);

  //Free the parking place, 3 seconds after the summary is released
  setTimeout(function() {
    freeSpot(event);
  }, 3000);
}

const addCar = () => {
  let newLicensee = document.querySelector("#carValue").value;

  let newCar = {
    licensee: newLicensee,
    arrival: new Date(),
    leave: '-',
    isParked: true
  }

  // Add new car to the cars array

  document.querySelector('#message').style.display = 'none';
  if (newLicensee.length >= minLicenseeLength && cars.length < totalPlaces) {
    cars.unshift(newCar);
  } else {
    if (newLicensee.length < minLicenseeLength) {
      document.querySelector('#message').style.display = 'block';
    }
  }

  if (cars.length == totalPlaces) {
    document.querySelector('#carButton').setAttribute('disabled', true);
  }

  setClassForBadge();

  //Update places count
  countAvailablePlaces(event);

  // Empty text box
  document.querySelector("#carValue").value = '';

  // Render the table
  renterTable();
}

const freeSpot = (event) => {
  cars.splice(event.target.dataset.row, 1);
  setClassForBadge();

  if (cars.length == totalPlaces) {
    document.querySelector('#carButton').setAttribute('disabled');
  } else {
    document.querySelector('#carButton').removeAttribute('disabled');
  }

  // Render Table again after delete 
  renterTable();
  //Update places count
  countAvailablePlaces(event);
}

// Add new car to the array
addCarButton.addEventListener('click', addCar);

// Render Table
renterTable();

//Show places count at page load
countAvailablePlaces(event);
#addForm {
  position: relative;
}

#message {
  display: none;
  position: relative;
  font-size: 10px;
  position: absolute;
}

#placesCount {
  font-size: 90%;
}

#parking th,
#parking td {
  white-space: nowrap;
  font-size: 14px;
}

#myModal .font-weight-bold {
  font-weight: 500 !important;
}
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.16.0/umd/popper.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>

<div class="container">
  <div class="card my-3">
    <div class="card-header px-3 d-flex">
      <h5 class="m-0">Parking management</h5>
      <div class="ml-auto">
        <span id="placesCount" class="badge badge-success"></span> available places
      </div>
    </div>
    <div class="card-body p-0">
      <div class="input-group p-2" id="addForm">
        <input type="text" class="form-control" id="carValue" placeholder="Registration number">
        <div class="input-group-append">
          <button id="carButton" class="btn btn-sm btn-success">Park car</button>
        </div>
        <p id="message" class="text-danger m-0">Registration number invalid</p>
      </div>
      <div class="table-responsive">
        <table id="parking" class="table table-striped m-0">
          <thead>
            <tr>
              <th>Registration no</th>
              <th>Arrival</th>
              <th>Departure</th>
              <th>Status</th>
              <th class="text-right">Bill</th>
              <th class="text-right">Actions</th>
            </tr>
          </thead>
          <tbody></tbody>
        </table>
      </div>
    </div>
  </div>
</div>

<div class="modal" id="myModal">
  <div class="modal-dialog">
    <div class="modal-content">
      <div class="modal-header">
        <h6 class="modal-title">Sumar Parcare</h6>
        <button type="button" class="close" data-dismiss="modal">&times;</button>
      </div>
      <div id="modalBody" class="modal-body"></div>
    </div>
  </div>
</div>

Sehen Sie Probleme mit dieser Anwendung, auch unter Sicherheitsgesichtspunkten ?

Vielen Dank!

Antworten

5 CertainPerformance Oct 21 2020 at 00:50

Lieberconst über letund var. Wenn Sie eine Variable mit deklarieren let, geben Sie jedem Leser des Codes an, dass die Variable jederzeit neu zugewiesen werden kann. Wenn die Neuzuweisung nicht wirklich erforderlich ist, damit der Code funktioniert, führt dies zu unerwünschtem kognitiven Aufwand - es kann eine ständige Sorge im Hinterkopf sein. "Dieser Variablenname kann jederzeit neu zugewiesen werden, worauf es sich jetzt bezieht ist möglicherweise nicht das, was es ursprünglich zugewiesen wurde. * Siehehttps://softwareengineering.stackexchange.com/questions/278652/how-much-should-i-be-using-let-vs-const-in-es6

Am besten varganz zu vermeiden , es hat zu viele Probleme, um es heutzutage wert zu sein - vor allem hat es einen nicht intuitiven Funktionsumfang anstelle eines Blockumfangs.

String Polsterung Wenn Sie Pad wollen den Beginn einer Zeichenfolge mit einigen führenden Zeichen dem String eine bestimmte Länge machen ( zum Beispiel hier, 9und 10auf 09und 10jeweils), die am besten geeignete Methode zu verwenden istpadStart

Vorlagenliterale eignen sich hervorragend, wenn Sie mehrere Variablen in eine Zeichenfolge interpolieren müssen. Oft sind sie dem Stil von vorzuziehen ' + someVar + '.

Unter Berücksichtigung der oben genannten 3 Tipps kann die formatDateFunktion wie folgt umgestaltet werden:

const formatDate = (date) => {
  const hoursMilitary = date.getHours();
  const minutesToDisplay = String(date.getMinutes()).padStart('0', 2);
  const ampm = hoursMilitary >= 12 ? 'PM' : 'AM';
  const hoursToDisplay = (hoursMilitary % 12) || 12;
  const strTime = `${hoursToDisplay}:${minutesToDisplay} ${ampm}`; return `${date.getDate()}/${date.getMonth() + 1}/${date.getFullYear()} ${strTime}`;
}

Sekunden bis Stunden können viel präziser und ohne Neuzuweisung des Parameters gestaltet werden:

const secondsToHours = d => Math.ceil(d / 3600);

/zwingt Nicht-Zahlen zu Zahlen; Sie nicht anrufen müssen Numberauf derste.

renterTableHier können viele Verbesserungen vorgenommen werden. Erstens ist der Funktionsname nicht ganz intuitiv - was macht etwas, das als renterTable(Substantiv) bezeichnet wird? Ist es eine Variable, die ein HTMLTableElement enthält? Erstellt es eine Tabelle und gibt sie zurück? Nein, die Tabelle im DOM wird anhand der Daten im carsArray neu gerendert. Nennen Sie es stattdessen renderTableoder renderRenterTable(Verb).

Das Erstellen von HTML durch Verketten von Benutzereingaben ist unsicher. Sie haben:

  results += `<tr>
        <td>${licensee}</td>
        <td>${arrival}</td> <td>${leave}</td>
        <td>${showStatus(cars[i])}</td> <td class="text-right">${makeBill(cars[i])}</td>
        <td class="text-right">
          <button data-row="${i}" onclick="showSummary(event)" data-toggle="modal" data-target="#myModal" class="btn btn-sm btn-success">Summary</button>
                  </td>
      </tr>`;
}
document.querySelector("#parking tbody").innerHTML = results;

Dies ermöglicht die Ausführung von beliebigem Code. Stellen Sie sich vor, jemand sagte:

Versuchen Sie, das folgende Nummernschild anzubringen, wenn Sie ein Auto registrieren. Sie werden nicht glauben, was als nächstes passiert!

<img src onerror='alert("evil")'>

wo das alert("evil")durch alles ersetzt werden kann, was der Phisher will. Dann könnten einige nicht allzu intelligente Benutzer darauf hereinfallen und ihre Anmeldeinformationen / Kontoguthaben / usw. kompromittieren lassen.

Dies kann behoben werden, indem sichergestellt wird, dass das licenseeZeichen zuerst nur gültige Zeichen enthält, z. B. turn:

let newLicensee = document.querySelector("#carValue").value;
if (!/^[a-z\d]+$/.test(newLicensee)) {
  // display error message to user: license invalid
  return;
}

Aber das ist immer noch nur ein Patch-Job. Wenn das Skript in Zukunft entwickelt wird, habe ich Angst, eine Eingabe versehentlich nicht zu bereinigen, bevor sie in das DOM eingefügt wird. Wenn Benutzereingaben erforderlich sind, würde ich es vorziehen, die Zeichenfolgeninterpolation vollständig mit HTML zu vermeiden und stattdessen die textContentin das Element einzufügen, nachdem das Element in das Dokument eingefügt wurde, z.

const tr = document.querySelector("#parking tbody").appendChild(document.createElement('tr'));
tr.innerHTML = `
  <td></td>
  <td></td>
  <td></td>
  ...
`;
tr.children[0].textContent = licensee;
tr.children[1].textContent = arrival;
tr.children[2].textContent = leave;
// ...

Oder, noch besser , für größere Projekte sollten Sie ein Framework verwenden, das die präzise Interpolation von Eingaben in das DOM ermöglicht, wie z. B. React:

const CarRow = (carInfo) => (
  <tr>
    <td>{carInfo.licensee}</td>
    <td>{carInfo.arrival}</td>
    <td>{carInfo.leave}</td>
    ...

All dies gilt auch für die sumarryTable, die die gleiche Art von verketteter HTML-Vulnurabilität aufweist

Rechtschreibung Rechtschreibung ist bei der Programmierung wichtig - richtig geschriebene Variablennamen helfen, Fehler zu vermeiden. Nennen Sie es summaryTablestattdessen. Das Wort "Lizenznehmer" bezieht sich auch auf die Person, die eine Lizenz besitzt, aber Ihre Variable bezieht sich auf die Kennzeichenfolge. Erwägen Sie, es licenseIDstatt zu nennen licensee; es wird weniger verwirrend sein.

VermeideninnerHTML Verwenden Sie aus ähnlichen Gründen wie oben nur, innerHTMLwenn Sie HTML-Markups einfügen müssen. Wenn Sie nur Text in ein Element einfügen möchten , verwenden Sie textContent; Es ist sicherer, schneller und hat keine Probleme mit Zeichen, die in HTML eine besondere Bedeutung haben. Diese:

document.querySelector('#placesCount').innerHTML = totalPlaces - cars.length;

kann sein

document.querySelector('#placesCount').textContent = totalPlaces - cars.length;

(Könnte auch den Verweis auf speichern, #placesCountanstatt ihn bei jedem Aufruf der Funktion erneut auszuwählen.)

Vermeiden Sie Inline-Handler Innerhalb des Zeilen-Markups haben Sie:

<button data-row="${i}" onclick="showSummary(event)" data-toggle="modal" data-target="#myModal" class="btn btn-sm btn-success">Summary</button>

Inline-Handler haben zu viele Probleme , als dass sie sich in modernem Code lohnen könnten. Sie erfordern globale Umweltverschmutzung und haben unter anderem eine verrückte Zielfernrohrkette. Fügen Sie den Ereignis-Listener stattdessen ordnungsgemäß mit JavaScript hinzu. Hängen Sie den Listener entweder an jede Schaltfläche an, wenn die Schaltfläche in das DOM eingefügt wird, oder verwenden Sie, möglicherweise eine bessere Option, die Ereignisdelegierung. Siehe auch die folgenden Kommentare:

table.addEventListener('click', (event) => {
  if (!event.target.matches('button')) {
    return;
  }
  // I renamed this from "row" to "rowIndex" to make the variable name more precise;
  // it's not an actual row, it's only an index
  const { rowIndex } = event.target.dataset;
  // Now that the rowIndex has been extracted, grab the car,
  // then pass the car along instead of extracting it inside each function:
  const car = cars[rowIndex];
  changeStatus(car);
  setLeaveTime(car);
  
  renderRenterTable();
  printSummary(car);

  //Free the parking place, 3 seconds after the summary is released
  setTimeout(function() {
    freeSpot(rowIndex);
  }, 3000);
});

Wenn möglich, bevorzugen Sie die Punktnotation , sie ist prägnanter und leichter zu lesen - das haben Sie

document.querySelector('#placesCount').setAttribute('class', badgeClassName);
// ...
if (cars.length == totalPlaces) {
  document.querySelector('#carButton').setAttribute('disabled');
} else {
  document.querySelector('#carButton').removeAttribute('disabled');
}

Diese können geändert werden in:

document.querySelector('#placesCount').className = badgeClassName;
// ...
document.querySelector('#carButton').disabled = cars.length === totalPlaces;

( Denken Sie daran, immer === anstelle von zu verwenden == ; ==hat seltsame Regeln, die Leser eines Skripts nicht verstehen müssen sollten, um die implementierte Logik zu verstehen.)

Moderne Syntax Im Quellcode werden moderne Syntax und moderne Methoden verwendet, was gut ist - es sorgt für sauberen, präzisen und lesbaren Code. Verwenden Sie für Produktionscode Babel und Polyfills, um die Quelle automatisch auf ES5 zu übertragen, damit veraltete Browser wie IE den Code ebenfalls ausführen können.