Skip to content

New Lab Creation

AndreasLc1103 edited this page Jan 16, 2024 · 38 revisions

Front End

Before adding content for the lab, we need to perform some pre-work that will be required for sections covered later on.
Note: lab# refers to lab and the number of the lab. (Ie. lab5)
Note: All folder paths assume we are starting in the client folder

Pre-Work

  1. Creating folders in the following sections:
    1. src > components > body: create a 'lab#' folder. This will hold the about, reading and video file
    2. src > components > exercise: create a 'lab#' folder. This will hold all the exercise components
      1. src > components > exercise > lab#: create a 'components' folder. This will hold all shared exercise components for that lab
      2. src > components > exercise > lab#: create a 'pages' folder. This will hold the individual exercise pages
    3. src > components > quiz > api: create a 'Lab#' folder. This will hold all the quiz questions
    4. src > constants: create a 'lab#' folder. This will hold the constants file for the lab
    5. src > reducers: create a 'lab#' folder. This will hold lab-specific reducers - App, Exercise, and RepairReducer
    6. src > services: create a 'lab#' folder. This will hold lab-specific APIs to connect to the backend- Exercise, and RepairService

Next, we will employ a top-down strategy, starting with the top of the directory and work downward.

Constants

The constants files help to determine the state of the exercise, used by components like the footer and header

First update the existing constant file in the src > constants > index.js file. This file contains the names and section headers for each lab
Append the following code to the constant variable: 'sections' inside index.js

{
    labid :{
        fullname: "Lab Name",
        name: "Lab #",
        0:{
            name: "About"
        },
        1:{
            name: "Reading"
        },
        2:{
            name: "Exercise"
        },
        3:{
            name: "Video"
        },
        4:{
            name: "Quiz"
        }
    }
 }

Create an index.js file inside src > constants > lab# with the following content

    export const GAME_PLAYING = 'GAME_PLAYING';
    export const GAME_IDLE = 'GAME_IDLE';
    export const LAB_ID = <replace with lab number>

Body

In this section we will be adding the about, reading and video components

About

This is a database-driven component that gives an introduction to the lab and the topics that will be covered. To add an about section for a new lab, you must log in to DataGrip using the all_db@localhost database, go to the database labelled 'testing' at ball.rit.edu, and find the 'labs' table. If the lab you are currently working on has been entered into the table, find the 'about' column for the specified lab, and enter the About text for the lab. Below is a working example of how an About section should look:

In this lab, you will learn about the importance of [lab topic]. You will learn about issues related to a [problems presented by the lab topic], increase your understanding through an interactive module about [lab topic], view related media to reinforce the topic, and take a quiz to test your knowledge. Click "Next" to start!

import React, { useEffect, useState } from "react";
    import Spinner from "../../common/Spinner/Spinner";
    import LabService from "../../services/LabService";
    import UserLabService from "../../services/UserLabService";
    import useScroll from "../../use-hooks/useScroll";

    const About = (props) => {
      const { user, labID } = props;
      const [aboutText, setAboutText] = useState(null);

      useScroll();

      useEffect(() => {
        if (user?.firstname !== null && user !== null) {
          UserLabService.user_complete_about(user.userid, labID);
        }
      UserLabService.complete_about(labID);
      LabService.getLabAbout(labID).then((data) => {
        setAboutText(data[0]);
      });
    }, [user, labID]);

  if (!aboutText) {
    return (
      <div className="landingpage__row">
        <Spinner />
      </div>
    );
  }

  return (
    <div className="study">
      <p>{aboutText?.about}</p>
    </div>
  );
};

export default About;

Reading

