W04 Learning Activity: Repeating the Process: Inserting New Service Projects
Overview
In previous activities you learned how to handle form submissions to insert new organizations into your application. In this activity, you will repeat that process to add the ability to insert new service projects.
In your team activity you will handle updating service projects.
Activity Instructions
One main difference between inserting new projects as compared to the organizations that you have done previously, is that projects are associated with organizations, so you need to handle that relationship when inserting new projects. To handle this, you will create a dropdown menu in the new project form that allows users to select the associated organization. This requires the controller for the form to get the list of organizations and pass it to the new project view.
There are many new files and functions to create that go end-to-end from the client to the database. As mentioned in a previous activity, you can approach these new function from a top-down or a bottom-up approach.
The previous activity followed a top-down approach starting with the views. This activity will follow a bottom-up approach, where you first create the model functions, then the controller and route functions, and finally create and update the views.
Create the model function to insert new service projects.
- In your
src/models/projects.jsfile, create a new function namedcreateProject. This function should accept the following parameters:title,description,location,date, andorganizationId. - The function should execute an SQL INSERT statement to add a new record to the
projectstable in the database using the provided parameters. - The function should return the result the ID of the newly inserted project.
- Add the new function to the module exports.
Sample Code (click to expand)
const createProject = async (title, description, location, date, organizationId) => {
const query = `
INSERT INTO project (title, description, location, date, organization_id)
VALUES ($1, $2, $3, $4, $5)
RETURNING project_id;
`;
const query_params = [title, description, location, date, organizationId];
const result = await db.query(query, query_params);
if (result.rows.length === 0) {
throw new Error('Failed to create project');
}
if (process.env.ENABLE_SQL_LOGGING === 'true') {
console.log('Created new project with ID:', result.rows[0].project_id);
}
return result.rows[0].project_id;
}
Create the controller functions to serve the new project form and to process the form submission.
- Open your
src/controllers/projects.jsfile. - Add an import for the
createProjectmodel function you just created. - Also, add an import for the
getAllOrganizationsfunction from the../models/organizations.jsfile. This is needed to populate the dropdown list of organizations on the new project form. - In your
src/controllers/projects.jsfile, create a new function namedshowNewProjectForm. This function should do the following:- Call the
getAllOrganizationsmodel function to get a list of all organizations from the database. - Render the
new-projectview, passing in the page title and the list of organizations to populate the dropdown menu.
- Call the
- Create another function named
processNewProjectForm. This function should do the following:- Extract the project data (organizationId, title, description, location, date) from the form submission using
req.body. - Call the
createProjectmodel function you created in the previous step, passing all of the necessary parameters. - After the insertion is complete, set a success flash message.
- Redirect the user back to the main service project list page.
- Extract the project data (organizationId, title, description, location, date) from the form submission using
- Add both new functions to the module exports.
Sample Code (click to expand)
const showNewProjectForm = async (req, res) => {
const organizations = await getAllOrganizations();
const title = 'Add New Service Project';
res.render('new-project', { title, organizations });
}
const processNewProjectForm = async (req, res) => {
// Extract form data from req.body
const { title, description, location, date, organizationId } = req.body;
try {
// Create the new project in the database
const newProjectId = await createProject(title, description, location, date, organizationId);
req.flash('success', 'New service project created successfully!');
res.redirect(`/project/${newProjectId}`);
} catch (error) {
console.error('Error creating new project:', error);
req.flash('error', 'There was an error creating the service project.');
res.redirect('/new-project');
}
}
Create the routes to serve the new project form and to handle the form submission.
- Open your
src/controllers/routes.jsfile. - Add imports for the two controller functions you just created.
- Create a GET route for
/new-projectthat calls theshowNewProjectFormcontroller function. - Create a POST route for
/new-projectthat calls theprocessNewProjectFormcontroller function.
Sample Code (click to expand)
// Route for new project page
router.get('/new-project', showNewProjectForm);
// Route to handle new project form submission
router.post('/new-project', processNewProjectForm);
Create the view for the new project form.
- Create a new file named
new-project.ejsin thesrc/views/folder. - Design a form that collects the following information about the new service project:
title,description,location, anddate. - This form also needs a way to select the
organizationIdassociated with the project. For this, create a dropdown menu populated with organizations.- Use EJS syntax to loop through the
organizationsarray passed to the view and create an<option>element for each organization. - The value of each option should be the organization's ID, and the display text should be the organization's name.
- Use EJS syntax to loop through the
- The form should submit a POST request to the
/new-projectroute.
Remember that AI can be very helpful in creating these kinds of forms. Just be sure to double check that all of your form field names match the expected names in your controller and database.
Sample Code (click to expand)
<%- include('partials/header') %>
<main>
<h1><%= title %></h1>
<form action="/new-project" method="POST">
<div class="form-group">
<label for="title">Project Title</label>
<input
type="text"
id="title"
name="title"
maxlength="150"
required
/>
</div>
<div class="form-group">
<label for="description">Description</label>
<textarea
id="description"
name="description"
maxlength="500"
required
></textarea>
</div>
<div class="form-group">
<label for="location">Location</label>
<input
type="text"
id="location"
name="location"
maxlength="255"
required
/>
</div>
<div class="form-group">
<label for="date">Date</label>
<input
type="date"
id="date"
name="date"
required
/>
</div>
<div class="form-group">
<label for="organizationId">Organization</label>
<select
id="organizationId"
name="organizationId"
required
>
<option value="">Select an organization</option>
<% organizations.forEach(org => { %>
<option value="<%= org.organization_id %>"><%= org.name %></option>
<% }); %>
</select>
</div>
<button type="submit">Create Project</button>
</main>
<%- include('partials/footer') %>
Update the main service project list view to include a link to the new project form.
- Open your main service project list view file
src/views/projects.ejs. - Add a link that navigates to the
/new-projectroute, allowing users to access the new project form.
Sample Code (click to expand)
<p><a href="/new-project">Add a New Service Project</a></p>
Test the new functionality
- Start your application and navigate to the main service project list page.
- Click the link to add a new service project. Verify that the new project form loads and that the organization dropdown is populated with organizations from the database.
- Fill out the form with valid data and submit it. Verify that the new project is added to the database and that you are redirected back to the main service project list page with a success flash message.
- Verify that the new project is displayed in the project list on the main service project list page.
Add Validation
- Ensure that you have proper client-side validation for all form fields to improve user experience and reduce invalid submissions:
- Make all fields required.
- Set appropriate maximum lengths for text fields.
- Use the correct input types (e.g., date picker for date fields).
- Test your client-side validation.
- Implement server-side validation in the
processNewProjectFormcontroller function to ensure data integrity:- Open your
src/controllers/projects.jsprojects controller file. - Import the
bodyandvalidationResultfunctions from theexpress-validatorpackage. - At the top of the controllers file, create a
projectValidationarray to define the validation rules for projects as follows: (remember that AI can be very helpful in generating validation code, but be sure to review and test it thoroughly)- title: trim, ensure not empty, length between 3 and 200.
- description: trim, ensure not empty, length less than 1000.
- location: trim, ensure not empty, length less than 200.
- date: ensure not empty, valid date format.
- organizationId: ensure not empty, valid integer.
- In the
processNewProjectFormfunction, check for validation errors usingvalidationResult(req). If there are errors, set a flash message and redirect the user to thenew-projectview. - Export the
projectValidationarray from the projects controller file so it can be used in your routes. - Open your
src/controllers/routes.jsroute handler file. - Import the
projectValidationarray from the projects controller file. - Update the POST route for
/new-projectto include theprojectValidationarray as middleware before theprocessNewProjectFormcontroller function.
Sample Code (click to expand)
const projectValidation = [ body('title') .trim() .notEmpty().withMessage('Title is required') .isLength({ min: 3, max: 200 }).withMessage('Title must be between 3 and 200 characters'), body('description') .trim() .notEmpty().withMessage('Description is required') .isLength({ max: 1000 }).withMessage('Description must be less than 1000 characters'), body('location') .trim() .notEmpty().withMessage('Location is required') .isLength({ max: 200 }).withMessage('Location must be less than 200 characters'), body('date') .notEmpty().withMessage('Date is required') .isISO8601().withMessage('Date must be a valid date format'), body('organizationId') .notEmpty().withMessage('Organization is required') .isInt().withMessage('Organization must be a valid integer') ];Sample Code (click to expand)
// Check for validation errors const errors = validationResult(req); if (!errors.isEmpty()) { // Loop through validation errors and flash them errors.array().forEach((error) => { req.flash('error', error.msg); }); // Redirect back to the new project form return res.redirect('/new-project'); } - Open your
- Test your server-side validation by submitting the new project form with invalid data and verifying that the appropriate error messages are displayed. Because your client-side validation will catch most of the errors, try submitting a project with a less than 3 letters, this should be caught only by the server-side validation logic.
Next Step
Complete the other Week 04 Learning Activities
After you have completed all the learning activities for this lesson, return to Canvas to submit a quiz.
Other Links:
- Return to: Week Overview | Course Home