-
Notifications
You must be signed in to change notification settings - Fork 2
New Lab Creation
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
- Creating folders in the following sections:
- src > components > body: create a 'lab#' folder. This will hold the about, reading and video file
- src > components > exercise: create a 'lab#' folder. This will hold all the exercise components
- src > components > exercise > lab#: create a 'components' folder. This will hold all shared exercise components for that lab
- src > components > exercise > lab#: create a 'pages' folder. This will hold the individual exercise pages
- src > components > quiz > api: create a 'Lab#' folder. This will hold all the quiz questions
- src > constants: create a 'lab#' folder. This will hold the constants file for the lab
- src > reducers: create a 'lab#' folder. This will hold lab-specific reducers - App, Exercise, and RepairReducer
- 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.
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>
In this section we will be adding the about, reading and video components
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;
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;
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;
We need to add a clickable module to the landing page and also include links to the new lab in the sitemap
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>
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>
The exercise is the central component of the lab. It uses the following format:
- User experiences the software as a normal user
- User experiences the software through emulation lens
- Exercise discusses the problems and solutions to address the inaccessible software
- User repairs the software using code editor
- 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
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);
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:
- Codeblock.js - The dynamic black background used to hold all of the text components used in the code block
- CodeLine.js - Functions as a flex row wrapper that will line up all of its children in a single line of code
- 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.
- 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.
- 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.
- 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.
- 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.
- 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) = {</ReactText>
<CodeLine>
<Tab /> <ReactText> const handleNext = () => </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>}</ReactText>
<ReactText>export default ReactText;</ReactText>
<JSONText> variable: jsonvariable </JSONText>
<HTMLTag> <div className = “coolCSS”> </HTMLTag> // This will appear as <div className = "coolCSS">
<CodeLine>
<Tab />
<HTMLText> hello this is html text </HTMLText>
</CodeLine>
<HTMLTag></div></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.
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#];
Another change to make is in the App.js file found in src > components > quiz
- Import the quiz questions at the top of the file like so:
import quizQuestionsLab# from './api/Lab#/quizQuestions';
- 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
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.
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 })
};
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 })
};
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#,
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.
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.
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)
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.
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:
- Notice, we import the API file above, this allows us to leverage the existing POST and GET calls.
- We define the comma-separated endpoints using a constant.
- 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- Notice the parameters are the react server url (our backend) + the specific endpoint
- We pass into the body our data we want to store. In this case we are storing data related to in-exercise question/answers.
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
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.
- About Component: Add
<AboutLab# path="/Lab#/"/>
and<AboutLab5 path="/Lab5/About"/>
- Reading Component: Add
<ReadingLab5 path="/Lab5/Reading"/>
- Exercise Component: Add
<ExerciseLab5 path="/Lab5/Exercise/*" />
- Video Component: Add
<VideoLab5 path="/Lab5/Video" />
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
- Creating folders in the following sections:
- controllers: create a 'lab#' folder. This will hold the controller files - ExerciseController and RepairController
- services: create a 'lab#' folder. This will hold the services files which correspond to the controller files - ExerciseService and RepairService
- database > models: create a 'lab#' folder. This will hold the model files specific to the lab - Exercise and Repair
- src > components > exercise > lab#: create a 'components' folder. This will hold all shared exercise components for that lab
- 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.
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
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
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
}
};
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
}
}
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
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);
})
}
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
}
;
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.
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;
};
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;
};
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.