This is a database-driven component that gives context to the lab topic and what the participant will be learning. To add an reading section for a new lab, you must log in to DataGrip using the all_db@localhost database, go to the database labelled 'testing', and find the 'labs' table. If the lab you are currently working on has been entered into the table, find the 'reading' column for the specified lab, and enter information for both the pie chart and reading paragraphs for the lab. Below is a working example of how an Reading section should be inputted in the reading column, including how to set up the pie chart on top of the page:

    "piechart":
       {"header":"Pie chart header name",
       "caption":[""],
       "data":{
          "labels": [
                "Data Label", "Data Label", "Data Label", "Data Label"
             ],
          "datasets": [
                   {
                   "label": "Dataset Label",
                   "borderColor": "Black",
                   "backgroundColor": [
                      "#283D3B",
                      "#197278",
                      "#83A8A6",
                      "#EDDDD4"
                  ],
                   "data": [59,44,32,29],
                   "borderWidth": "1"
                   }
             ]
          }
    },
    "description":{
       "header":"Reading Header",
       "content":"Reading content (use sourcing and data to back up content assertions)"
    },
    "body":[
        {
            "header":"Body Header",
            "type":"study__list",
            "content":["Body content (use data from sourced"]
        },
       {
          "header":"Body Header",
          "type":"",
          "content":["Body Content"]
       }
    ],
    "footer":{
       "links":[
            {
                "name":"Source Name",
                "link":"Source link"
            },
            {
                "name":"Source Name",
                "link":"Source link"
            },
            {
                "name":"Source Name",
                "link":"Source link"
            }
        ]
    }
import React, { Fragment, useEffect, useState } from "react";
import UserLabService from "../../../services/UserLabService";
import LabService from "../../../services/LabService";
import { Pie } from "react-chartjs-2";
import useScroll from "../../../use-hooks/useScroll";
import StudyList from "./studylist";
import NonBulletList from "./NonBulletList";
import Image from "./Image";
import Spinner from "../../../common/Spinner/Spinner";
import LinkFooter from "./LinkFooter";
import Links from "./Links";
import OrderedList from "./OrderedList";

const Reading = (props) => {
  const { user, labID } = props;
  const [readingData, setReadingData] = useState("");

  useScroll();
  useEffect(() => {
    UserLabService.complete_reading(labID);
    if (user?.firstname !== null && user !== null) {
      UserLabService.user_complete_reading(user.userid, labID);
    }
    LabService.getLabReading(labID).then((data) => {
      setReadingData(data[0].reading);
    });
  }, [user, labID]);

  if (!readingData) {
    return (
      <div className="landingpage__row">
        <Spinner />
      </div>
    );
  }

  return (
    <div className="study">
      {readingData?.description !== "" ? (
        <>
          <h3>{readingData?.description.header}</h3>
          <p>{readingData?.description.content}</p>
        </>
      ) : (
        <></>
      )}
      {readingData?.piechart?.header && (
        <>
          <h3>{readingData?.piechart.header}</h3>
          <div className="flex">
            <Pie data={readingData?.piechart.data} height={100} />
          </div>
        </>
      )}
      {readingData?.piechart.caption !== "" ? (
        readingData?.piechart.caption.map((data, index) => {
          return (
            <div key={index} id={"caption"}>
              {data}
            </div>
          );
        })
      ) : (
        <></>
      )}

      {readingData?.body !== "" ? (
        readingData?.body.map((data, index) => {
          return (
            <Fragment key={index}>
              {data.header !== "" && <h3>{data.header}</h3>}
              {data.type === "" && (
                <>
                  {data.content.map((content, index) => {
                    return <p key={index}>{content}</p>;
                  })}
                </>
              )}
              {data.type === "study__list" && <StudyList data={data.content} />}
              {data.type === "ordered-list" && (
                <OrderedList data={data.content} />
              )}
              {data.type === "non-bullet-list" && (
                <NonBulletList data={data.content} />
              )}
              {data.type === "image" && <Image data={data.content} />}
              {data.type === "links" && <Links data={data.content} />}
            </Fragment>
          );
        })
      ) : (
        <></>
      )}
      {readingData?.footer !== "" ? (
        <LinkFooter data={readingData?.footer.links} />
      ) : (
        <></>
      )}
    </div>
  );
};

export default Reading;

Video

