├── .vscode └── settings.json ├── README.md ├── assets ├── css │ ├── calendar.css │ ├── modal.css │ └── styles.css ├── img │ ├── DV.ico │ ├── calendar.jpg │ └── vader-pointer.cur └── js │ └── main.js └── index.html /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "liveServer.settings.port": 5501 3 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vanilla JS Calendar 2 | 3 | #### A calendar that shows the current month and allows the user to add events on the selected date 4 | 5 | ## Developed with 6 | 7 | * HTML 8 | * CSS 9 | * JS 10 | 11 | # 12 | 13 | ## Preview 14 | 15 | 16 | ![Web structure](/assets/img/calendar.jpg "Web display") 17 | 18 | ### Team 5 19 | 20 | * [Javier Fernandez](https://github.com/DogSoulDev). 21 | * [Agustin Garcia](https://github.com/agustingar). 22 | * [Sofia Romera](https://github.com/Sofianct). 23 | 24 | ### Daily log 25 | 26 | #### Day 1: 27 | * Javier: Jira set up 28 | * Agustin:Info gathering 29 | * Sofía: Design (mobile & web) 30 | 31 | #### Day 2: 32 | * Javier: HTML & CSS BASIC STRUCTURE 33 | * Agustin: HTML & CSS BASIC STRUCTURE 34 | * Sofía: HTML & CSS BASIC STRUCTURE 35 | 36 | #### Day 3: 37 | * Javier: Main functions 38 | * Agustin: Main functions 39 | * Sofía: Main functions and CSS 40 | 41 | #### Day 4: 42 | * Javier: Form and form validation 43 | * Agustin: CloseModal function 44 | * Sofía: CSS responsive, Modal Form 45 | 46 | #### Day 5: 47 | * Javier: Fixed show more than 1 event per day 48 | * Agustin: Open modal and close modal 49 | * Sofía: Store info from modal form, and display that info -------------------------------------------------------------------------------- /assets/css/calendar.css: -------------------------------------------------------------------------------- 1 | /* CSS Reset */ 2 | *, *::before, *::after { 3 | box-sizing: border-box; 4 | } 5 | * { 6 | margin: 0; 7 | } 8 | html, body { 9 | height: 100%; 10 | } 11 | /*--------------*/ 12 | :root{ 13 | --back-color: #1F1F1F; 14 | --font-color: #ffffff; 15 | --selected-day: #589C5F; 16 | --modal-color: #363434; 17 | --input-color: #525252; 18 | } 19 | *{ 20 | background-color: var(--back-color); 21 | color: var(--font-color); 22 | font-family: 'Inter', sans-serif; 23 | } 24 | .title__calendar{ 25 | text-align: center; 26 | font-size: 2rem; 27 | margin: 2rem; 28 | font-weight: lighter; 29 | user-select: none; 30 | } 31 | 32 | .title__calendar--style{ 33 | font-weight: bold; 34 | color: rgb(204, 173, 0); 35 | user-select: none; 36 | } 37 | .header__buttons{ 38 | display: flex; 39 | justify-content: center; 40 | } 41 | button{ 42 | text-align: center; 43 | text-decoration: none; 44 | border: none; 45 | cursor: pointer; 46 | } 47 | /*------------↓↓↓ TO FIX ↓↓↓-------------*/ 48 | .calendar{ 49 | display: flex; 50 | justify-content: center; 51 | flex-wrap: wrap; 52 | width: 100%; 53 | margin: auto; 54 | } 55 | .calendar__weekdays{ 56 | display: flex; 57 | justify-content: center; 58 | width: 70%; 59 | font-size: 1em; 60 | font-weight: lighter; 61 | color:var(--font-color); 62 | margin: 0.31rem; 63 | } 64 | 65 | .calendar--weekdays__div{ 66 | width: 5.5rem; 67 | padding: 1rem 2rem 1rem 2rem; 68 | user-select: none; 69 | } 70 | 71 | .calendar--weekdays__div:nth-child(1){ 72 | width: 5.5rem; 73 | padding-left: 2.8rem; 74 | } 75 | 76 | .calendar--weekdays__div:nth-child(2){ 77 | width: 5.5rem; 78 | padding-left: 2.3rem; 79 | } 80 | 81 | .calendar--weekdays__div:nth-child(3){ 82 | width: 5.5rem; 83 | padding-left: 2rem; 84 | } 85 | 86 | .calendar--weekdays__div:nth-child(4){ 87 | width: 5.5rem; 88 | padding-left: 1.6rem; 89 | } 90 | 91 | .calendar--weekdays__div:nth-child(5){ 92 | width: 5.5rem; 93 | padding-left: 1.3rem; 94 | } 95 | 96 | .calendar--weekdays__div:nth-child(6){ 97 | width: 5.5rem; 98 | padding-left: 1.4rem; 99 | } 100 | 101 | .calendar--weekdays__div:nth-child(7){ 102 | width: 5.5rem; 103 | padding-left: 0.9rem; 104 | } 105 | 106 | .days{ 107 | display: grid; 108 | grid-template-columns: repeat(7, 1fr); 109 | grid-template-rows: 1fr repeat(5, 1fr); 110 | grid-column-gap: 5px; 111 | grid-row-gap: 5px; 112 | } 113 | .day{ 114 | display: flex; 115 | width: 4.25rem; 116 | height: 5.25rem; 117 | margin: 0.31rem; 118 | box-sizing: border-box; 119 | padding: 0.625rem; 120 | background-color: #252525; 121 | border-radius: 0.3rem; 122 | color: #a7a7a7; 123 | font-size: 0.8rem; 124 | cursor: pointer; 125 | user-select: none; 126 | cursor: url(/assets/img/vader-pointer.cur), pointer ; 127 | } 128 | .selected-day{ 129 | background-color: rgb(71, 71, 71); 130 | border-radius: 0.3rem; 131 | } 132 | .day:hover { 133 | background-color: #292929; 134 | } 135 | /*---------------------------------------*/ 136 | .header__buttons{ 137 | display: flex; 138 | justify-content: center; 139 | color: white; 140 | } 141 | .header__date-title{ 142 | font-size: 1.8rem; 143 | font-weight: 500; 144 | user-select: none; 145 | } 146 | .btn.prev-btn, .btn.next-btn { 147 | margin: 0em 5em; 148 | } 149 | .hidden{ 150 | display: none; 151 | } 152 | /*---------------------------------------*/ 153 | 154 | .title--event__heading { 155 | color: #eeeeee; 156 | font-size: 1.6rem; 157 | margin-bottom: 0.7rem; 158 | user-select: none; 159 | } 160 | 161 | .padding { 162 | cursor: default !important; 163 | background-color: var(--back-color) !important; 164 | box-shadow: none !important; 165 | } 166 | #newEventModal, #deleteEventModal { 167 | display: none; 168 | position: absolute; 169 | width: 350px; 170 | padding: 25px; 171 | top: 100px; 172 | left: calc(50% - 175px); 173 | z-index: 20; 174 | background-color: var(--back-color)!important; 175 | border-radius: 5px; 176 | font-family: sans-serif; 177 | box-shadow: 0px 0px 3px black; 178 | } 179 | input{ 180 | background-color: var(--modal-color); 181 | } 182 | .title--event__input { 183 | padding: 10px; 184 | width: 100%; 185 | box-sizing: border-box; 186 | border-radius: 3px; 187 | outline: none; 188 | border: none; 189 | box-shadow: 0px 0px 3px gray; 190 | } 191 | 192 | .name--event__label { 193 | font-size: 0.8rem; 194 | font-weight: 300; 195 | } 196 | 197 | .date--event__input { 198 | padding: 10px; 199 | height: 1.3rem; 200 | width: 7.9rem; 201 | box-sizing: border-box; 202 | margin-bottom: 0.7rem; 203 | margin-left: 0.5rem; 204 | margin: 1rem; 205 | border-radius: 3px; 206 | font-size: 0.6rem; 207 | color: #949494; 208 | outline: none; 209 | border: none; 210 | box-shadow: 0px 0px 3px gray; 211 | } 212 | 213 | .reminder--event__select { 214 | height: 1.3rem; 215 | width: 7.9rem; 216 | border-radius: 0.3rem; 217 | box-shadow: 0px 0px 3px gray; 218 | background-color: var(--modal-color); 219 | font-size: 0.6rem; 220 | } 221 | 222 | .cancel--btn { 223 | margin-top: 1rem; 224 | margin-right: 0.3rem; 225 | padding: 0.3rem 0.6rem; 226 | border-radius: 0.2rem; 227 | } 228 | 229 | .save--btn { 230 | margin-top: 1rem; 231 | padding: 0.3rem 1.3rem; 232 | border-radius: 0.2rem; 233 | } 234 | 235 | .close--btn { 236 | margin-top: 1rem; 237 | margin-right: 1rem; 238 | padding: 0.3rem 1rem; 239 | border-radius: 0.2rem; 240 | } 241 | 242 | #eventTitleInput.error { 243 | border: 2px solid red; 244 | } 245 | #cancelButton, #deleteButton { 246 | background-color: #d36c6c; 247 | } 248 | #saveButton, #closeButton { 249 | background-color: #92a1d1; 250 | } 251 | #eventText { 252 | font-size: 14px; 253 | } 254 | #modalBackDrop { 255 | display: none; 256 | position: absolute; 257 | width: 100vw; 258 | height: 100vh; 259 | top: 0px; 260 | left: 0px; 261 | z-index: 10; 262 | background-color: rgba(0,0,0,0.8); 263 | } 264 | 265 | .event { 266 | display: flex; 267 | background-color: transparent; 268 | font-size: 0.6rem; 269 | padding: 0; 270 | margin: 0; 271 | margin-top: 1rem; 272 | } 273 | 274 | .event:hover { 275 | background-color: #292929; 276 | } 277 | -------------------------------------------------------------------------------- /assets/css/modal.css: -------------------------------------------------------------------------------- 1 | #newEventModal, #deleteEventModal { 2 | display: none; 3 | z-index: 20; 4 | padding: 25px; 5 | background-color: #e8f4fa; 6 | box-shadow: 0px 0px 3px black; 7 | border-radius: 5px; 8 | width: 350px; 9 | top: 100px; 10 | left: calc(50% - 175px); 11 | position: absolute; 12 | font-family: sans-serif; 13 | } 14 | #eventTitleInput { 15 | padding: 10px; 16 | width: 100%; 17 | box-sizing: border-box; 18 | margin-bottom: 25px; 19 | border-radius: 3px; 20 | outline: none; 21 | border: none; 22 | box-shadow: 0px 0px 3px gray; 23 | } 24 | #eventTitleInput.error { 25 | border: 2px solid red; 26 | } 27 | #cancelButton, #deleteButton { 28 | background-color: #d36c6c; 29 | } 30 | #saveButton, #closeButton { 31 | background-color: #92a1d1; 32 | } 33 | #eventText { 34 | font-size: 14px; 35 | } 36 | #modalBackDrop { 37 | display: none; 38 | top: 0px; 39 | left: 0px; 40 | z-index: 10; 41 | width: 100vw; 42 | height: 100vh; 43 | position: absolute; 44 | background-color: rgba(0,0,0,0.8); 45 | } 46 | 47 | /* Steps of form*/ 48 | #form { 49 | width: 100%; 50 | } 51 | 52 | .form_page { 53 | display: flex; 54 | justify-content: flex-start; 55 | margin: 0 50px; 56 | } 57 | 58 | .form-group { 59 | width: 100%; 60 | margin-top: 25px; 61 | margin-bottom: 15px; 62 | font-size: 13px; 63 | } 64 | 65 | .form-group label { 66 | display: block; 67 | margin-bottom: 10px; 68 | } 69 | 70 | .form-group input, 71 | .form-group select { 72 | display: block; 73 | width: 30em; 74 | border: 1.5px solid black; 75 | border-radius: 4px; 76 | padding: 3px 10px; 77 | font-size: 12px; 78 | } -------------------------------------------------------------------------------- /assets/css/styles.css: -------------------------------------------------------------------------------- 1 | /* TYPOS */ 2 | @import url('https://fonts.googleapis.com/css2?family=Inter:wght@100;400;700&display=swap'); 3 | 4 | /* font-family: 'Inter', sans-serif; */ 5 | 6 | /* COMMON */ 7 | @import "./calendar.css"; 8 | @import "./modal.css"; 9 | 10 | /* MODAL 11 | @import "./modal.css";*/ -------------------------------------------------------------------------------- /assets/img/DV.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DogSoulDev/js-calendar/2360ac9d2a2f7c606e4faff731629593a78278f7/assets/img/DV.ico -------------------------------------------------------------------------------- /assets/img/calendar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DogSoulDev/js-calendar/2360ac9d2a2f7c606e4faff731629593a78278f7/assets/img/calendar.jpg -------------------------------------------------------------------------------- /assets/img/vader-pointer.cur: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DogSoulDev/js-calendar/2360ac9d2a2f7c606e4faff731629593a78278f7/assets/img/vader-pointer.cur -------------------------------------------------------------------------------- /assets/js/main.js: -------------------------------------------------------------------------------- 1 | let nav = 0; 2 | let clicked = null; 3 | let events = localStorage.getItem('events') ? JSON.parse(localStorage.getItem('events')) : []; 4 | 5 | const calendar = document.getElementById('calendar'); 6 | const newEventModal = document.getElementById('newEventModal'); 7 | const deleteEventModal = document.getElementById('deleteEventModal'); 8 | const backDrop = document.getElementById('modalBackDrop'); 9 | const eventTitleInput = document.getElementById('eventTitleInput'); 10 | const weekdays = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']; 11 | const butt = document.getElementById('plus-btn'); 12 | const startDate = document.getElementById('startDate'); 13 | const endDate = document.getElementById('endDate'); 14 | const description = document.getElementById('description'); 15 | const eventType = document.getElementById('eventType'); 16 | const eventTypeValue = eventType.options[eventType.selectedIndex].value; 17 | const reminderSelect = document.getElementById('reminderSelect'); 18 | const reminderSelectValue = reminderSelect.options[reminderSelect.selectedIndex].value; 19 | const checkbox = document.getElementById('endDate'); 20 | const to = document.getElementById('to'); 21 | const dateTime = document.getElementById('dateTime'); 22 | const reminder = document.getElementById('reminder'); 23 | 24 | checkbox.addEventListener('click', isChecked); 25 | reminder.addEventListener('click', isChecked); 26 | 27 | function isChecked(){ 28 | if(checkbox.checked){ 29 | to.classList.remove('hidden'); 30 | dateTime.classList.remove('hidden'); 31 | }else if(!checkbox.checked){ 32 | to.classList.add('hidden'); 33 | dateTime.classList.add('hidden'); 34 | } 35 | 36 | if(reminder.checked){ 37 | reminderSelect.classList.remove('hidden'); 38 | }else if(!reminder.checked){ 39 | reminderSelect.classList.add('hidden'); 40 | } 41 | } 42 | 43 | butt.onclick = function (date) { 44 | clicked = date; 45 | const eventForDay = events.find(e => e.date === clicked); 46 | 47 | if (eventForDay) { 48 | document.getElementById('eventText').innerText = eventForDay.title; 49 | deleteEventModal.style.display = 'block'; 50 | } else { 51 | newEventModal.style.display = 'block'; 52 | } 53 | 54 | backDrop.style.display = 'block'; 55 | } 56 | 57 | function openModal(date, e) { 58 | clicked = date; 59 | 60 | if(e.target.matches('.day')){ 61 | newEventModal.style.display = 'block'; 62 | }else{ 63 | const eventForDay = events.find(e => e.date === clicked); 64 | 65 | if (eventForDay) { 66 | document.getElementById('eventText').innerText = `${eventForDay.title}`; 67 | document.getElementById('initDate').innerText = `${eventForDay.date}`; 68 | document.getElementById('eventDescription').innerText = `${eventForDay.description}`; 69 | document.getElementById('typeOfEvent').innerText = `${eventForDay.eventType}`; 70 | 71 | deleteEventModal.style.display = 'block'; 72 | } 73 | } 74 | backDrop.style.display = 'block'; 75 | } 76 | 77 | function load() { 78 | const dt = new Date(); 79 | 80 | if (nav !== 0) { 81 | dt.setMonth(new Date().getMonth() + nav); 82 | } 83 | 84 | const day = dt.getDate(); 85 | const month = dt.getMonth(); 86 | const year = dt.getFullYear(); 87 | 88 | const firstDayOfMonth = new Date(year, month, 1); 89 | const daysInMonth = new Date(year, month + 1, 0).getDate(); 90 | 91 | const dateString = firstDayOfMonth.toLocaleDateString('en-us', { 92 | weekday: 'long', 93 | year: 'numeric', 94 | month: 'numeric', 95 | day: 'numeric', 96 | }); 97 | const paddingDays = weekdays.indexOf(dateString.split(', ')[0]); 98 | 99 | document.getElementById('monthDisplay').innerText = 100 | `${dt.toLocaleDateString('en-us', { month: 'long' })} ${year}`; 101 | 102 | calendar.innerHTML = ''; 103 | 104 | for (let i = 1; i <= paddingDays + daysInMonth; i++) { 105 | const daySquare = document.createElement('div'); 106 | daySquare.classList.add('day'); 107 | 108 | const dayString = `${month + 1}/${i - paddingDays}/${year}`; 109 | 110 | if (i > paddingDays) { 111 | daySquare.innerText = i - paddingDays; 112 | const eventForDay = events.filter(e => e.date === dayString); 113 | 114 | if (i - paddingDays === day && nav === 0) { 115 | daySquare.classList.add('selected-day'); 116 | } 117 | 118 | if (eventForDay) { 119 | eventForDay.forEach(showEvent =>{ 120 | const eventDiv = document.createElement('div'); 121 | eventDiv.classList.add('event'); 122 | eventDiv.innerText = showEvent.title; 123 | daySquare.appendChild(eventDiv); 124 | }) 125 | 126 | } 127 | 128 | daySquare.addEventListener('click', (e) => openModal(dayString,e)) 129 | } else { 130 | daySquare.classList.add('padding'); 131 | } 132 | 133 | calendar.appendChild(daySquare); 134 | } 135 | } 136 | 137 | function closeModal() { 138 | eventTitleInput.classList.remove('error'); 139 | newEventModal.style.display = 'none'; 140 | deleteEventModal.style.display = 'none'; 141 | backDrop.style.display = 'none'; 142 | eventTitleInput.value = ''; 143 | clicked = null; 144 | load(); 145 | } 146 | 147 | window.addEventListener('keydown', function (event) { 148 | if (event.key === 'Escape') { 149 | newEventModal.style.display = 'none'; 150 | deleteEventModal.style.display = 'none'; 151 | backDrop.style.display = 'none'; 152 | eventTitleInput.value = ''; 153 | clicked = null; 154 | 155 | load(); 156 | } 157 | }) 158 | 159 | function getEvents(){ 160 | const event = document.querySelectorAll('.event') 161 | Array.from(event).forEach((eventElement) => { 162 | eventElement.addEventListener('click', (e)=> { 163 | console.log(e.target); 164 | }); 165 | }); 166 | } 167 | 168 | function saveEvent() { 169 | if (eventTitleInput.value) { 170 | eventTitleInput.classList.remove('error'); 171 | 172 | events.push({ 173 | date: clicked, 174 | title: eventTitleInput.value, 175 | startDate: startDate.value, 176 | endDate: endDate.value, 177 | description: description.value, 178 | eventType: eventTypeValue, 179 | reminder: reminderSelectValue 180 | }); 181 | 182 | localStorage.setItem('events', JSON.stringify(events)); 183 | closeModal(); 184 | 185 | } else { 186 | eventTitleInput.classList.add('error'); 187 | } 188 | } 189 | 190 | function deleteEvent() { 191 | events = events.filter(e => e.date !== clicked); 192 | localStorage.setItem('events', JSON.stringify(events)); 193 | closeModal(); 194 | } 195 | 196 | function initButtons() { 197 | document.getElementById('nextButton').addEventListener('click', () => { 198 | nav++; 199 | load(); 200 | }); 201 | 202 | document.getElementById('backButton').addEventListener('click', () => { 203 | nav--; 204 | load(); 205 | }); 206 | 207 | document.getElementById('saveButton').addEventListener('click', saveEvent); 208 | document.getElementById('cancelButton').addEventListener('click', closeModal); 209 | document.getElementById('deleteButton').addEventListener('click', deleteEvent); 210 | document.getElementById('closeButton').addEventListener('click', closeModal); 211 | } 212 | 213 | initButtons(); 214 | load(); 215 | getEvents(); -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Calendar Js 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 21 |
22 |
23 |

JSCALENDAR

24 |
25 | 26 |

27 | 28 | 29 |
30 |
31 |
32 |
33 |
Sun
34 |
Mon
35 |
Tue
36 |
Wed
37 |
Thu
38 |
Fri
39 |
Sat
40 |
41 |
42 |
43 |
44 |
45 |
46 |

New Event

47 | 48 | 49 | 50 |
51 | 52 | 53 |
54 | 55 | 56 |
57 | 58 | 59 | 66 |
67 | 68 | 69 |
70 | 71 | 77 |
78 | 79 | 80 |
81 |
82 |

Event

83 |

84 |

85 |

86 |

87 |

88 | 89 | 90 |
91 |
92 | 93 |
94 | 95 | --------------------------------------------------------------------------------