mod18.html 52 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8" />
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  6. <meta http-equiv="X-UA-Compatible" content="ie=edge" />
  7. <link href="https://rsms.me/inter/inter.css" rel="stylesheet" />
  8. <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.js"></script>
  9. <script src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.0/js/select2.full.js"></script>
  10. <link
  11. href="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.0/css/select2.css"
  12. rel="stylesheet"
  13. />
  14. <title>Module 18 NoSQL Challenge Rubric</title>
  15. <style>
  16. *,
  17. *::before,
  18. *::after {
  19. box-sizing: border-box;
  20. }
  21. html,
  22. body {
  23. margin: 0;
  24. padding: 0;
  25. line-height: 1.15;
  26. font-size: 100%;
  27. color: #fefefe;
  28. transition: all 0.4s;
  29. }
  30. body {
  31. background-color: #fbfcfd;
  32. font-family: 'Inter', sans-serif;
  33. color: #4a4a4a;
  34. }
  35. #app.dark {
  36. background-color: #121212;
  37. color: #e2e2e2;
  38. }
  39. h1 {
  40. font-size: 1.9rem;
  41. }
  42. h2 {
  43. font-size: 1.6rem;
  44. }
  45. h3 {
  46. font-size: 1.2rem;
  47. }
  48. h4 {
  49. font-size: 0.9rem;
  50. }
  51. p,
  52. legend {
  53. font-size: 1.2rem;
  54. }
  55. label {
  56. font-size: 0.9rem;
  57. line-height: 2;
  58. }
  59. fieldset {
  60. border: none;
  61. padding: 0;
  62. margin-top: 0.9rem;
  63. }
  64. input[type='radio'],
  65. input[type='checkbox'] {
  66. position: absolute !important;
  67. height: 1px;
  68. width: 1px;
  69. overflow: hidden;
  70. clip: rect(1px 1px 1px 1px);
  71. clip: rect(1px, 1px, 1px, 1px);
  72. white-space: nowrap;
  73. }
  74. .select-lg,
  75. .select-lg option {
  76. font-family: 'Inter', sans-serif;
  77. padding: 10px;
  78. }
  79. .narrow {
  80. width: 800px;
  81. }
  82. #app {
  83. padding: 3%;
  84. transition: all 0.4s;
  85. }
  86. #app h1 {
  87. font-size: 1.9rem;
  88. font-weight: 700;
  89. line-height: 1.3;
  90. }
  91. header {
  92. display: flex;
  93. justify-content: space-between;
  94. align-items: center;
  95. }
  96. .switch-group {
  97. display: flex;
  98. align-items: center;
  99. height: 2.25rem;
  100. width: 4.5rem;
  101. }
  102. input[type='checkbox'] + .toggle {
  103. height: 100%;
  104. width: 100%;
  105. background-color: #77a1db;
  106. border-radius: 1.9rem;
  107. cursor: pointer;
  108. display: flex;
  109. align-items: center;
  110. padding: 0.35rem;
  111. cursor: pointer;
  112. overflow: hidden;
  113. background-position: 25% 125%;
  114. transition: all 0.5s;
  115. }
  116. input[type='checkbox']:focus + .toggle {
  117. outline: rgb(59, 153, 252) auto 5px;
  118. }
  119. input[type='checkbox']:checked + .toggle {
  120. background-color: rgb(85, 89, 204);
  121. background-size: 1rem;
  122. background-repeat: no-repeat;
  123. background-position: 25%;
  124. background-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTQiIGhlaWdodD0iMTQiIHZpZXdCb3g9IjAgMCAxNCAxNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGNpcmNsZSBjeD0iMSIgY3k9IjgiIHI9IjEiIGZpbGw9IndoaXRlIi8+CjxjaXJjbGUgY3g9IjgiIGN5PSI3IiByPSIxIiBmaWxsPSJ3aGl0ZSIvPgo8Y2lyY2xlIGN4PSI1IiBjeT0iMTMiIHI9IjEiIGZpbGw9IndoaXRlIi8+CjxjaXJjbGUgY3g9IjUiIGN5PSIyIiByPSIyIiBmaWxsPSJ3aGl0ZSIvPgo8Y2lyY2xlIGN4PSIxMyIgY3k9IjMiIHI9IjEiIGZpbGw9IndoaXRlIi8+CjxjaXJjbGUgY3g9IjEyLjUiIGN5PSI5LjUiIHI9IjEuNSIgZmlsbD0id2hpdGUiLz4KPC9zdmc+Cg==');
  125. transition: all 0.5s;
  126. }
  127. input[type='checkbox'] + .toggle::after {
  128. content: '';
  129. display: block;
  130. height: 1.7rem;
  131. width: 1.7rem;
  132. border-radius: 50%;
  133. cursor: pointer;
  134. border: 4px solid #e1be64;
  135. background-color: #fddf74;
  136. transform: translateX(0rem);
  137. transition: all 0.5s;
  138. background-position: center bottom;
  139. }
  140. input[type='checkbox']:hover + .toggle::after {
  141. transform: translateX(0rem) scale(1.1);
  142. }
  143. input[type='checkbox']:checked + .toggle::after {
  144. border: 4px solid #ffffff;
  145. background-color: rgba(255, 255, 255, 0.4);
  146. transform: translateX(2.1rem);
  147. transition: all 0.5s;
  148. background-position: center;
  149. background-size: 60%;
  150. background-repeat: no-repeat;
  151. background-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTkiIGhlaWdodD0iMTkiIHZpZXdCb3g9IjAgMCAxOSAxOSIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTE4LjUgMTAuMjlDMTguMzQyNyAxMS45OTIyIDE3LjcwMzkgMTMuNjE0NCAxNi42NTgyIDE0Ljk2NjhDMTUuNjEyNiAxNi4zMTkyIDE0LjIwMzUgMTcuMzQ1OCAxMi41OTU3IDE3LjkyNjVDMTAuOTg3OSAxOC41MDczIDkuMjQ3OTggMTguNjE4MSA3LjU3OTUgMTguMjQ2MUM1LjkxMTAyIDE3Ljg3NDEgNC4zODI5OSAxNy4wMzQ1IDMuMTc0MjMgMTUuODI1OEMxLjk2NTQ2IDE0LjYxNyAxLjEyNTk0IDEzLjA4OSAwLjc1MzkwNyAxMS40MjA1QzAuMzgxODc1IDkuNzUyMDIgMC40OTI3MTYgOC4wMTIwOCAxLjA3MzQ2IDYuNDA0M0MxLjY1NDIgNC43OTY1MSAyLjY4MDgzIDMuMzg3MzcgNC4wMzMyMSAyLjM0MTc1QzUuMzg1NTkgMS4yOTYxNCA3LjAwNzggMC42NTczMDUgOC43MSAwLjVDNy43MTM0MSAxLjg0ODI3IDcuMjMzODUgMy41MDk0NSA3LjM1ODUzIDUuMTgxNDFDNy40ODMyMSA2Ljg1MzM4IDguMjAzODYgOC40MjUwNiA5LjM4OTQgOS42MTA2QzEwLjU3NDkgMTAuNzk2MSAxMi4xNDY2IDExLjUxNjggMTMuODE4NiAxMS42NDE1QzE1LjQ5MDYgMTEuNzY2MiAxNy4xNTE3IDExLjI4NjYgMTguNSAxMC4yOVYxMC4yOVoiIGZpbGw9IndoaXRlIi8+Cjwvc3ZnPgo=');
  152. }
  153. input[type='checkbox']:checked:hover + .toggle::after {
  154. transform: translateX(2.1rem) scale(1.075);
  155. }
  156. .card {
  157. background-color: #fff;
  158. border-radius: 6px;
  159. border-width: 0;
  160. box-shadow: rgba(155, 155, 155, 0.45) 0 0 30px -5px;
  161. font-size: 1.1rem;
  162. height: 100%;
  163. line-height: 1.8rem;
  164. padding: 1.5rem 2rem;
  165. width: 100%;
  166. margin-bottom: 1.9rem;
  167. }
  168. .dark .card {
  169. background-color: #333333;
  170. box-shadow: none;
  171. }
  172. .card h1,
  173. .card h2,
  174. .card h3,
  175. .card h4,
  176. .card h5,
  177. .card h6 {
  178. color: #4a4a4a;
  179. }
  180. .dark .card h1,
  181. .dark .card h2,
  182. .dark .card h3,
  183. .dark .card h4,
  184. .dark .card h5,
  185. .dark .card h6 {
  186. color: #e2e2e2;
  187. }
  188. .card-header h2 {
  189. display: inline-block;
  190. }
  191. .card .cat-score {
  192. margin-left: 20px;
  193. font-size: smaller;
  194. }
  195. .card h3 {
  196. font-size: 1.2rem;
  197. font-weight: 700;
  198. line-height: 1.8em;
  199. margin: 0;
  200. padding: 0;
  201. }
  202. .card p,
  203. .card label,
  204. .card legend {
  205. color: #7a869c;
  206. border-width: 0;
  207. display: block;
  208. font-size: 1.1rem;
  209. font-weight: 300;
  210. line-height: 1.8em;
  211. margin: 0;
  212. padding: 0;
  213. }
  214. .card legend {
  215. display: flex;
  216. justify-content: flex-start;
  217. align-items: center;
  218. }
  219. .card legend .score {
  220. font-size: smaller;
  221. margin-left: 20px;
  222. font-weight: 100;
  223. min-width: 5%;
  224. }
  225. .dark .card p,
  226. .dark .card label,
  227. .dark .card legend {
  228. color: #e2e2e2;
  229. }
  230. .card label {
  231. display: inline-block;
  232. }
  233. .card input[type='radio'] + label::before {
  234. content: '';
  235. height: 0.9rem;
  236. width: 0.9rem;
  237. margin-right: 0.7rem;
  238. border-radius: 50%;
  239. border: 1px solid rgb(74, 74, 74);
  240. background-color: #ffffff;
  241. display: inline-block;
  242. cursor: pointer;
  243. position: relative;
  244. top: 0.09rem;
  245. }
  246. .dark .card input[type='radio'] + label::before {
  247. border: 1px solid #e2e2e2;
  248. background-color: inherit;
  249. }
  250. .card input[type='radio']:checked + label::before {
  251. background-color: rgb(47, 106, 189);
  252. border-color: rgb(47, 106, 189);
  253. }
  254. .dark .card input[type='radio']:checked + label::before {
  255. background-color: #d9d9d9;
  256. border-color: #d9d9d9;
  257. }
  258. .card input[type='radio']:checked + label {
  259. font-weight: bold;
  260. color: #4a4a4a;
  261. }
  262. .dark input[type='radio']:checked + label {
  263. color: #e2e2e2;
  264. }
  265. .card input[type='radio']:focus + label::before {
  266. outline: rgb(59, 153, 252) auto 5px;
  267. }
  268. .card legend {
  269. margin-bottom: 0.4rem;
  270. }
  271. .totals {
  272. font-size: 1.6rem;
  273. }
  274. .totals + h2 {
  275. font-size: 1.9rem;
  276. margin-top: 0;
  277. }
  278. .grade {
  279. font-size: 1.9rem;
  280. }
  281. button {
  282. background-color: #fff;
  283. border: 1px solid #4a4a4a;
  284. border-radius: 6px;
  285. box-shadow: rgba(155, 155, 155, 0.5) 1px 2px 3px 0;
  286. color: #4a4a4a;
  287. cursor: pointer;
  288. display: inline-block;
  289. font-size: 1rem;
  290. font-weight: 500;
  291. letter-spacing: 0.04em;
  292. line-height: 1.8em;
  293. margin: 0.75rem 1.1rem 1.1rem 0;
  294. outline: none;
  295. padding: 0.5rem 1.4rem;
  296. text-transform: uppercase;
  297. transition-delay: 0s;
  298. transition-duration: 0.25s;
  299. transition-property: all;
  300. transition-timing-function: ease-out;
  301. width: fit-content;
  302. }
  303. .dark button {
  304. background: #d9d9d9;
  305. color: #121212;
  306. box-shadow: none;
  307. }
  308. button:hover {
  309. border-color: #dbdbdb;
  310. color: #dbdbdb;
  311. }
  312. .dark button:hover {
  313. color: #121212;
  314. }
  315. button.copy {
  316. margin-left: 0.4rem;
  317. font-size: 0.8rem;
  318. }
  319. button:focus {
  320. border-color: rgb(56, 56, 56);
  321. box-shadow: rgb(219, 219, 219) 0px 0px 0px 3px;
  322. color: rgb(56, 56, 56);
  323. }
  324. div[role='alert'] {
  325. color: #4a4a4a;
  326. font-size: 1.2rem;
  327. margin-bottom: 1.2rem;
  328. }
  329. .dark div[role='alert'] {
  330. color: #e2e2e2;
  331. }
  332. .summary {
  333. font-size: 1.2rem;
  334. border: none;
  335. font-family: monospace;
  336. background-color: #d5d9de;
  337. padding: 0.9rem;
  338. border-radius: 6px;
  339. line-height: 1.5;
  340. width: 100%;
  341. }
  342. .summary h2,
  343. .dark .summary h2 {
  344. font-size: 1.2rem;
  345. font-family: 'Inter', sans-serif;
  346. margin-top: 35px;
  347. margin-bottom: 5px;
  348. }
  349. .summary h2:first-of-type,
  350. .dark .summary h2:first-of-type {
  351. margin-top: 15px;
  352. }
  353. .dark .summary {
  354. background: #6b6a6a;
  355. color: #e2e2e2;
  356. }
  357. .summary pre,
  358. .dark .summary pre {
  359. white-space: pre-wrap;
  360. border-radius: 5px;
  361. padding: 15px;
  362. border: solid 2px rgba(0, 0, 0, 0.4);
  363. }
  364. .summary pre:empty {
  365. display: none;
  366. }
  367. .summary pre:empty + button {
  368. display: none;
  369. }
  370. .d-none {
  371. display: none;
  372. }
  373. @media (max-width: 450px) {
  374. .btn-container {
  375. text-align: center;
  376. }
  377. button {
  378. width: 100%;
  379. margin: 0.5rem;
  380. }
  381. input[type='checkbox']:checked + .toggle::after {
  382. transform: translateX(1.3rem);
  383. background-size: 50%;
  384. }
  385. input[type='checkbox']:checked:hover + .toggle::after {
  386. transform: translateX(1.3rem) scale(1.1);
  387. background-size: 50%;
  388. }
  389. }
  390. @media (max-width: 515px) {
  391. input[type='checkbox']:checked + .toggle::after {
  392. transform: translateX(1.7rem);
  393. }
  394. input[type='checkbox']:checked:hover + .toggle::after {
  395. transform: translateX(1.7rem) scale(1.1);
  396. }
  397. }
  398. @media (max-width: 460px) {
  399. input[type='checkbox']:checked + .toggle::after {
  400. transform: translateX(1.4rem);
  401. }
  402. input[type='checkbox']:checked:hover + .toggle::after {
  403. transform: translateX(1.4rem) scale(1.1);
  404. }
  405. }
  406. @media (max-width: 400px) {
  407. #app h1 {
  408. font-size: 1.6rem;
  409. }
  410. input[type='checkbox']:checked + .toggle::after {
  411. transform: translateX(1.3rem);
  412. }
  413. input[type='checkbox']:checked:hover + .toggle::after {
  414. transform: translateX(1.3rem) scale(1.1);
  415. }
  416. }
  417. @media (max-width: 340px) {
  418. input[type='checkbox']:checked + .toggle::after {
  419. transform: translateX(0.9rem);
  420. }
  421. input[type='checkbox']:checked:hover + .toggle::after {
  422. transform: translateX(0.9rem) scale(1.075);
  423. }
  424. }
  425. </style>
  426. <script>
  427. $(function () {
  428. let $select2 = $('.select2').select2({
  429. containerCssClass: 'wrap',
  430. });
  431. function triggerEvent(name, val) {
  432. let selected = $("select[name='" + name + "']");
  433. let change = new Event('change', { bubbles: true });
  434. selected[0].dispatchEvent(change);
  435. }
  436. $('.select2').on('select2:select', function (e) {
  437. triggerEvent(e.target.name, e.target.value);
  438. });
  439. });
  440. </script>
  441. </head>
  442. <body>
  443. <div id="app"></div>
  444. <script>
  445. (function (window) {
  446. 'use strict';
  447. function Model() {
  448. this.title = `Module 18 NoSQL Challenge Rubric (Coding)`;
  449. this.categories = [
  450. {
  451. title: 'Deliverables',
  452. subtitle: null,
  453. score: 0,
  454. questions: [
  455. {
  456. title:
  457. 'Student submitted GitHub Repo containing application code',
  458. score: 10,
  459. type: 'true false',
  460. message: 'GitHub repo is empty',
  461. status: false,
  462. },
  463. ],
  464. },
  465. {
  466. title: 'Walkthrough Video',
  467. subtitle: null,
  468. score: 0,
  469. questions: [
  470. {
  471. title: 'Student includes link to walkthrough video in README',
  472. score: 5,
  473. type: 'true false',
  474. message:
  475. 'Student does not include link to walkthrough video in README',
  476. status: false,
  477. },
  478. {
  479. title:
  480. 'Walkthrough video shows all of the technical acceptance criteria being met',
  481. score: 5,
  482. type: 'true false',
  483. message:
  484. "Walkthrough video doesn't show all of the technical acceptance criteria being met",
  485. status: false,
  486. },
  487. {
  488. title:
  489. 'Walkthrough video demonstrates how to start the server',
  490. score: 2,
  491. type: 'true false',
  492. message:
  493. 'Walkthrough video does not demonstrate how to start the server',
  494. status: false,
  495. },
  496. {
  497. title:
  498. 'Walkthrough video demonstrates GET routes for all users and all thoughts being tested in Insomnia',
  499. score: 5,
  500. type: 'likert',
  501. options: [
  502. {
  503. message:
  504. 'Walkthrough video does not demonstrate GET routes for all users and all thoughts being tested in Insomnia',
  505. score: 0,
  506. },
  507. {
  508. message:
  509. 'Walkthrough video demonstrates GET routes for 1 of the 2 following being tested in Insomnia: all users or all thoughts',
  510. score: 2,
  511. },
  512. {
  513. message:
  514. 'Walkthrough video demonstrates GET routes for all users and all thoughts being tested in Insomnia',
  515. score: 5,
  516. },
  517. ],
  518. message: '',
  519. status: false,
  520. },
  521. {
  522. title:
  523. 'Walkthrough video demonstrates GET routes for a single user and a single thought being tested in Insomnia',
  524. score: 5,
  525. type: 'likert',
  526. options: [
  527. {
  528. message:
  529. 'Walkthrough video does not demonstrate GET routes for a single user and a single thought being tested in Insomnia',
  530. score: 0,
  531. },
  532. {
  533. message:
  534. 'Walkthrough video demonstrates GET routes for 1 of the 2 following being tested in Insomnia: a single user or a single thought',
  535. score: 2,
  536. },
  537. {
  538. message:
  539. 'Walkthrough video demonstrates GET routes for a single user and a single thought being tested in Insomnia',
  540. score: 5,
  541. },
  542. ],
  543. message: '',
  544. status: false,
  545. },
  546. {
  547. title:
  548. 'Walkthrough video demonstrates POST, PUT, and DELETE routes for users and thoughts being tested in Insomnia',
  549. score: 5,
  550. type: 'likert',
  551. options: [
  552. {
  553. message:
  554. 'Walkthrough video does not demonstrate POST, PUT, and DELETE routes for users and thoughts being tested in Insomnia',
  555. score: 0,
  556. },
  557. {
  558. message:
  559. 'Walkthrough video demonstrates POST, PUT, and DELETE routes for 1 of the 2 following being tested in Insomnia: users or thoughts',
  560. score: 2,
  561. },
  562. {
  563. message:
  564. 'Walkthrough video demonstrates POST, PUT, and DELETE routes for users and thoughts being tested in Insomnia',
  565. score: 5,
  566. },
  567. ],
  568. message: '',
  569. status: false,
  570. },
  571. {
  572. title:
  573. 'Walkthrough video demonstrates POST and DELETE routes for a user’s friend list being tested in Insomnia',
  574. score: 5,
  575. type: 'likert',
  576. options: [
  577. {
  578. message:
  579. 'Walkthrough video does not demonstrate POST and DELETE routes for a user’s friend list being tested in Insomnia',
  580. score: 0,
  581. },
  582. {
  583. message:
  584. 'Walkthrough video demonstrates 1 of the 2 following being tested in Insomnia: POST or DELETE routes for a user’s friend list',
  585. score: 2,
  586. },
  587. {
  588. message:
  589. 'Walkthrough video demonstrates POST and DELETE routes for a user’s friend list being tested in Insomnia',
  590. score: 5,
  591. },
  592. ],
  593. message: '',
  594. status: false,
  595. },
  596. {
  597. title:
  598. 'Walkthrough video demonstrates POST and DELETE routes for reactions to thoughts being tested in Insomnia',
  599. score: 5,
  600. type: 'likert',
  601. options: [
  602. {
  603. message:
  604. 'Walkthrough video does not demonstrate POST and DELETE routes for reactions to thoughts being tested in Insomnia',
  605. score: 0,
  606. },
  607. {
  608. message:
  609. 'Walkthrough video demonstrates 1 of the 2 following being tested in Insomnia: POST or DELETE routes for reactions to thoughts',
  610. score: 2,
  611. },
  612. {
  613. message:
  614. 'Walkthrough video demonstrates POST and DELETE routes for reactions to thoughts being tested in Insomnia',
  615. score: 5,
  616. },
  617. ],
  618. message: '',
  619. status: false,
  620. },
  621. ],
  622. },
  623. {
  624. title: 'Technical Acceptance Criteria',
  625. subtitle:
  626. 'Watch out for errors in the console from this point forward.',
  627. score: 0,
  628. questions: [
  629. {
  630. title:
  631. 'Application includes User and Thought models that match Challenge instructions',
  632. score: 5,
  633. type: 'likert',
  634. options: [
  635. {
  636. message:
  637. "Application doesn't include User and Thought models that match Challenge instructions",
  638. score: 0,
  639. },
  640. {
  641. message:
  642. 'Application includes 1 of the 2 following: User or Thought models that match Challenge instructions',
  643. score: 2,
  644. },
  645. {
  646. message:
  647. 'Application includes User and Thought models that match Challenge instructions',
  648. score: 5,
  649. },
  650. ],
  651. message: '',
  652. status: false,
  653. },
  654. {
  655. title:
  656. 'Application includes schema settings for User and Thought models as outlined in the Challenge instructions',
  657. score: 5,
  658. type: 'likert',
  659. options: [
  660. {
  661. message:
  662. "Application doesn't include schema settings for User and Thought models as outlined in the Challenge instructions",
  663. score: 0,
  664. },
  665. {
  666. message:
  667. 'Application includes schema settings as outlined in the Challenge instructions for 1 of the 2 following: User or Thought models',
  668. score: 2,
  669. },
  670. {
  671. message:
  672. 'Application includes schema settings for User and Thought models as outlined in the Challenge instructions',
  673. score: 5,
  674. },
  675. ],
  676. message: '',
  677. status: false,
  678. },
  679. {
  680. title:
  681. "Application includes Reactions as the reaction field's subdocument schema in the Thought model",
  682. score: 5,
  683. type: 'true false',
  684. message:
  685. "Application doesn't include Reactions as the reaction field's subdocument schema in the Thought model",
  686. status: false,
  687. },
  688. {
  689. title:
  690. 'Application includes GET routes that return all users and all thoughts',
  691. score: 5,
  692. type: 'likert',
  693. options: [
  694. {
  695. message:
  696. "Application doesn't include GET routes that return all users and all thoughts",
  697. score: 0,
  698. },
  699. {
  700. message:
  701. 'Application includes GET routes that return 1 of the 2 following: all users or all thoughts',
  702. score: 2,
  703. },
  704. {
  705. message:
  706. 'Application includes GET routes that return all users and all thoughts',
  707. score: 5,
  708. },
  709. ],
  710. message: '',
  711. status: false,
  712. },
  713. {
  714. title:
  715. 'Application includes GET routes that return a single user and a single thought',
  716. score: 5,
  717. type: 'likert',
  718. options: [
  719. {
  720. message:
  721. "Application doesn't include GET routes that return a single user and a single thought",
  722. score: 0,
  723. },
  724. {
  725. message:
  726. 'Application includes GET routes that return 1 of the 2 following: a single user or a single thought',
  727. score: 2,
  728. },
  729. {
  730. message:
  731. 'Application includes GET routes that return a single user and a single thought',
  732. score: 5,
  733. },
  734. ],
  735. message: '',
  736. status: false,
  737. },
  738. {
  739. title:
  740. "Application includes POST routes that can create a new user, a new thought, a reaction to a thought, and add a friend to a user's friend list",
  741. score: 5,
  742. type: 'likert',
  743. options: [
  744. {
  745. message:
  746. "Application doesn't include POST routes that can create a new user, a new thought, a reaction to a thought, or add a friend to a user's friend list",
  747. score: 0,
  748. },
  749. {
  750. message:
  751. "Application includes POST routes that can do 1 of the 4 following: create a new user, create a new thought, create a reaction to a thought, or add a friend to a user's friend list",
  752. score: 2,
  753. },
  754. {
  755. message:
  756. "Application includes POST routes that can do 2 of the 4 following: create a new user, create a new thought, create a reaction to a thought, or add a friend to a user's friend list",
  757. score: 3,
  758. },
  759. {
  760. message:
  761. "Application includes POST routes that can do 3 of the 4 following: create a new user, create a new thought, create a reaction to a thought, or add a friend to a user's friend list",
  762. score: 4,
  763. },
  764. {
  765. message:
  766. "Application includes POST routes that can create a new user, a new thought, a reaction to a thought, and add a friend to a user's friend list",
  767. score: 5,
  768. },
  769. ],
  770. message: '',
  771. status: false,
  772. },
  773. {
  774. title:
  775. 'Application includes PUT routes that can update a user or a thought',
  776. score: 5,
  777. type: 'likert',
  778. options: [
  779. {
  780. message:
  781. "Application doesn't include PUT routes that can update a user or a thought",
  782. score: 0,
  783. },
  784. {
  785. message:
  786. 'Application includes PUT routes that can update 1 of the 2 following: a user or a thought',
  787. score: 2,
  788. },
  789. {
  790. message:
  791. 'Application includes PUT routes that can update a user or a thought',
  792. score: 5,
  793. },
  794. ],
  795. message: '',
  796. status: false,
  797. },
  798. {
  799. title:
  800. "Application includes DELETE routes that can delete a user, a thought, a reaction to a thought, and remove a friend from a user's friend list",
  801. score: 5,
  802. type: 'likert',
  803. options: [
  804. {
  805. message:
  806. "Application doesn't include DELETE routes that can delete a user, a thought, a reaction to a thought, or a friend from a user's friend list",
  807. score: 0,
  808. },
  809. {
  810. message:
  811. "Application includes DELETE routes that can do 1 of the 4 following: delete a user, delete a thought, delete a reaction to a thought, or remove a friend from a user's friend list",
  812. score: 2,
  813. },
  814. {
  815. message:
  816. "Application includes DELETE routes that can do 2 of the 4 following: delete a user, delete a thought, delete a reaction to a thought, or remove a friend from a user's friend list",
  817. score: 3,
  818. },
  819. {
  820. message:
  821. "Application includes DELETE routes that can do 3 of the 4 following: delete a user, delete a thought, delete a reaction to a thought, or remove a friend from a user's friend list",
  822. score: 4,
  823. },
  824. {
  825. message:
  826. "Application includes DELETE routes that can delete a user, a thought, a reaction to a thought, and remove a friend from a user's friend list",
  827. score: 5,
  828. },
  829. ],
  830. message: '',
  831. status: false,
  832. },
  833. ],
  834. },
  835. {
  836. title: 'Repository Quality',
  837. subtitle: null,
  838. score: 0,
  839. questions: [
  840. {
  841. title: 'Repository has a unique name',
  842. score: 2,
  843. type: 'true false',
  844. message: "Repo doesn't have a unique name",
  845. status: false,
  846. },
  847. {
  848. title:
  849. 'Repository follows best practices for file structure and naming conventions',
  850. score: 2,
  851. type: 'true false',
  852. message:
  853. "Code doesn't follow recommended file/folder structure",
  854. status: false,
  855. },
  856. {
  857. title:
  858. 'Repository follows best practices for variable and function naming conventions, indentation, quality comments, etc.',
  859. score: 2,
  860. type: 'true false',
  861. message:
  862. "Code doesn't follow good practices for variable names, indentation, lacks quality comments",
  863. status: false,
  864. },
  865. {
  866. title:
  867. 'Repository contains multiple descriptive commit messages',
  868. score: 2,
  869. type: 'true false',
  870. message:
  871. "Repo doesn't contain multiple descriptive commit messages",
  872. status: false,
  873. },
  874. {
  875. title:
  876. 'Repository contains quality README with description and a link to walkthrough video',
  877. score: 5,
  878. type: 'likert',
  879. options: [
  880. {
  881. message:
  882. "Repo doesn't contain a quality README with description and a link to walkthrough video",
  883. score: 0,
  884. },
  885. {
  886. message:
  887. 'Repository README is missing either the description or the link to the walkthrough video',
  888. score: 2,
  889. },
  890. {
  891. message:
  892. 'Repository contains quality README with description and a link to walkthrough video',
  893. score: 5,
  894. },
  895. ],
  896. message: '',
  897. status: false,
  898. },
  899. ],
  900. },
  901. {
  902. title: 'Bonus',
  903. subtitle: null,
  904. score: 0,
  905. questions: [
  906. {
  907. title:
  908. "Application removes a user's associated thoughts when the user is deleted",
  909. score: 10,
  910. type: 'true false',
  911. message:
  912. "Application removes a user's associated thoughts when the user is deleted",
  913. status: false,
  914. },
  915. ],
  916. },
  917. {
  918. title: 'Academic Integrity',
  919. subtitle:
  920. 'If any of of the following are true, partner with your instructor to investigate plagiarism. Contact your SSM if you believe plagiarism has occurred.',
  921. score: 0,
  922. questions: [
  923. {
  924. title: 'Application contains a low number of commits',
  925. score: 0,
  926. type: 'true false',
  927. message: null,
  928. status: false,
  929. },
  930. {
  931. title:
  932. 'Application codebase appears advanced for the student, at this point in the course, or in general',
  933. score: 0,
  934. type: 'true false',
  935. message: null,
  936. status: false,
  937. },
  938. {
  939. title:
  940. 'Application package.json file references a different author and/or repository',
  941. score: 0,
  942. type: 'true false',
  943. message: null,
  944. status: false,
  945. },
  946. {
  947. title:
  948. 'Application package.json file references much older versions of npm packages',
  949. score: 0,
  950. type: 'true false',
  951. message: null,
  952. status: false,
  953. },
  954. ],
  955. },
  956. ];
  957. this.initStorage();
  958. }
  959. Model.prototype.initStorage = function () {
  960. this.score = 0;
  961. this.questions = [];
  962. this.messages = new Set();
  963. this.flagged = new Set();
  964. this.isDarkMode =
  965. JSON.parse(localStorage.getItem('isDarkMode')) || false;
  966. this.categories.forEach((category) => {
  967. category.score = 0;
  968. category.questions.forEach((question) => {
  969. question.category = category.title;
  970. question.status = false;
  971. question.id = this.questions.length;
  972. question.flagged = category.title === 'Academic Integrity';
  973. question.bonus = category.title === 'Bonus';
  974. if (question.type === 'likert') {
  975. question.message = question.options[0].message
  976. .split(' - ')
  977. .pop();
  978. }
  979. let messageObj = {
  980. message: question.message,
  981. category: question.category,
  982. };
  983. question.message &&
  984. !question.bonus &&
  985. this.messages.add(messageObj);
  986. this.questions.push(question);
  987. category.score += question.score;
  988. });
  989. });
  990. };
  991. Model.prototype.toggleDarkMode = function () {
  992. this.isDarkMode = !this.isDarkMode;
  993. localStorage.setItem('isDarkMode', this.isDarkMode);
  994. };
  995. Model.prototype.toggleRadio = function (radio) {
  996. const id = Number(radio.getAttribute('name'));
  997. const question = this.questions[id];
  998. const questionScore = document.querySelector(
  999. "span[data-id='" + id + "']"
  1000. );
  1001. const category = question.category;
  1002. const categoryScore = document.querySelector(
  1003. "span[data-cat='" + category + "']"
  1004. );
  1005. const resultsCategoryScore = document.querySelector(
  1006. "span[data-res-cat='" + category + "']"
  1007. );
  1008. question.status = !question.status;
  1009. if (question.status === true) {
  1010. if (!question.bonus) {
  1011. this.messages.forEach((message) => {
  1012. if (message.message === question.message) {
  1013. this.messages.delete(message);
  1014. }
  1015. });
  1016. } else {
  1017. let messageObj = {
  1018. message: question.message,
  1019. category: category,
  1020. };
  1021. this.messages.add(messageObj);
  1022. }
  1023. this.score += question.score;
  1024. if (question.flagged) {
  1025. this.flagged.add(question.id);
  1026. }
  1027. } else {
  1028. if (question.flagged) {
  1029. this.flagged.delete(question.id);
  1030. }
  1031. if (!question.bonus) {
  1032. if (question.message) {
  1033. let messageObj = {
  1034. message: question.message,
  1035. category: category,
  1036. };
  1037. this.messages.add(messageObj);
  1038. }
  1039. } else {
  1040. this.messages.forEach((message) => {
  1041. if (message.message === question.message) {
  1042. this.messages.delete(message);
  1043. }
  1044. });
  1045. }
  1046. this.score -= question.score;
  1047. }
  1048. if (category !== 'Academic Integrity') {
  1049. if (question.status) {
  1050. questionScore.textContent = question.score;
  1051. } else {
  1052. questionScore.textContent = 0;
  1053. }
  1054. const catQuestions = this.categories.filter(
  1055. (cat) => cat.title === category
  1056. )[0].questions;
  1057. let categoryTotal = 0;
  1058. catQuestions.forEach((question) => {
  1059. if (question.type === 'true false') {
  1060. if (question.status) {
  1061. categoryTotal += question.score;
  1062. }
  1063. } else {
  1064. let score = document.querySelector(
  1065. "select[name='" + question.id + "']"
  1066. ).value;
  1067. categoryTotal += parseInt(score);
  1068. }
  1069. });
  1070. categoryScore.textContent = categoryTotal;
  1071. resultsCategoryScore.textContent = categoryTotal;
  1072. }
  1073. };
  1074. Model.prototype.handleSelect = function (select) {
  1075. const id = Number(select.getAttribute('name'));
  1076. const question = this.questions[id];
  1077. const questionScore = document.querySelector(
  1078. "span[data-id='" + id + "']"
  1079. );
  1080. const oldScore = questionScore.textContent;
  1081. const category = question.category;
  1082. const categoryScore = document.querySelector(
  1083. "span[data-cat='" + category + "']"
  1084. );
  1085. const resultsCategoryScore = document.querySelector(
  1086. "span[data-res-cat='" + category + "']"
  1087. );
  1088. let newMessage = select.selectedOptions[0].text.split(' - ').pop();
  1089. if (newMessage === 'Open this menu') {
  1090. newMessage = select[1].text.split(' - ').pop();
  1091. }
  1092. if (question.type === 'likert') {
  1093. this.messages.forEach((item) => {
  1094. if (item.message === question.message) {
  1095. this.messages.delete(item);
  1096. }
  1097. });
  1098. question.message = newMessage;
  1099. let messageObj = {
  1100. message: newMessage,
  1101. category: category,
  1102. };
  1103. this.messages.add(messageObj);
  1104. }
  1105. this.score -= parseInt(oldScore);
  1106. this.score += parseInt(select.value);
  1107. questionScore.textContent = select.value;
  1108. const catQuestions = this.categories.filter(
  1109. (cat) => cat.title === category
  1110. )[0].questions;
  1111. let categoryTotal = 0;
  1112. catQuestions.forEach((question) => {
  1113. if (question.type === 'true false') {
  1114. if (question.status) {
  1115. categoryTotal += question.score;
  1116. }
  1117. } else {
  1118. let score = document.querySelector(
  1119. "select[name='" + question.id + "']"
  1120. ).value;
  1121. categoryTotal += parseInt(score);
  1122. }
  1123. });
  1124. categoryScore.textContent = categoryTotal;
  1125. resultsCategoryScore.textContent = categoryTotal;
  1126. };
  1127. Model.prototype.calcGrade = function () {
  1128. const roundedScore = Math.min(100, Math.ceil(this.score));
  1129. let letterGrade = '';
  1130. if (roundedScore >= 97) {
  1131. letterGrade = 'A+';
  1132. } else if (roundedScore >= 93 && roundedScore <= 96) {
  1133. letterGrade = 'A';
  1134. } else if (roundedScore >= 90 && roundedScore <= 92) {
  1135. letterGrade = 'A-';
  1136. } else if (roundedScore >= 87 && roundedScore <= 89) {
  1137. letterGrade = 'B+';
  1138. } else if (roundedScore >= 83 && roundedScore <= 86) {
  1139. letterGrade = 'B';
  1140. } else if (roundedScore >= 80 && roundedScore <= 82) {
  1141. letterGrade = 'B-';
  1142. } else if (roundedScore >= 77 && roundedScore <= 79) {
  1143. letterGrade = 'C+';
  1144. } else if (roundedScore >= 73 && roundedScore <= 76) {
  1145. letterGrade = 'C';
  1146. } else if (roundedScore >= 70 && roundedScore <= 72) {
  1147. letterGrade = 'C-';
  1148. } else if (roundedScore >= 67 && roundedScore <= 69) {
  1149. letterGrade = 'D+';
  1150. } else if (roundedScore >= 63 && roundedScore <= 66) {
  1151. letterGrade = 'D';
  1152. } else if (roundedScore >= 60 && roundedScore <= 62) {
  1153. letterGrade = 'D-';
  1154. } else {
  1155. letterGrade = 'F';
  1156. }
  1157. return letterGrade;
  1158. };
  1159. Model.prototype.hasFlags = function () {
  1160. return this.flagged.size > 0;
  1161. };
  1162. function View(model) {
  1163. this.model = model;
  1164. this.generateHTML();
  1165. this.cacheDom();
  1166. }
  1167. View.prototype.cacheDom = function () {
  1168. this.darkModeToggle = document.querySelector('#dark-mode');
  1169. this.radios = Array.from(
  1170. document.querySelectorAll("input[type='radio']")
  1171. );
  1172. this.selects = Array.from(document.querySelectorAll('select'));
  1173. this.totals = document.querySelector('.totals');
  1174. this.grade = document.querySelector('.grade');
  1175. this.alert = document.querySelector("div[role='alert']");
  1176. this.summaryHeading = document.querySelector('.summary-heading');
  1177. this.summary = document.querySelector('.summary');
  1178. this.model.categories
  1179. .filter((category) => category.title !== 'Academic Integrity')
  1180. .map((category) => {
  1181. let summary = category.title.replace(/\s+/g, '-').toLowerCase();
  1182. let summaryName = summary + '-summary';
  1183. return (this[summaryName] = document.querySelector(
  1184. '.' + summary
  1185. ));
  1186. });
  1187. this.resetButton = document.querySelector('.reset');
  1188. this.model.categories
  1189. .filter((category) => category.title !== 'Academic Integrity')
  1190. .map((category) => {
  1191. let button = category.title.replace(/\s+/g, '-').toLowerCase();
  1192. let buttonName = 'copyButton-' + button;
  1193. return (this[buttonName] = document.querySelector(
  1194. '.copy-' + button
  1195. ));
  1196. });
  1197. };
  1198. View.prototype.createFieldset = function ({
  1199. title,
  1200. id,
  1201. score,
  1202. status,
  1203. category,
  1204. type,
  1205. options,
  1206. }) {
  1207. return `<fieldset class="input-group">
  1208. <legend>
  1209. <span class="title">${title}</span> ${
  1210. category === 'Academic Integrity'
  1211. ? ''
  1212. : "<span class='score'><span data-id='" +
  1213. id +
  1214. "' class='current-score'>0</span> / " +
  1215. score +
  1216. '</span>'
  1217. }
  1218. </legend>
  1219. ${
  1220. type === 'true false'
  1221. ? `<div class="radio-group">
  1222. <input type="radio" name="${id}" id="${id}-true" value="true">
  1223. <label for="${id}-true">True</label>
  1224. </div>
  1225. <div class="radio-group">
  1226. <input type="radio" name="${id}" id="${id}-false" value="false" checked>
  1227. <label for="${id}-false">False</label>
  1228. </div>`
  1229. : `<div class="select-group">
  1230. <select name="${id}" class="select-lg select2 narrow wrap">
  1231. <option value="0" selected>Open this menu</option>
  1232. ${options.map(
  1233. (option) =>
  1234. `<option value="${option.score}">${option.score} - ${option.message}</option>`
  1235. )}
  1236. </select>
  1237. </div>`
  1238. }
  1239. </fieldset>`;
  1240. };
  1241. View.prototype.createCard = function ({
  1242. title,
  1243. subtitle,
  1244. questions,
  1245. score,
  1246. }) {
  1247. return `<div class="card">
  1248. <div class="card-header"><h2>${title}</h2> ${
  1249. title !== 'Academic Integrity'
  1250. ? "<span class='cat-score'><span data-cat='" +
  1251. title +
  1252. "'>0</span> / " +
  1253. score +
  1254. '</span>'
  1255. : ''
  1256. }</div>
  1257. ${subtitle ? `<h3>${subtitle}</h3>` : ''}
  1258. ${questions.map((child) => this.createFieldset(child)).join('')}
  1259. </div>`;
  1260. };
  1261. View.prototype.createResults = function () {
  1262. return `<div class="card">
  1263. <h2>Recommended Grade: <span class="grade">F</span></h2>
  1264. <h2 class="totals">Total: (0/100)</h2>
  1265. <div class="d-none" role="alert">
  1266. You've selected <strong>True</strong> for one of more Academic
  1267. Integrity prompts and are advised to investigate plagiarism before
  1268. issuing a grade.
  1269. </div>
  1270. <div class="summary" aria-label="summary">
  1271. ${this.model.categories
  1272. .filter((category) => category.title !== 'Academic Integrity')
  1273. .map((category) => {
  1274. let score = category.score;
  1275. let title = category.title;
  1276. let catClass = title.replace(/\s+/g, '-').toLowerCase();
  1277. return (
  1278. '<h2>' +
  1279. title +
  1280. "<span class='cat-score'><span data-res-cat='" +
  1281. title +
  1282. "'>0</span> / " +
  1283. score +
  1284. "</span></h2><pre class='" +
  1285. catClass +
  1286. "'>" +
  1287. [...this.model.messages]
  1288. .filter((messageObj) => messageObj.category === category.title)
  1289. .map((messageObj) => `* ${messageObj.message}\n`)
  1290. .join('') +
  1291. "</pre><button class='copy copy-" +
  1292. catClass +
  1293. "'>Copy To Clipboard</button>"
  1294. );
  1295. })
  1296. .join('')}
  1297. </div>
  1298. <div class="btn-container">
  1299. <button class="reset">Reset Form</button>
  1300. </div>
  1301. </div>`;
  1302. };
  1303. View.prototype.createHeader = function (title, isDarkMode = false) {
  1304. return `<header>
  1305. <h1>${title}</h1>
  1306. <div class="switch-group">
  1307. <input type="checkbox" name="dark-mode" id="dark-mode" ${
  1308. isDarkMode ? 'checked' : ''
  1309. }>
  1310. <label aria-label="dark mode" for="dark-mode" class="toggle">
  1311. </label>
  1312. </div>
  1313. </header>`;
  1314. };
  1315. View.prototype.generateHTML = function () {
  1316. this.wrapper = document.querySelector('#app');
  1317. if (this.model.isDarkMode) {
  1318. this.wrapper.classList.add('dark');
  1319. }
  1320. const survey = this.model.categories.reduce((final, current) => {
  1321. final += this.createCard(current);
  1322. return final;
  1323. }, '');
  1324. const results = this.createResults();
  1325. const header = this.createHeader(
  1326. this.model.title,
  1327. this.model.isDarkMode
  1328. );
  1329. this.wrapper.innerHTML = header + survey + results;
  1330. };
  1331. View.prototype.update = function () {
  1332. const roundedScore = Math.min(100, Math.ceil(this.model.score));
  1333. this.totals.textContent = `Total: (${roundedScore}/ 100)`;
  1334. const letterGrade = this.model.calcGrade();
  1335. this.grade.textContent = letterGrade;
  1336. if (this.model.hasFlags()) {
  1337. this.alert.classList.remove('d-none');
  1338. } else {
  1339. this.alert.classList.add('d-none');
  1340. }
  1341. if (this.model.isDarkMode) {
  1342. this.wrapper.classList.add('dark');
  1343. } else {
  1344. this.wrapper.classList.remove('dark');
  1345. }
  1346. this.model.categories
  1347. .filter((category) => category.title !== 'Academic Integrity')
  1348. .forEach((category) => {
  1349. let catClass = category.title.replace(/\s+/g, '-').toLowerCase();
  1350. this[catClass + '-summary'].innerHTML = [...this.model.messages]
  1351. .filter((messageObj) => messageObj.category === category.title)
  1352. .map((messageObj) => `* ${messageObj.message}\n`)
  1353. .join('');
  1354. });
  1355. };
  1356. function Controller(model, view) {
  1357. this.model = model;
  1358. this.view = view;
  1359. this.addEventListeners();
  1360. }
  1361. Controller.prototype.addEventListeners = function () {
  1362. this.view.wrapper.addEventListener('change', (event) => {
  1363. const { target } = event;
  1364. if (target.name !== 'dark-mode') {
  1365. if (!target.matches("input[type='radio']")) {
  1366. this.model.handleSelect(target);
  1367. } else {
  1368. this.model.toggleRadio(target);
  1369. }
  1370. }
  1371. this.view.update();
  1372. });
  1373. this.view.resetButton.addEventListener('click', () => {
  1374. this.model.initStorage();
  1375. this.view.radios.forEach(
  1376. (radio) => (radio.checked = radio.hasAttribute('checked'))
  1377. );
  1378. this.view.selects.forEach(
  1379. (select) => (select.selectedIndex = null)
  1380. );
  1381. this.model.questions
  1382. .filter((question) => question.category !== 'Academic Integrity')
  1383. .forEach((question) => {
  1384. const id = question.id;
  1385. const questionScore = document.querySelector(
  1386. "span[data-id='" + id + "']"
  1387. );
  1388. questionScore.textContent = 0;
  1389. });
  1390. this.model.categories
  1391. .filter((category) => category.title !== 'Academic Integrity')
  1392. .forEach((category) => {
  1393. const title = category.title;
  1394. const categoryScore = document.querySelector(
  1395. "span[data-cat='" + title + "']"
  1396. );
  1397. const resultsCategoryScore = document.querySelector(
  1398. "span[data-res-cat='" + title + "']"
  1399. );
  1400. categoryScore.textContent = 0;
  1401. resultsCategoryScore.textContent = 0;
  1402. });
  1403. this.view.update();
  1404. window.scrollTo(0, 0);
  1405. });
  1406. this.model.categories
  1407. .filter((category) => category.title !== 'Academic Integrity')
  1408. .forEach((category) => {
  1409. let title = category.title.replace(/\s+/g, '-').toLowerCase();
  1410. let buttonName = 'copyButton-' + title;
  1411. let summaryName = title + '-summary';
  1412. this.view[buttonName].addEventListener('click', () => {
  1413. const selection = window.getSelection();
  1414. const range = document.createRange();
  1415. range.selectNodeContents(this.view[summaryName]);
  1416. selection.removeAllRanges();
  1417. selection.addRange(range);
  1418. document.execCommand('copy');
  1419. });
  1420. });
  1421. this.view.darkModeToggle.addEventListener('change', () => {
  1422. this.model.toggleDarkMode();
  1423. this.view.update();
  1424. });
  1425. };
  1426. function App(Model, View, Controller) {
  1427. this.model = new Model();
  1428. this.view = new View(this.model);
  1429. this.controller = new Controller(this.model, this.view);
  1430. }
  1431. return new App(Model, View, Controller);
  1432. })(window);
  1433. </script>
  1434. </body>
  1435. </html>