First commit
This commit is contained in:
81
Lab 7/card-creator.html
Executable file
81
Lab 7/card-creator.html
Executable 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
214
Lab 7/cards.css
Executable 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
154
Lab 7/createcard.js
Executable 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
95
Lab 7/modal.css
Executable 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;
|
||||
}
|
||||
Reference in New Issue
Block a user