First commit

This commit is contained in:
2025-09-10 14:42:57 -04:00
commit 1d12903e4e
65 changed files with 3587 additions and 0 deletions

81
Lab 7/card-creator.html Executable file
View File

@@ -0,0 +1,81 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Card Creator</title>
<link rel="stylesheet" href="cards.css">
<link rel="stylesheet" href="modal.css">
</head>
<body>
<aside class="modal fade" tabindex="-1" role="dialog" id="user-modal">
<div class="modal-dialog">
<form class="modal-content card-form" id="user-form">
<header class="modal-header">
<h3>Add/Edit User</h3>
</header>
<section class="modal-body">
<input name="guest-full" type="text" />
<input name="guest-short" type="text" />
<div class="invalid-feedback">Short name must be unique</div>
<input name="old-short" type="hidden" />
</section>
<footer class="modal-footer">
<button type="submit">Submit</button>
</footer>
</form>
</div>
</aside>
<nav>
<h1>Guest List</h1>
<ul id="guest-list" class="card-list">
<li id="alice">Alice <button class="btn-edit-guest" data-user="alice">Edit</button></li>
<li id="bob">Bob <button class="btn-edit-guest" data-user="bob">Edit</button></li>
<li id="charlie">Charlie <button class="btn-edit-guest" data-user="charlie">Edit</button></li>
</ul>
<button type="button" id="btn-add-guest">Add Guest</button>
</nav>
<main class="left-right pt-4">
<section id="form-area">
<h2>Invitation Details</h2>
<form class="card-form" id="preview-form">
<div class="form-group">
<label for="host">Host</label>
<input name="host" type="text" placeholder="Message sender">
</div>
<div class="form-group">
<label for="title">Title</label>
<input name="title" type="text" placeholder="Message title">
</div>
<div class="form-group">
<label for="date">Party Date</label>
<input name="date" type="date">
</div>
<div class="form-group">
<label for="message">Message</label>
<textarea name="message" placeholder="Message content" rows="8"></textarea>
</div>
<div class="form-buttons">
<button type="reset" id="reset-btn">Clear Form</button>
</div>
</form>
</section>
<section id="preview-area">
<div class="card-display">
<div class="card-page page-one">
<div class="title"><span id="preview-title">TITLE</span></div>
<div class="subtitle"><span id="preview-date">DATE</span></div>
</div>
<div class="card-page page-two">
<div class="to">Dear <span id="preview-guest">GUEST</span>,</div>
<div class="message"><span id="preview-message">YOUR MESSAGE HERE</span></div>
<div class="from">Sincerely, <span id="preview-host">HOST</span></div>
</div>
</div>
</section>
</main>
<script src="createcard.js"></script>
</body>
</html>

214
Lab 7/cards.css Executable file
View File

@@ -0,0 +1,214 @@
body {
font-family: 'Trebuchet MS', sans-serif;
background-color: #f0f0f0;
display: grid;
grid-template-rows: 1fr 9fr;
}
button {
border: 2px solid black;
border-radius: 5px;
padding: 0.2rem 0.2rem;
}
nav {
grid-row: 1;
grid-column:1;
ul {
list-style: none;
width: 90%;
}
li {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
max-width: 100%;
border:1px solid black;
background: white;
padding: 1vh 1vw;
text-wrap: wrap;
}
li.blank {
border: none;
background-color: #f0f0f0;
justify-content: flex-end;
}
li:not(.blank):hover {
background: #c0c0b0;
}
.btn-edit-guest {
z-index: 2;
}
#btn-add-guest {
float: right;
}
}
.left-right {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: center;
align-items: center;
height: 100vh;
grid-row: 1;
grid-column: 2;
}
#card-list {
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: center;
[contenteditable] {
border: 1px solid #ccc;
}
}
.card-form {
margin: 20px;
min-width: 500px;
.form-buttons {
display: flex;
flex-direction: row;
justify-content: flex-end;
align-items: baseline;
margin: 10px;
}
.form-buttons > button {
margin: 0px 5px;
}
.form-group {
margin: 10px;
width: auto;
display: grid;
grid-template-columns: 1fr 3fr;
grid-template-rows: 1fr;
grid-template-areas: "label input"
". feedback";
& label {
grid-area: label;
text-align: right;
padding-right: 10px;
}
& input,
& textarea {
grid-area: input;
}
}
}
.card-display {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
width: fit-content;
background-color: white;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
font-size: 18px;
font-family: 'Garamond', Times, serif;
.card-page {
display: grid;
grid-template-columns: 1fr;
grid-template-rows: 1fr 2fr 2fr 1fr;
width: 300px;
height: 400px;
padding: 40px;
}
.page-one {
background: rgb(240, 240, 240);
background: linear-gradient(270deg, rgba(240, 240, 240, 1) 0%, rgba(255, 255, 255, 1) 20%);
grid-template-areas:
"delete"
"title"
"subtitle"
".";
.title {
grid-area: title;
font-size: 24px;
font-weight: bold;
text-align: center;
justify-self: center;
align-self: center;
font-family: "Lucida Handwriting", cursive;
}
.subtitle {
grid-area: subtitle;
font-size: 20px;
font-weight: bold;
text-align: center;
justify-self: center;
align-self: start;
}
.delete-btn {
grid-area: delete;
justify-self: center;
align-self: start;
}
}
.page-two {
background: rgb(230, 230, 230);
background: linear-gradient(90deg, rgba(230, 230, 230, 1) 0%, rgba(255, 255, 255, 1) 5%);
grid-template-areas:
"to"
"message"
"message"
"from";
.to {
grid-area: to;
justify-self: start;
align-self: start;
}
.message {
grid-area: message;
white-space: pre-wrap;
}
.from {
grid-area: from;
justify-self: end;
align-self: end;
}
}
}
form .invalid-feedback {
color: red;
font-size: 0.8em;
grid-area: feedback;
visibility: hidden;
}
form .invalid {
border-color: red;
outline: none;
}
form .invalid ~ .invalid-feedback {
visibility: visible;
}