This video component displays empathy-creating videos from youtube Create a video.js file inside src > components > body > lab# with the following content

    import React, {useEffect} from "react";
    
    import {LAB_ID} from '../../../constants/<lab#>';
    import UserLabService from '../../../services/UserLabService';
    const Video = () => {
        useEffect(() => {
            return () => {
                UserLabService.complete_video(LAB_ID);
            }
        });
      return (
          <div>
          <div className="row">
            <h4>Here are some videos to aid in understanding the material.</h4>
          </div>
          <div className="row">
              <iframe></iframe>
          </div>
          <div className="row">
            <p>Video Description</p>
          </div>
          <div className="row">
              <iframe></iframe>
          </div>
          <div className="row">
            <p>Video Description</p>
          </div>
        </div>
      );
    };
    
    export default Video;

Landing Page

We need to add a clickable module to the landing page and also include links to the new lab in the sitemap

Index.js

Before we add code to index.js (landing page), we need to add the lab image to the appropriate folder

Inside client > src > assets > labs upload the image. Ensure it is a square resolution (ie. 700 X 700px)

Once the image is uploaded, we add the following code to include the new lab on the landing page

    <div class="row"> //IF THIS IS AN ODD LAB IE LAB 3, THEN DON'T CREATE A NEW ROW
      <div class="col-md-4 portfolio-item">
        <a
          class="portfolio-link"
          onClick={() => handleRedirect(actions, lab#)}
          href="# "
        >
          <img
            class="img-fluid landingpage__image"
            src={ } //IMPORT THE IMAGE AT THE TOP OF THE FILE AND REFERENCE HERE
            alt=" INSERT ALT TAG"
          />
        </a>
        <div class="portfolio-caption">
          <h4>
            <a onClick={() => handleRedirect(actions, lab#)} href="# "
            >Lab Title</a
            >
          </h4>
          <p class="">
            Description of lab
          </p>
        </div>
      </div>
      </div>

Sitemap.js

Add the following code to include the new lab in the sitemap

    <div class="col-md-4">
      <h4 class="service-heading">
        <a href="# " onClick={() => handleRedirect(actions, <lab#>, 0)} >Lab #</a> //REPLACE WITH ACTUAL LAB #
      </h4>
      <ul>
        <li><a href="# " onClick={() => handleRedirect(actions,<lab#>,0)} >About</a></li>
        <li><a href="# " onClick={() => handleRedirect(actions,<lab#>,1)} >Reading</a></li>
        <li><a href="# " onClick={() => handleRedirect(actions,<lab#>,2)} >Exercise</a></li>
        <li><a href="# " onClick={() => handleRedirect(actions,<lab#>,3)} >Video</a></li>
        <li><a href="# " onClick={() => handleRedirect(actions,<lab#>,4)} >Quiz</a></li>
      </ul>
    </div>

Exercise

The exercise is the central component of the lab. It uses the following format:

  1. User experiences the software as a normal user
  2. User experiences the software through emulation lens
  3. Exercise discusses the problems and solutions to address the inaccessible software
  4. User repairs the software using code editor
  5. User views the accessible software with emulation lens

When developing the lab, keep in mind the above format and ensure there is a exercisepage for each step listed.
It is helpful to sort the activities within the lab into their own folder within src > components > exercise > lab# > pages
Also, any commonly used components in the activities should be placed into the src > components > exercise > lab# > components folder. Ie: a form that is reused or a Q/A type component.

We will be covering the following files in this section:

  • Main.js: Imports all the exercise pages and is the component we call in App.js
  • ExerciseStart.js: First page user lands on in the Exercise section, think of this as the instruction screen
  • DementiaAccessible.js: An example exercise page to demonstrate usage of pageservicetimer
  • Repair.js: The repair page where user makes repairs of the exercise
  • ExerciseEnd.js: This component calls the exerciseComplete endpoint to let the database know the user has completed the exercise

Main.js

Create a Main.js file inside src > components > exercise > lab#

Here is an example of the file

    import React, {Component} from 'react';
    import {connect} from 'react-redux';
    import {Router} from '@reach/router';
    import '../../../assets/stylesheets/main.scss';
    import {actions as exerciseActions } from "../../../reducers/lab#/ExerciseReducer";
    import {actions as repairActions } from '../../../reducers/lab#/RepairReducer';
    import ExerciseStart from './pages/ExerciseStart';
    import PageLayoutRepair from "./pages/PageLayoutActivity/PageLayoutRepair";
    import DementiaAccessible from './pages/PageLayoutActivity/DementiaAccessible';
    import ExerciseEnd from "./pages/ExerciseEnd";
    const mapStateToProps = (state) => ({
     state: state
    });
  
    const mapDispatchToProps = dispatch => {
    return {
      actions: bindActionCreators({ ...exerciseActions, ...repairActions}, dispatch),
    };
    };
  
  
    class Main extends Component {
  
    render() {
      const {actions, state} = this.props;
      return (
        <div className="bottomSpace" >
          <Router className="app">
            <ExerciseStart default path="/" actions={actions}/>
            <PageLayoutRepair path ="/PageLayoutRepair" visible={state.repair5.repairVisible} data={state.repair5}
                              handlers={actions} state ={state} actions={actions}/>
            <DementiaAccessible path="/DementiaAccessible" actions={actions} state={state}/>
            <ExerciseEnd path="/ExerciseEnd" actions={actions} state={state} />
          </Router>
        </div>
      );
    }
    }
  
    export default connect(mapStateToProps, mapDispatchToProps)(Main);

ExerciseStart.js

DementiaAccessible.js

Repair.js

The repair component is essential to each exercise, as this is the interactive section for the participant. When developing a Repair.js file, there is a component library developed specifically for implementing the interactive codeblocks used in the repair sections that will highlight inputted text based on the code language the developer is looking to emulate. The code block components can be found in the Client > src > components > all-components > CodeBlockComponents folder in the repository. These components include:

  1. Codeblock.js - The dynamic black background used to hold all of the text components used in the code block
  2. CodeLine.js - Functions as a flex row wrapper that will line up all of its children in a single line of code
  3. ReactText.jsx - ReactText is a wrapper class that will style its children by parsing the inputted text for keywords, such as const, var, useState, useEffect, etc and color those words in the codeblock similarly to how React.js would look in an IDE. Only a string value can be inputted in ReactText.
  4. JSONText.jsx - JSONText.jsx is a wrapper class that will style its children by parsing the inputted text for operators, and colored the rest of the text a singular color, similar to how a JSON file looks in an IDE. Only a string value can be inputted.
  5. HTMLTag.jsx - Another wrapper class that is implemented when the developer would like to insert HTML elements into the codeblock, the HTMLTag will automatically style those elements. Only a String can be passed in.
  6. HTMLText.jsx - A simple wrapper class that is implemented when the developer wishes to insert text under an HTMLTag component, and colors the text a singular color as it would appear in an IDE.
  7. Tab.jsx - A Tab component will move an element 3 spaces to the right without need to insert multiple HTML spaces. Should be called inside of a CodeLine component, NOT inside of any of the Text or Tag components.
  8. CodeBlockInput.jsx - A styled input that will accept any standard input parameters. Should be called inside a CodeLine component.
     <CodeBlock fileName={fileName}>
          <ReactText>export const ReactText = (props) = &#123;</ReactText>

          <CodeLine>
            <Tab /> <ReactText> const handleNext = () =&#62; </ReactText>
          </CodeLine>

          <CodeLine>
            <Tab /> <Tab />
            <ReactText> const input = </ReactText>
            <CodeBlockInput
              attributes={{
                id: 1,
                name: "example",
                type: "text",
                defaultValue: 1,
                placeholder: "Enter Code Here",
              }}
            />
            <ReactText>;</ReactText>
          </CodeLine>

          <ReactText>&#125;</ReactText>
          <ReactText>export default ReactText;</ReactText>

          <JSONText> variable: jsonvariable </JSONText>

          <HTMLTag> &#60;div className = &ldquo;coolCSS&rdquo;&#62; </HTMLTag> // This will appear as <div className = "coolCSS">

          <CodeLine>
            <Tab />
            <HTMLText> hello this is html text </HTMLText>
          </CodeLine>

          <HTMLTag>&#60;/div&#62;</HTMLTag>
        </CodeBlock>

A quick breakdown of the above code shows how to implement each of these Repair components, such that they will display the proper coloring and styling based on code the developer plans to implement in the code block.

ExerciseEnd.js

Quiz

This is a database-driven component that handles the generation of quiz questions for the specified lab. To add a new set of questions for a Quiz section for a new lab, you must log in to DataGrip using the all_db@localhost database, go to the database labelled 'testing', and find the 'labs' table. If the lab you are currently working on has been entered into the table, find the 'quiz' column for the specified lab, and enter the agreed upon questions and their respective answers. Below is a working example of how a Quiz section should be inputted in the quiz column, including how to set up correct/incorrect answers and allow multiple correct answers:

[
  {
    question: "[Quiz question]",
    answers: [
      {
        val: 0, <- If this answer is incorrect, set the value to 0
        type: "0",
        content:
          "[Quiz answer option]",
      },
      {
        val: 1, <- If answer is correct, set the value to 1
        type: "1",
        content:
          "[Quiz answer option]",
      },
      {
        val: 0,
        type: "2",
        content:
          "[Quiz answer option]",
      },
      {
        val: 0,
        type: "3",
        content: "[Quiz answer option]",
      },
    ],
    multiChoice: false, <- To set if multiple answers are required, select True. If only one answer is correct, select false
  },
  {
    question:
      "[Quiz question]",
    answers: [
      {
        val: 1,
        type: "0",
        content: "[Quiz answer option]",
      },
      {
        val: 1,
        type: "1",
        content: "[Quiz answer option]",
      },
      {
        val: 1,
        type: "2",
        content: "[Quiz answer option]",
      },
      {
        val: 1,
        type: "3",
        content: "[Quiz answer option]",
      },
      {
        val: 0,
        type: "4",
        content: "[Quiz answer option]",
      },
    ],
    multiChoice: true,
  }
];

export default quizQuestionsLab[lab#];

Quiz App.js

Another change to make is in the App.js file found in src > components > quiz

  1. Import the quiz questions at the top of the file like so: import quizQuestionsLab# from './api/Lab#/quizQuestions';
  2. Next, add a case to the switch statement inside assignQuizQuestions() function (around line 55), like so: case #: return quizQuestionsLab#. This will render the correct quiz questions on the site once the user navigates to the new lab

Reducers

The reducers allow us to manage the state of the application so other components can appropriately update based on state changes. For example, if the application is in Exercise Start mode, the footer will disappear to prevent in-exercise color/font changes.
For a new lab, we will be adding a ExerciseReducer and RepairReducer.

ExerciseReducer

The exercise reducer manages the exercise start and exercise end. Once the user starts the exercise, we dispatch a start exercise action to this reducer in order to change the state, so components like the footer can be aware the exercise has started. Similarly, once the exercise ends, we can change the state so components like the navigation can be enabled again.

Create ExerciseReducer.js inside src > components > exercise > lab# with the following content

	import {
		GAME_IDLE
	} from '../../constants/lab#/index'; //UPDATE LAB #
	
	export const types = {
		UPDATE_STATE: '@accessibility-lab/lab#/exercise/update_state', //UPDATE LAB #
		ENABLE_END: '@accessibility-lab/lab#/exercise/enable_end'
	};
	
	export const initialState = {
		state: GAME_IDLE,
		end: false
	};
	
	export default (state = initialState, action) => {
		switch (action.type) {
			case types.UPDATE_STATE:
				return {
					...state,
					state: action.state
				};
			case types.ENABLE_END:
				return {
					...state,
					end: action.state
				};
	
			default:
				return state;
		}
	};
	
	export const actions = {
		updateState: (state) => ({ type: types.UPDATE_STATE, state }),
		enableEnd: (state) => ({ type: types.ENABLE_END, state })
	};

RepairReducer

The repair reducer holds the repair changes made in exercise so that the user may perform repairs again and so that the repaired exercise may know about the changes.

Create RepairReducer.js inside src > components > exercise > lab# with the following content

	export const types = {
		UPDATE_REPAIR: '@accessibility-lab/lab#/repair/update_repair',
		UPDATE_TAB: '@accessibility-lab/lab#/repair/update_tab',
		OPEN_REPAIR: '@accessibility-lab/lab#/repair/open_repair',
		CLOSE_REPAIR: '@accessibility-lab/lab#/repair/close_repair',
		UPDATE_POPUP: '@accessibility-lab/lab#/repair/update_popup'
	};
	
	export const initialState = {
		cowAltValue: '', //REPAIR VALUE, REPLACE WITH RELEVANT VARIABLE NAME
		currentTab: 1,
		repairVisible: false,
		changesApplied: false,
		popupMessage: ''
	};
	export default (state = initialState, action) => {
		switch (action.type) {
			case types.UPDATE_REPAIR:
				return {
					...state,
					cowAltValue: action.cowAltValue //REPLACE HERE TOO
					changesApplied: true
				};

			case types.UPDATE_TAB:
				return {
					...state,
					currentTab: action.tab
				};

			case types.OPEN_REPAIR:
				return {
					...state,
					repairVisible: true
				};

			case types.CLOSE_REPAIR:
				return {
					...state,
					repairVisible: false
				};
			case types.UPDATE_POPUP:
				return {
					...state,
					popupMessage: action.message
				};
			default:
				return state;
		}
	};

	export const actions = {
		updateRepair: (cowAltValue) => ({ // PASS IN APPROPRIATE VALUE
			type: types.UPDATE_REPAIR,
			cowAltValue
		}),
		updatePopup: (message) => ({ type: types.UPDATE_POPUP, message }),
		updateTab: (tab) => ({ type: types.UPDATE_TAB, tab }),
		openRepair: () => ({ type: types.OPEN_REPAIR }),
		closeRepair: () => ({ type: types.CLOSE_REPAIR })
	};

Index.js

Once the reducers are created we need to import them in the index.js file, so they can be accessed anywhere in the application

Import the reducers like so:

    import RepairReducer# from './lab#/RepairReducer';
    import ExerciseReducer# from './lab#/ExerciseReducer';

Next, add them to the combineReducers method:

    exercise#: ExerciseReducer#,
    repair#: RepairReducer#,

Helpers

The helpers are used by the redux reducers and the site navigation. Thus, we need to make the following changes inside the GetReducer and Redirect File.

GetReducer

This file allows the rest of the site to know about the exercise state. Thus, we need to connect the lab's exercise reducer to the switch statement.
Add the following line: case #: return state.exercise#.state obove the default case.

Redirect

This file allows the site to display the correct lab on page load and update the redux appropriately. Inside the stateChange() function, locate the switch statement and add the following code: case "Lab#": actions.setLab(#); break; above the case "". (around line 70)

Services

This section allows us to make the appropriate api calls to our backend to retrieve (GET) or store (POST) our data from the front end. We will be adding lab-specific files in this section: ExerciseService and RepairService.

ExerciseService

The ExerciseService allows us to store exercise-specific data in our backend such as options the user selected in the exercise. Keep in mind repair specific data is covered in RepairService and time spent on any given page is already handled by PageService.js which does not need to be modified.

Create ExerciseService.js inside src > services > lab# . The following is an example of what that exerciseservice looks like. The specifics depend on the construction of the exercise and what you want to track during the exercise.

    import API from '../API';
	
	const endpoints = {
		SUBMIT_CHOICE: '/lab5/exercise/choice',
	};
	
	export default {
		submitChoice: (correct, question,selectedoption,
					   options) => {
			return API.postWithBody(process.env.REACT_APP_SERVER_URL + endpoints.SUBMIT_CHOICE, {
				correct,
				question,
				selectedoption,
				options
			});
		}
	};

Here is the breakdown of the code above:

  1. Notice, we import the API file above, this allows us to leverage the existing POST and GET calls.
  2. We define the comma-separated endpoints using a constant.
  3. We export the submitChoice()function so that the exercise can call this function. If you have more than one function, simply append it below using a comma
    1. Notice the parameters are the react server url (our backend) + the specific endpoint
    2. We pass into the body our data we want to store. In this case we are storing data related to in-exercise question/answers.

RepairService

The RepairService allows us to store information pertaining to the repair. Every time the user clicks submit repair, this service is called.
Create RepairService.js inside src > services > lab# The code flow is much like the ExerciseService, however, there are two ways of structuring it. If there is only one repair performed in the exercise, then you may pass in the body the exact choices made by the user like so:

    export default {
        submitRepair: (cowAltValue,
                       carAltValue,
                       burgerAltValue,
                       catAltValue) => {
            return API.postWithBody(process.env.REACT_APP_SERVER_URL + endpoints.SUBMIT_REPAIR, {
                cowAltValue,
                carAltValue,
                burgerAltValue,
                catAltValue
            });
        }
    };

However, if there are multiple repairs, you will want to specify the activity that the repair is for and the value in a json format (since tables shouldn't have multivalued attributes)

    export default {
        submitRepair: (activity, repair) => {
            return API.postWithBody(process.env.REACT_APP_SERVER_URL + endpoints.SUBMIT_REPAIR, {
                activity, repair
            })
        }
    };

Notice, repair is a json object constructed in the exercise repair

App.js

The App.js file declares all the routes for the front end. So, when a user navigates to all.rit.edu/Lab#, the proper component will be displayed.
In order to add the new lab to the site, we need to add it's routes as well.
Begin by importing in the lab components at the top of app.js

    import {default as AboutLab#} from "./components/body/lab#/about";
    import {default as ReadingLab#} from "./components/body/lab#/reading";
    import {default as ExerciseLab#} from "./components/exercise/lab#/Main";
    import {default as VideoLab#} from "./components/body/lab#/video";

Next, we need to add the imported component into the router (around line 80). Note, the components are grouped together (ie Reading, Exercise, etc). Please add new imports in the respective groups for readability and organization purposes.

  1. About Component: Add <AboutLab# path="/Lab#/"/> and <AboutLab5 path="/Lab5/About"/>
  2. Reading Component: Add <ReadingLab5 path="/Lab5/Reading"/>
  3. Exercise Component: Add <ExerciseLab5 path="/Lab5/Exercise/*" />
  4. Video Component: Add <VideoLab5 path="/Lab5/Video" />

Back End

Just like the front end, we need to perform some pre-work that will be required for sections covered later on.
Note: lab# refers to lab and the number of the lab. (Ie. lab5)
Note: All folder paths assume we are starting in the server folder

Pre-Work

  1. Creating folders in the following sections:
    1. controllers: create a 'lab#' folder. This will hold the controller files - ExerciseController and RepairController
    2. services: create a 'lab#' folder. This will hold the services files which correspond to the controller files - ExerciseService and RepairService
    3. database > models: create a 'lab#' folder. This will hold the model files specific to the lab - Exercise and Repair
      1. src > components > exercise > lab#: create a 'components' folder. This will hold all shared exercise components for that lab
      2. src > components > exercise > lab#: create a 'pages' folder. This will hold the individual exercise pages

Next, we will employ a top-down strategy, starting with the top of the directory and work downward.

Routes

The routes allow us to receive the API calls from the front-end services and appropriately route them to the controllers so we can add/update/retrieve data from our database.

We will begin by adding our controller inputs to the file. Keep in mind, the controllers haven't been created yet, however, for organizational reasons we update the routes file first.

As mentioned above, the repair and exercise service send the exercise and repair information to the backend. Thus we need to import the controllers like so:

    //LAB5 Controller
    let RepairControllerLab# = require('../controllers/lab#/RepairController');
    let ExerciseControllerLab# = require('../controllers/lab#/ExerciseController');

Next, we declare the routes and call the imported controllers

    router.post('/lab#/exercise/choice', ExerciseControllerLab#.submitChoice) 
    router.post('/lab#/repair/submit', RepairControllerLab#.submitChange);

The exercise route will be different depending on what is stored and the exact endpoint in ExerciseService.js in the client side

Controllers

The controllers allow us to dispatch the request to appropriate server-side services and respond back to the front end with a success message upon successful data retrieval/creation

We will be creating a repair and exercise controller that match the routes we declared earlier

Exercise Controller

This controller stores exercise relevant data into the database

Create ExerciseController.js inside controllers > lab#

    const ExerciseService = require('../../services/lab#/ExerciseService');

   async function submitChoice(req, res){
       const { body, session } = req;
       const { token } = session;
       const { question, correct, options } = body;
       try {
          const response = await ExerciseService.submitChoice({
                usersessionid: token, //HELPS TO CONNECT GAME DATA TO A USER
                question: question, //DATA TO BE STORED, SENT THROUGH req.body
                correct: correct, //DATA TO STORE
                options: options, //DATA TO STORE
                selectedoption: selectedoption, //DATA TO STORE
          });
          res.sendStatus(200); //SENDS SUCCESS CODE TO THE FRONTEND
          return response;
       } catch (err) {
          console.error(err);
       }

       module.exports = {
        submitChoice
      }
    };

RepairController

This controller stores the repair information into the database

Create RepairController.js inside controllers > lab# An example of this looks like:

  const RepairService = require('../../services/lab#/RepairService');
  
  async function submitChange(req, res){
   try {
       // destructure the body of the request
      const {token, cowAltValue} = req.body
      const response = await RepairService.submitChange({
        usersessionid: token, //ASSOCIATES DATA TO USER
        cowAltValue: cowAltValue //THIS 'body.cowAltValue' NEEDS TO MATCH THE PARAMETER NAME IN REPAIRSERVICE.JS CLIENT SIDE 
      });
      return response;   
    } catch(err) {
    console.error(err);
    }

  module.exports{
    submitChange
  }
 }

Services

Now that the controller is created, we need the respective services that will interact with the database model to store the data

We will create a exercise and repair service

ExerciseService

Create ExerciseService.js inside services> lab#

  const db = require('../../database');
  async function submitChoice(data){    
    try{
      if (data.usersessionid){
        const response  = await db.ExerciseLab#.create({
          usersessionid: data.usersessionid,
          question: data.question,
          correct: data.correct,
          selectedoption:data.selectedoption,
          options: data.options
        });
        return response;
      }
    }catch((err) => {
      console.log(err);
    })
 }

RepairService

Create RepairService.js inside services> lab#

	
  const db = require('../../database');
  async function submitChange(data){
  try {
    const created = await db.RepairLab5.create({
     usersessionid: data.usersessionid, //USER TO STORE
     cowAltValue: data.cowAltValue //REPAIR DATA TO STORE
   });
    return true;
  }catch (err) {
    console.error(err);
  }
 }

  module.exports = {
    submitChange
  }
;

Model

The model defines the column attributes of the table we are creating in the database. We will be creating a table for the repair and the exercise.

Exercise

Create Exercise.js inside services> lab#

	module.exports = (sequelize, DataTypes) => {
		const Exercise = sequelize.define(
			'ExerciseLab#',
			{
				exerciseid: {                         //AUTOPOPULATE A GAMEID FOR EACH ENTRY
					type: DataTypes.INTEGER,
					unique: true,
					primaryKey: true,
					autoIncrement: true
				},
				usersessionid: {
					type: DataTypes.BIGINT
				},
				correct: { type: DataTypes.BOOLEAN }, //THE DATA TO STORE, SPECIFY THE TYPE: BOOLEAN< STRING, OR INTEGER
				question: { type: DataTypes.STRING },
				selectedoption: { type: DataTypes.STRING },
				options: { type: DataTypes.STRING }
			},
			{ tableName: 'lab#_exercise' }   //REPLACE WITH LAB#
		);
		Exercise.sync();
		return Exercise;
	};

Repair

Create Repair.js inside services> lab#

	module.exports = (sequelize, DataTypes) => {
		const Repair = sequelize.define(
			'RepairLab#',
			{
				repairid: {
					type: DataTypes.INTEGER,
					unique: true,
					primaryKey: true,
					autoIncrement: true
				},
				usersessionid: {
					type: DataTypes.BIGINT
				},
				cowAltValue: {                 //REPAIR DATA TO STORE
					type: DataTypes.STRING
				}
			},
			{ tableName: 'lab#_repair' } //REPLACE LAB #
		);
		Repair.sync();
		return Repair;
	};

Ending

That's all! You've completed the lab. Now, you will open a pull request on github and once merged, see it published onto the site. If you have any questions, please refer to the existing code base for code patterns and examples. In addition, documentation on the database entities can be found in the documentation directory inside the repository.