UWD
Section 1: Introduction
Section 2: JS Values & Variables
- PROPERTIES = values we can access
"hello".length
– length
is property of string "hello"
-
Strings are immutable in JS
-
METHODS = actions that can be performed on/with (format:
string.method()
)
Section 3: How to Model Data Efficiently
Section 4: Controlling Program Logic and Flow
Section 5: Capture Collections of Data with Arrays
Section 6: Objects - The Core of Javascript
Section 7: The World of Loops
Section 8: Writing Reusable Code with Functions
Section 9: An Advanced Look at Functions
Section 10: Apply Functions to Collections of Data
Section 11: A Few Miscellaneous JS Features
Section 12: Object Methods and the ‘This’ Keyword
Section 13: JS In the Browser - DOM Manipulation
Section 14: Twisting the DOM to Our Will!
Section 15: Communicating with Events
Section 16: Asynchronous Code, Callbacks & Promises
Section 17: Making HTTP Requests
Section 18: Async & Await: JS Magic
Section 19: Prototypes, Classes, & The New Operator
Section 20: Drawing Animations
195. Welcome to Part 2!
196. App Overview
197. Project Setup
- make new dir
- Make
index.html
andindex.js
boilerplate
TINIEST POSSIBLE “APP”
index.html
<!DOCTYPE html>
<html>
<head></head>
<body>
<script src="index.js"></script>
</body>
</html>
index.js
console.log("SHHH!! I LOVE CATS!");
198. Event-Based Architecture
What does our program do?
- displays a timer
- Shows an animated border around the timer
TL;DR – using callbacks to say “timer started!”, “timer ticked” etc
BONUS: this can be REUSED when waiting for web requests
199. Class-Based Implementation
Class TIMER!! With Constructor: constructor(durationInput, startButton, pauseButton) With methods: start() pause() setDuration() tick()
200. Binding Events in a Class
Tiniest class!!
class Timer {
constructor(message) {
console.log(message);
}
}
myTimer = new Timer("i love cats!!");
- take three inputs
- instantiate them in constructor
- add event listener inside constructor
- make a
start()
method
201. Reminder on ‘This’
202. Determining the Value of ‘This’
bind
, call
, and apply
are BUILD IN FUNCTIONS that belong to ALL functions inside of JavaScript
The first argument to all three is going to OVERRIDE the mythical this
that we want (NOT the imposter this
we get from console logging this within the start() function within the Timer class)
203. Solving the ‘This’ Issue
Option 1: Arrow function!
start = () => {
console.log("TIMER HAS BEEN STARTED!!");
};
Option 2:
class Timer {
constructor(durationInput, startButton, pauseButton) {
this.durationInput = durationInput;
this.startButton = startButton;
this.pauseButton = pauseButton;
this.startButton.addEventListener("click", this.start.bind(this));
this.pauseButton.addEventListener("click", this.pause.bind(this));
}
.
.
.
start(){}
pause(){}
}
NOTE: we will be using Option 1
204. Starting and Pausing the Timer
Starting the timer!!
- add
tick()
method to timer class - use
setInterval
function, that’s part of the js language - PROBLEM: it doesn’t start ticking right away!
- SOLUTION: call tick() before setInterval, like so:
start = () => {
this.tick();
setInterval(this.tick, 1000);
};
tick = () => {
console.log("tick!");
};
NOTE: don’t be a dooface and do setInterval(this.tick(),1000)
and try calling the function like that
Pausing the timer!!
TL;DR:
clearInterval(timerID)
BUT WAIT!
PROBLEM:
How will we get that timerID
from the other Timer class function, start
!
SOLUTION:
Assign timer to the this
variable so we can access it EVERYWHERE, like so
205. Where to Store Data?
OPTION 1: Inside our JS code
OPTION 2: Inside the DOM
206. DOM-Centric Approach
tick = () => {
const timeRemaining = parseFloat(this.durationInput.value);
this.durationInput.value = timeRemaining - 1;
};
207. Getters and Setters
Let’s reduce code reuse!
getTime()
& setTime()
FIRST WAY:
getTime(){
return parseFloat(this.durationInput.value)
}
setTime(time){
this.durationInput.value = time
}
SECOND WAY (with getters and setters!):
tick = () => {
this.timeRemaining = this.timeRemaining - 1
};
get timeRemaining() {
return parseFloat(this.durationInput.value);
}
set timeRemaining(time) {
this.durationInput.value = time
}
208. Stopping the Timer
209. Notifying the Outside World
- pass callbacks to our
new Timer(.... pauseButton, { callbacks here!!})
- (thinking about the actual rendering of the border) I only care about:
- onStart
- onTick
- onComplete
210. OnTick and OnComplete
211. Extracting Timer Code
PROBLEM:
so much code in this one file!!
SOLUTION:
pull it out into its own js file!!
212. Introducing SVG’s
- SVGs are HTML elements!
- Add a simple circle to index.html like so:
<body>
<svg width="200", height="200">
<circle r="20", cx="30", cy="30">
</svg>
</body>
213. Rules of SVG’s
- svgs need width and height
- they draw a canvas
- “origin” is starting point and that is upper left point of box
- (meaning we will rarely use negative numbers – at least not in our current app)
- you can add other elements to svg elements (like we did with circle)
- more than just circles!! also square, also polygons, also paths that have an arbitrary direction
- We can also have MANY smaller shapes inside the SVG
More about our circle:
r = radius
cx
and cy
stand for “centerpoint x and centerpoint y”
meaning where the center of the circle will be placed inside the SVG canvas we created enCIRCLING (lololol) our shape
214. Advanced Circle Properties
This makes a red circle with a blue border!
<!DOCTYPE html>
<html>
<head></head>
<body>
<svg width="200", height="200">
<circle
r="90",
cx="100",
cy="100",
fill="red",
stroke="blue",
stroke-width="10">
</svg>
<input id="duration" value="30" />
<button id="start">Start</button>
<button id="pause">Pause</button>
<script src="timer.js"></script>
<script src="index.js"></script>
</body>
</html>
all we have to do to get a see-through circle is change
fill="transparent"
THINGS WE NEED:
stroke-dasharray
This takes two arguments but only really needs one (the first one)
stroke-dasharray="10"
means “give me a dash of 10 pixels every 10 pixels”stroke-dasharray="10 5"
means “give me a dash of 10 pixels every 5 pixels”
CIRCLE REVIEW:
Perimeter = 2PIr
THINGS WE NEED CONT:
stroke-dashoffset="x"
- (this subtracks from what we already have as a gap – makes the gap we have larger by x)
dashoffset
set to negative goes CLOCKWISE!!transform=rorate(-90,100,100)
215. The Secret to the Animation
216. First Pass on the Animation
217. Smoothing the Animation
PROBLEM:
Our animation is blocky!!
SOLUTION:
- Update how frequently we are “ticking” by updating
setInterval
to run every 50ms instead of every 1 sec - Update how we are calculating time remaining (it should be
-0.05
now instead of every 1) - Update the
-50
to simply-1
for the dashoffset pixels
PROBLEM:
Now our numbers have all these 9s in the text input!!
SOLUTION:
toFixed(2)
218. Adjusting by an Even Interval
offset = (perimeter * timeRemaining) / totalDuration - perimeter
- timer.js – pass this.timeRemaining to onTick
- pass this.timeRemaining to onStart because GASP this is the DURATION at on start!! 3.
219. Using Icons
- cdnjs –> font awesome
- link rel=”stylesheet, href=”what we just googled” 3.
220. Styling and Wrapup
==============================================================
Section 21: Application Design Patterns
221. Application Overview
222. Starter Kit Setup
PLAN OF ATTACK
- Set up “boiler plate”
- Call out challenging parts of the app
- Start project and actually write the code!
223. Big Challenges
BIG CHALLENGES (& possible solutions)
- Where do we get movies?!
- Build an autocomplete widget from scratch
- Styling!
APIS
- INDEX results (a list of records)
- SHOW results (full set of attributes about that record)
224. Fetching Movie Data
GOAL: Get a few movies from OMDb
- Get API key
- modify index.js to include a fetchData() method
const response = await axios.get("url", {
params: {
apikey: 'mykey'
s: 'avengers'}})
225. Fetching a Single Movie
226. AutoComplete Widget Design
THINKING IT THROUGH!!
Behavior diagram
A. List all the ways a user can interact with this widget
- Default state
- User starts typing…
- User finishes typing…
- – we find nothing (display error)
- – we do find something!
- User clicks an entry
227. Searching the API on Input Change
228. Delaying Search Input
DEBOUNCE!!
setTimeout(() => {
console.log("henlo der");
}, 1000);
clearTimeout(ID_OF_TIMEOUT);
REFACTOR
const onInput
Pass onInput into EventListener
NOW we can add logic to call onInput
- Move fetchData out
- Wrap that in setTimeout
- Profit?
DIY DEBOUNCE!!!!
let timeoutId;
const sendInput = (event) => {
if (timeoutId) {
clearTimeout(timeoutId);
}
timeoutId = setTimeout(() => {
fetchData(event.target.value);
}, 500);
};
input.addEventListener("input", sendInput);
229. Understanding Debounce
PROBLEM:
We will frequently need to debounce
things
SOLUTION:
Make a reusable function!
const debounce = (func) => {
let timeoutId;
return () => {
if (timeoutId) {
clearTimeout(timeoutId);
}
timeoutId = setTimeout(() => {
func();
}, 500);
};
};
PROBLEM:
What happens when we need to pass something to func
like event.target.value
?
SOLUTION:
func.apply(null, args)
!!
const debounce = (func) => {
let timeoutId;
return (...args) => {
if (timeoutId) {
clearTimeout(timeoutId);
}
timeoutId = setTimeout(() => {
func.apply(null, args);
}, 500);
};
};
230. Implementing a Reusable Debounce
231. Extracting Utility Functions
make utils.js require it before index.js on html
232. Awaiting Async Functions
response.data.Search
233. Rendering Movies
don’t forget to add double quotes to img src
document query selector #target appendChild
234. Handling Errored Responses
235. Opening a Menu
bulma.io
div dropdown is-active dropdown-menu dropdown-content dropdown-item
236. Style of Widget Creation
237. Moving HTML Generation
TL; DR: We want reusable widgets (components) This is hard when we de-couple html and javascript. Solution? Coupling the html and javascript
PROBLEM: We don’t want to write the same code over and over again!
SOLUTION: move the html creation into the javascript file so we have reusable “widtets” (components)
Is HEADER going to be reused within the app? No? Well, then it can live in the HTML file. Is INPUT going to be reused within the app? Yes? Well then, it should be put into its own little “widget” (component)
- JAVASCRIPT HEAVY Reusable widgets!!
WHAT DO WE WANT:
- Reusable widgets!
WHEN DO WE WANT THEM!?
- NOW!
BEFORE
<div class="container">
<div class="dropdown is-active">
<input
id="movieSearch"
name="searchTerm"
placeholder="Search for a movie!"
/>
<div class="dropdown-menu">
<div class="dropdown-content">
<a class="dropdown-item">Movie 1</a>
<a class="dropdown-item">2</a>
<a class="dropdown-item">3</a>
<a class="dropdown-item">4</a>
</div>
</div>
</div>
<div id="target"></div>
</div>
AFTER
index.html
<div class="container">
<div class="autocomplete"></div>
</div>
index.js
const root = document.querySelector(".autocomplete");
root.innerHTML = `
<div class="dropdown is-active">
<input
id="movieSearch"
name="searchTerm"
placeholder="Search for a movie!"
/>
<div class="dropdown-menu">
<div class="dropdown-content">
<a class="dropdown-item">Movie 1</a>
<a class="dropdown-item">2</a>
<a class="dropdown-item">3</a>
<a class="dropdown-item">55555</a>
</div>
</div>
</div>
<div id="target"></div>
`;
add selectors for dropdown and resultsWrapper for results
238. Quick Note
239. Repairing References
Add is-active anchor
240. Handlincg Broken Images
- Return empty string if image is NA
- clear resultsWrapper when we search for movie
241. Automatically Closing the Dropdown
- add event listener for clicks
dropdown.remove('is-active')
242. Handling Empty Responses
243. Handling Movie Selection
update text and close dropdown
option.addEventListener(‘click, event => {
input.value = movie.Title; })
NOTES: We want to:
- add an event listener to our options
- once clicked, we want to
- close dropdown
- replace input text
244. Making a Followup Request
- Need to do a request when user clicks on our “option”
- PROBLEM: that file is so big
- SOLUTION: helper function – onMovieSelect(movie-user-just-clicked-on)
- make function
onMovieSelect
- make that solution async
- copy params from first api resuest movie.imdbID
245. Rendering an Expanded Summary
- new helper function! with so much html!!
- add an element with id of
summary
to index.html - get that newly created element and add all the html we just made from our helper function!
246. Expanded Statistics
247. Issues with the Codebase
PROBLEM: Current code wouldn’t work if we wanted to reuse this for, say, recipes!! (recipes won’t have a POSTER)
248. Making the Autocomplete Reusable
249. Displaying Multiple Autocompletes
GOAL: display multiple autocompletes
- create autocomplete.js
- add autocomplete.js to index.html
250. Extracting Rendering Logic
- remove the test code from 249 in index.html
- ^ from index.js
- add
renderOptions
to ourcreateAutoComplete
function - pass that to our autocomplete.js
- test this by adding a date to our movie dropdown!
251. Extracting Selection Logic
- Add onOptionSelect() to config
- Add inputValue to config
252. Removing Movie References
Pass Fetch Data to Config function change all ‘movies’ to ‘items’ and ‘movie’ to ‘item’
253. Consuming a Different Source of Data
254. Refreshed HTML Structure
255. Avoiding Duplication of Config
256. Hiding the Tutorial
257. Showing Two Summaries
add “side” to onMovieSelect GOAL: “Run comparison” in console log
258. When to Compare?
259. How to Compare?
Update MovieDetail function
- Turn things into an easy-to-compare value!
- dollars = movieDetails.BoxOffice. replace
- parseInt
- parseFloat
260. Extracting Statistic Values
261. Parsing Number of Awards
if (isNaN(value)){}
262. Applying Parsed Properties
add data-value properties
263. Updating Styles
264. Small Bug Fix
265. App Wrapup
MOST IMPORTANT:
- Create REUSABLE Autocomplete widget!!
- Connect our html and js into ONE PLACE!
- Run comparison using data-value
Section 22: Javascript with the Canvas API
Section 23: Make a Secret-Message Sharing App
Section 24: Create Node JS Command Line Tools
THINGS WE USED
lstat
utils
chalk
-
path
- File sharing happens with module.export makes files available
- Require lets us access what we see in module.export
NOTE: arguments
is a great key word to inspect what we are looking at
Invisible Node Functions
- export
- require
- __filename
- __dirname
Debugging with node
chrome://inspect
node --inspect-brk index.js
HIGH POINTS: THINGS WE LEARNED
- Commands in package json (
bin
) - Package.json tracks dependencies
Section 25: Create Your Own Project Runner
THINGS WE USED:
- lodash.debounce
Creating a nodemon
clone!
same start as above
BIG APP ISSUES:
- Need to detect when a file changes
- Want to add help for other devs
- Need to run JS code from within a JS program
–
So we will use other libraries!
- Need to detect when a file changes –>
chokidar
- Want to add help for other devs –>
caporal
-
Need to run JS code from within a JS program –>
child_process
- DEBOUNCING (we add in a waiting timer)
345. Ensuring Files Exist
- Move functionality into caporal
- Sub console logs with
start
function - destructure to find {filename}
- Make that function async
- Require fs.promises to find the file name
- wrap that in a try catch
346. It works!
- GOAL: make test file and change test file to see it working
- use child_process {spawn}
348. More on Child_Process
spawn
does mostly everything we will needfork
will be used for webservers!
349. App Wrapup
- kill old programs
- if(proc_ proc.kill())
- we can use chalk again
Section 26: Project Start - E-Commerce App
351. App Architecture
PROJECT SETUP:
- Create a new project directory
- Generate package.json
npm init -y
- Add node modules
- Create a start script to run our project!
352. Package.json scripts
npm run dev
is goal- add
"dev": "nodemon index.js"
to scripts in package.json
353. Creating a Web Server
What is express?
// app.get("/", CALLBACK FUNCTION;
app.get("/", (req, res) => {});
// app.listen(PORT, CALLBACK)
app.listen(3000, () => {
console.log("Listening!");
});
REQ = REQUEST, an incoming request from the browser RES = RESPONSE, the outgoing response
REQ – If we need info from the user RES – If we ever need to send information back to the browser
356. Understanding Form Submissions
GOAL: form submission with response
- Create res.get
- Create.res.post
- profit
357. Parsing Form Data
GOAL: Save email, password and password confirmation on our server
NOTE: req.on
is very similar to addEventListener
- So we’re saying we want to run some callback function when some event occurs.
req.on('data', data => {})
is waiting for the DATA EVENT.- The request object emits a data event any time it receives some bit of data
- That data is passed in as the first argument to the callback function
const express = require("express");
const app = express();
app.get("/", (req, res) => {
res.send(`<div>
<form method='POST'>
<input name="email" type="email" />
<input name="password" type="password" />
<input name="passwordConfirmation" type="password" />
<button>Submit</button>
</form>
</div>`);
});
app.post("/", (req, res) => {
req.on("data", (data) => {
console.log(data);
});
res.send("ACCOUNT CREATED!!");
});
app.listen(3000, () => {
console.log("listening!");
});
PROBLEM: When we first get the data back in our terminal after this console log, it is in a very RAW formmat (it comes back in an array of raw information)
SOLUTION:
app.post("/", (req, res) => {
req.on("data", (data) => {
console.log(data.toString("utf8"));
});
res.send("ACCOUNT CREATED!!");
});
358. Middlewares in Express
WHAT IS MIDDLEWARE!?
Function that preprocesses the req
and res
objects!! Helping to reduce code repeats!!
app.post("/", bodyParser, middleWare2, mw3, (req, res) => {}
- BODYPARSER is middleware!!
- Made before promises were popular, it gives us NEXT to access the callback function
// bodyParser = (req, res, CALLBACK)
const bodyParser = ((req, res, next) = {});
— BEFORE
const express = require("express");
const app = express();
app.get("/", (req, res) => {
res.send(`<div>
<form method='POST'>
<input name="email" type="email" />
<input name="password" type="password" />
<input name="passwordConfirmation" type="password" />
<button>Submit</button>
</form>
</div>`);
});
const bodyParser = (req, res, next) => {
if (req.method == "POST") {
req.on("data", (data) => {
const parsed = data.toString("utf8").split("&");
const formData = {};
for (let pair of parsed) {
const [key, value] = pair.split("=");
formData[key] = value;
}
req.body = formData;
next();
});
} else {
next();
}
};
app.post("/", bodyParser, (req, res) => {
console.log(req.body);
res.send("ACCOUNT CREATED!!");
});
app.listen(3000, () => {
console.log("listening!");
});
359. Globally Applying Middleware
BEFORE:
bodyParser.urlencoded({ extended: true})
// GOOD: our DIY parser
app.post("/", bodyParser, (req, res) => {
console.log(req.body);
res.send("ACCOUNT CREATED!!");
});
// BETTER: Using the parser from the library!
app.post("/", bodyParser.urlencoded({ extended: true }), (req, res) => {
console.log(req.body);
res.send("ACCOUNT CREATED!!");
});
// BEST: Adding bodyParser to the WHOLE APP
app.use(bodyParser.urlencoded({ extended: true }));
Section 27: Design a Custom Database
360. Data Storage
We need to STORE information somewhere. We need it to be PERSISTENT
361. Different Data Modeling Approaches
362. Implementing the Users Repository
METHODS USED:
- fs.asscessSync
- fs.writeFileSync
NOTE: We are NOT allowed to have async code inside a constructor
(This is one of the only times using fs.__Sync functions is OK)
GOAL: Create & Implement a Users repository
- make folder
- make users.js file
- make class
- test class
- put constructor
- add try catch with fs.acscess and fswriteFile
363. Opening the Repo Data File
- Open the file
- Read the contents
- Parse the Contents
- Return the parsed data
364. Small Refactor
// BEFORE
async getAll() {
const contents = await fs.promises.readFile(this.filename, {
encoding: "utf8",
});
const data = await JSON.parse(contents);
return data;
}
// AFTER
async getAll() {
return JSON.parse(
await fs.promises.readFile(this.filename, {
encoding: "utf8",
})
);
}
365. Saving Records
- make create function
- use writefile
- with attrs for attributes
- test it by adding file before
const fs = require("fs");
class UsersRepository {
constructor(filename) {
if (!filename) {
throw new Error("Creating a repository requires a filename");
}
this.filename = filename;
try {
fs.accessSync(this.filename);
} catch (err) {
fs.writeFileSync(this.filename, "[]");
}
}
async getAll() {
return JSON.parse(
await fs.promises.readFile(this.filename, {
encoding: "utf8",
})
);
}
async create(attrs) {
const records = await this.getAll();
records.push(attrs);
await fs.promises.writeFile(this.filename, JSON.stringify(records));
}
}
const test = async () => {
const repo = new UsersRepository("users.json");
await repo.create({ email: "test@test.com", password: "password" });
const users = await repo.getAll();
console.log(users);
};
test();
NOTE: it is accessSync
not accessFileSync
(this was overwriting my values and giving me major errors)
366. Better JSON Formatting
// JSON.stringify(data, FUNCTION, SPACES)
JSON.stringify(records, null, 2);
367. Random ID Generation
PROBLEM: We don’t have unique IDs!
SOLUTION: create a RandomID method
368. Finding By Id
use find get a user id from user.json
369. Deleting Records
use filter
370. Updating Records
371. Adding Filtering Logic
372. Exporting an Instance
// potentially buggy
module.exports = UsersRepository;
// less buggy for our purposes
module.exports = new UsersRepository("users.json");
CLEANING UP AND REMOVING TESTS!
// const test = async () => {
// const repo = new UsersRepository("users.json");
// // TESTING CREATE + GET ALL
// // await repo.create({ email: "catmom@gmail", password: "ilovecats" });
// // const users = await repo.getAll();
// // console.log(users);
// // TESTING GETONE
// // const user = await repo.getOne("fbdeba1c");
// // console.log(user);
// // TESTING DELETE
// // await repo.delete("2675571a");
// // TESTING UPDATE
// // await repo.create({ email: "turtlemom@gmail" });
// // await repo.update("e72f6a67", { password: "iloveturtles" });
// // TESTING GETONEBY
// const user = await repo.getOneBy({ email: "turtlemom@gmail" });
// console.log(user);
// };
// test();
373. Signup Validation Logic
- Add usersRepo and require
- destructure the req to get all the things
- await getOneBy
await usersRepo.getOneBy({email})
- add async
Section 28: Production-Grade Authentication
- Hit log in and submitting the form just like above
- The BROWSER is going to collect information from the FORM
- It is going to SEND that information to the SERVER via a POST REQUEST
NOW, we can look at the session
in NETWORK and look at the RESPONSE HEADERS
374. Cookie Based Authentication
375. Creating User Records
- create user in user repo
await usersRepo.create({ email, password})
- RETURN addrs from create
- store id of that user and store it inside the users cookie
- Managing cookies is hard and so we are going to use another library
- npm install cookie-session
- before
await userRepo.create({email, password}
- )
376. Fetching a Session
- require cookiesession (its middleweare so we need to….)
app.use(cookieSession({ keys:['sdlfkjsdflskjdf']}))
req.session
is an object from cookie-session- req.sesion.userId = user.id;
BUG WITH NODEMON REFRESHING USERS.JSON
Fixed by adding below to package.json
:
"nodemonConfig": {
"ignore": [
"users.json"
]
}
377. Signing Out a User
- add /signin
- add /signout
- copy form from signin
378. Signing In
- get email and password from req.body
- take email check to see if email already exists via getOneBy() 3.
379. Hashing Passwords
PROBLEM: passwords aren’t secured. Doesn’t matter that this is a small app because people reuse passwords!
SOLUTION: Hashing algorithm
380. Salting Passwords
PROBLEM: Rainbow table attacks!!
SOLUTION: Salting!
381. Salting + Hashing Passwords
- crypto.randomBytes
- crypto.scrypt
- utils promisify
salt = crypto.randomBytes(8).toString(‘hex) scrypt(attrs.password, salt, 64, (err, buff) => { const hashed = buff.toString(‘hex’) })
- generate salt
- generate hashed password
- save new hased password as record
- return the record
382. Comparing Hashed Passwords
- async comparePasswords(saved, supplied)
- create new method
- destructure
- return
- profit
383. Testing the Full Flow
- update return of scrypt to reflect that it returns a buffer
Section 29: Structuring Javascript Projects
384. Project Structure
385. Structure Refactor
- make new file for auth routes in routes/admin/auth.js
- migrate all auth routes to new file
- confirm we are requiring things we need (usersRepo)
- hook new file into express with
router
- replace all occourance of
app
withrouter
- export router
- back in index.js, below the middlewear, get the export we just made
THINGS I FORGOT
module.exports = router;
386. HTML Templating Functions
- views > admin > auth > sign in & sign up
- modeule. exports = ({req} => { return
our form
}) - import singupTemplate
- res.send(signupTemplate({req}))
387. HTML Reuse with Layouts
PROBLEM: So much of this is not DRY
SOLUTION: Layout file!
388. Building a Layout File
GOAL: reuse of html elements
admin > layout.js
389. Adding Better Form Validation
express-validator pass an array to second argument
390. Validation vs Sanitization
destructure the one function we care about const {check} = require(‘express-validator’)
validator.js is being used by express-validator
391. Receiving Validation Output
392. Adding Custom Validators
express-validator checking password confirmation checking if email is in use
393. Extracting Validation Chains
394. Displaying Error Messages
const getError in validators
395. Validation Around Sign In
inSIGNIN checkemail checkpassword
396. Password Validation
397. Template Helper Functions
398. Adding Some Styling
399. Exposing Public Directories
public folder! public » css
Make folder available by this!!
app.use(express.static('public'))
400. Next Steps
401. Product Routes
/admin/products
/admin/products/new
router.get(‘/’
require productsRouter
- Build new file
products.js
- like
admin.js
- Add two routes above
- require it back inside our main express file
402. The Products Repository
OBJECT ORIENTED APPROACH
Users AND products EXTEND the Product CLASS!!
Repo.js
ALL methods to be shared
class Repository Pastefrom constructor to bottom
Remove create
and comparePasswords
add fs and crypto
Require in rhe repository and extends Repository means we are go
EXTENDS
Means “take a look at that class repository and all the different motheds that have been assigned to it and we can copy and paste them into the body of our class
EXTENDS essentially means that we are copying and pasting all the methods from (whatever we are extending) to (whereever we are usign the extend)
add a generic async CREATE method
- Create new file repository.js
- Remove all except
create
andchange password
from user repo - Add new create method to repository,js
403. Code Reuse with Classes
404. Creating the Products Repository
productsrepo extends repo
- create new products.js
- have it extend repo
- go into routes
- require the products repo inside the products route
405. Building the Product Creation Form
views » admin » products » new.js
const layout = require layout
const get error = require helpers
module.exports = ({ errors }) => { return layout({ content: ``}) }
productsNewTemplate
res.send inside /new productsNewTemplate({})
- Create new
new.js
page - require layout and get error
- return content in layout
- inside prouct routes, grab that new template
406. Some Quick Validation
Require title .trim() .isLength({ min: 5, max: 40 })
Require price .trim() .toFloat() .isFloat()
from products, require validators
Now make a post request handlersecond argument is array with validators and then req and res and res .
validationResult from express-validator
Section 30: Image and File Upload
407. Exploring Image Upload
add withMessage()
408. Understanding Mutli-Part Forms
IMPORTANT
How do you send an image in a form!?
form method='POST' enctype="multipart/form-data"
409. Accessing the Uploaded File
npm install multer
410. [Optional] Different Methods of Image Storage
OMG SERIOUSLY BEST IMAGE VIDEO EVER
411. Saving the Image
- buffer.toString()
const image = req.file.buffer.toString("base64");
- add to product json file using product repo
412. A Subtle Middleware Bug
Middlewares operate in a very specific order! We need to swap them to deal with this bug
// BEFORE
router.post(
"/admin/products/new",
[requireTitle, requirePrice],
upload.single("image"),
async (req, res) => {...
// AFTER
router.post(
"/admin/products/new",
[requireTitle, requirePrice],
upload.single("image"),
async (req, res) => {...
router.post(
"/admin/products/new",
upload.single("image"),
[requireTitle, requirePrice],
async (req, res) => {
413. Better Styling
414. Reusable Error Handling Middleware
Organize our middlewares!!
routes » admin » middlewares.js
module.exports = {
handleErrors(templateFunc) {
return (req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.send(templateFunc({ errors }));
}
next();
};
},
};
we MUST return a function because middlewares MUST be a function!!
const { handle errors } top of products js
handleErrors(productsNewTemplate) WITHOUT function parens becuase we’re passing a REFERENCE to the function
PROBLEM: Our error handling isn’t DRY!
SOLUTION: add a DIY handleErrors()
middleware!
- create new middlware file and function in routes/admin
- Require that function in routes/admin auth and products
- update the error-handling requests to use our new handleErrors middleware and delete old code!
415. Products Listing
Build a list using a map statement for every product we are going to generate an HTML snippet, Leading to an array of HTML snippets, which will will join together into a big string
416. Redirect on Success Actions
res.redirect
login signup
417. Requiring Authentication
CODE DUPLICATION MEANS MIDDLEWARE!!!
add requireAuth (req, res, next) to middlewares
// BEFORE
router.get("/admin/products", async (req, res) => {
if (!req.session.userId) {
return res.redirect("/signin");
}
const products = await productsRepo.getAll();
res.send(productsIndexTemplate({ products }));
});
// AFTER
router.get("/admin/products", requireAuth, async (req, res) => {
const products = await productsRepo.getAll();
res.send(productsIndexTemplate({ products }));
});
418. Template Update
419. Ids in URLs
Add id to edit button on products index
420. Receiving URL Params
NOW we need to make our route handler to handle the edit we just created above!
router.get(‘/admin/products/:id/edit)
:thing means WILDCARD!!
Now get product out of productsrepository using GET ONE
mark function async await products Repo.getOne(req.params.id)
421. Displaying an Edit Form
- update edit form to include the “handle errors” function
- add a second param to our handle errors function inside product router
Section 31: Building a Shopping Cart
422. Editing a Product
423. Fixing the HandleErrors Middleware
424. Edit Form Template
425. Deleting Products
Done
426. Starting with Seed Data
Done
427. User-Facing Products
routes > products
require express create new couter
associate route with get to route route
pass in async function
module.exports = router
roots.js
add
428. Products Index
429. Merging More Styling
430. Understanding a Shopping Cart
431. Solving Problem #1
PROBLEM: How do we tie cart to non-logged in user?!
SOLUTION: COOKIES!!
432. Solving Problem #2
PROBLEM: How do we tie a product to a cart??!
THREE DIFFERENT APPROACHES: Bad options first!!
BAD #1 – add a “Carts” array to each product
BAD #2 – Carts repository
GOOD!! – Carts repository that simply points to the product id
433. Shopping Cart Boilerplate
- new routes file » carts.js
- express
- router
- module.exports = router;
- IN INDEX.JS add carts router to app.use(cartsRouter)
- views directory
- new repo » carts.js (same as products to start)
– ROUTES
- Add to cart POST
- Show cart GET
- delete from cart POST
– TODO
- Add repo
- Add router
- require router
- Add comments
434. Submission Options
Now we are going to take the product id and assign it to a cart
- Take product id, assign to cart stored in our carts repository
- we want to find the cart assoiated with this user
- Take a look at the aitems array and add a new item or new object that represents the prodcut
TWO SCENARIOS:
- Firsttime user – no cart for user!
- Must CREATE CART
- then ADD TO CART
- Returning user –
- Must FIND CARD
- then ADD TO CART
435. Creating a Cart, One Way or Another
436. Adding Items to a Cart
437. Displaying Cart Items
router get ‘/cart’
if not req.session.cartId return res.redirect(‘/’) const cart = await cartsRepo.getOne(req.session.cartId)
for let item of cart items
need products repo product = await products repo.getOne
views carts show.js get layout take items modeule.exports
renderedItems = items.map((item ))
438. Rendering the List
439. Totaling Cart Items
NOTE!! In reducer functions, it is very important to return
a value
440. Removing Cart Items
await cartsRepo.getone req.session.cart id
items = cart.items.filter(item =>
await cartsRepo.update(req.session.cartId, { items: items})
res.redirect
441. Redirect on Remove
Section 32: The Basics of Testing
Use assert.strictEqual