154
Lab 7/createcard.js Executable file
View File

@@ -0,0 +1,154 @@
/* Default users in the invitation list */
let userList = [
{ shortName: "Alice", fullName: "Alice N. Wonderland" },
{ shortName: "Bob", fullName: "Bob Malooga" },
{ shortName: "Charlie", fullName: "Charlie Bucket" },
];
/* STEP 1:
* Update a field of the card preview based on the current value of the
* preview-form field. For the preview-form, use the field's .value field.
* Find the corresponding preview-X field (i.e., for the preview-form host
* field, you should update preview-host's textContent. */
function updateField(field) {
document.querySelector(`#preview-${field.name}`).textContent = field.value;
}
/* STEP 1:
* When the Clear Form button is clicked, set all of the preview-area's
* fields back to the default values as given in the HTML file. */
function defaultPreview() {
document.querySelector("#preview-host").textContent = "HOST";
document.querySelector("#preview-title").textContent = "TITLE";
document.querySelector("#preview-message").textContent = "YOUR MESSAGE HERE";
document.querySelector("#preview-date").textContent = "DATE";
}
/* STEP 2:
* Update the preview of the card to show the selected user. Use the
* user parameter to search through the userList, trying to match the
* shortName field. You should do a case-insensitive match, meaning
* the shortName field should be passed to the toLowerCase() function.
* Get the preview-guest field and set its textContent to the user's
* fullName field. */
function previewCard(user) {
const guestPreview = document.querySelector("#preview-guest");
const listUser = userList.find(
(listUser) => listUser.shortName.toLowerCase() === user.id
);
guestPreview.textContent = listUser.fullName;
}
(function () {
// STEP 1:
// Get the preview form. For each of the text fields (host, title, message),
// add a keyup event listener to call updateField on that field name. For
// the date field, use a change event listener to do the same.
const form = document.querySelector("#preview-form");
const { host, title, message, date } = form;
host.addEventListener("keyup", updateField.bind(null, host));
title.addEventListener("keyup", updateField.bind(null, title));
message.addEventListener("keyup", updateField.bind(null, message));
date.addEventListener("change", updateField.bind(null, date));
// STEP 1:
// When the reset-btn button is clicked, call defaultPreview() to restore
// the preview fields to their original values.
document
.querySelector("#reset-btn")
.addEventListener("click", defaultPreview);
// STEP 2:
// For each of the existing users in the invitation list, add a click
// listener that will put their name into the card preview.
// HINT: You can grab all of them and use .forEach:
document
.querySelectorAll("#guest-list > li")
.forEach((userCard) =>
userCard.addEventListener("click", previewCard.bind(null, userCard))
);
// STEP 3:
// Grab all elements with the "modal" class. When clicked, remove the
// "show" class from the target element.
const modal = document.querySelector(".modal");
modal.addEventListener("click", (event) => {
if (event.target === modal) {
modal.classList.remove("show");
}
});
// STEP 3:
// For each of the btn-edit-guest buttons, add a click event listener. When
// clicked, get the data-user attribute from the clicked item and find the
// guest from the userList with that shortName. Store the guest's fullName
// and shortName into the user-form fields and show the modal. Also store
// the shortName in the old-short field and stop the event propagation.
const userForm = document.querySelector("#user-form");
document.querySelectorAll(".btn-edit-guest").forEach((btn) => {
btn.addEventListener("click", (event) => {
event.stopPropagation();
const dataUser = btn.dataset.user;
const listUser = userList.find(
(listUser) => listUser.shortName.toLowerCase() === dataUser
);
userForm["guest-full"].value = listUser.fullName;
userForm["guest-short"].value = listUser.shortName;
userForm["old-short"].value = listUser.shortName;
modal.classList.add("show");
});
});
// STEP 3:
// When the modal form is submitted, user the user-form's old-short field
// to find the user from the userList. Also look for the new short-name value
// in the userList. If the new short-name is found and it is not the same
// user as before (e.g., the new name conflicts with someone else), add the
// "invalid" class to the guest-short input and return.
//
// If the short-name is valid, get the guest-short and guest-full values from
// the form and update the user in the userList. Hide the modal.
//
// BONUS: Update the short name that appears in the guest-list item. Note
// that you'll need to first get the item, then access its firstChild. Otherwise,
// setting the textContent will accidentally remove the edit button.
userForm.addEventListener("submit", (event) => {
event.preventDefault();
const guestFull = userForm["guest-full"].value;
const guestShort = userForm["guest-short"].value;
const oldShort = userForm["old-short"].value;
const newId = guestShort.toLowerCase();
const oldId = oldShort.toLowerCase();
const listUserIndex = userList.findIndex(
(listUser) => listUser.shortName.toLowerCase() === oldId
);
for (let i = 0; i < userList.length; i++) {
const shortNameMatches = userList[i].shortName.toLowerCase() === newId;
if (i !== listUserIndex && shortNameMatches) {
userForm["guest-short"].classList.add("invalid");
return;
}
}
userList[listUserIndex].fullName = guestFull;
userList[listUserIndex].shortName = guestShort;
const oldListItem = document.querySelector(`#guest-list > li#${oldId}`);
oldListItem.id = newId;
const newNode = document.createElement("div");
newNode.textContent = guestShort;
oldListItem.replaceChild(newNode.firstChild, oldListItem.firstChild);
oldListItem.firstElementChild.dataset.user = newId;
userForm["guest-short"].classList.remove("invalid");
modal.classList.remove("show");
});
})();

95
Lab 7/modal.css Executable file
View File

@@ -0,0 +1,95 @@
/* Modal interactions based on Bootstrap */
.modal {
/* The modal covers the entire screent */
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
overflow-x: hidden;
overflow-y: auto;
/* The modal has a dark color */
background-color: rgb(33, 37, 41, .5);
}
.fade:not(.show) {
/* When not shown, the modal is see-through and (important!) doesn't capture clicks */
pointer-events: none;
transition: opacity .15s linear;
opacity: 0;
z-index: -1;
}
.fade {
/* Controls the speed of the fade in/out transitions */
transition: opacity .15s linear;
z-index: 10;
}
.show {
/* When shown, make the dark gray (somewhat transparent) background solid */
opacity: 1;
}
/* Controls the drop-down motion of the dialog box */
.modal-dialog {
display: flex;
justify-content: center;
width: 100%;
pointer-events: none;
position: relative;
}
/* When the modal gets the show class added, drop down from 0 to 10vh from the top */
.modal.show .modal-dialog {
transition: transform .3s ease-out;
transform: translate(0,10vh);
}
/* When the modal is dismissed, raise it before making it disappear */
.modal.fade:not(.show) .modal-dialog {
transition: transform .3s ease-out;
transform: translate(0,-10vh);
}
/* Create the box around the modal content */
.modal-content {
pointer-events: auto;
background: white;
opacity: 1;
min-height: 10vh;
min-width: 30vw;
border: 1px solid rgba(0,0,0,.2);
border-radius: .3rem;
background-clip: padding-box;
}
.modal-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 1rem;
border-bottom: 1px solid gray;
}
.modal-header * {
margin: 0;
}
.modal-body {
padding: 0rem 1rem;
}
.modal-footer {
display: flex;
align-items: center;
justify-content: flex-end;
border-top: 1px solid gray;
padding: 1rem;
}