diff --git a/404.html b/404.html index 18d9125f8..3d761fe5a 100644 --- a/404.html +++ b/404.html @@ -5,13 +5,13 @@ Page Not Found | Trends in Web Dev - +
Skip to main content

Page Not Found

We could not find what you were looking for.

Please contact the owner of the site that linked you to the original URL and let them know their link is broken.

- + \ No newline at end of file diff --git a/assets/js/e93d9e53.9a6cf45e.js b/assets/js/e93d9e53.8957deaf.js similarity index 99% rename from assets/js/e93d9e53.9a6cf45e.js rename to assets/js/e93d9e53.8957deaf.js index 858a9217f..771687e4c 100644 --- a/assets/js/e93d9e53.9a6cf45e.js +++ b/assets/js/e93d9e53.8957deaf.js @@ -1 +1 @@ -"use strict";(self.webpackChunktrends_in_web_dev_website=self.webpackChunktrends_in_web_dev_website||[]).push([[7181],{3905:function(e,t,n){n.d(t,{Zo:function(){return d},kt:function(){return m}});var a=n(7294);function r(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function o(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function i(e){for(var t=1;t=0||(r[n]=e[n]);return r}(e,t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(r[n]=e[n])}return r}var s=a.createContext({}),u=function(e){var t=a.useContext(s),n=t;return e&&(n="function"==typeof e?e(t):i(i({},t),e)),n},d=function(e){var t=u(e.components);return a.createElement(s.Provider,{value:t},e.children)},c={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},p=a.forwardRef((function(e,t){var n=e.components,r=e.mdxType,o=e.originalType,s=e.parentName,d=l(e,["components","mdxType","originalType","parentName"]),p=u(n),m=r,h=p["".concat(s,".").concat(m)]||p[m]||c[m]||o;return n?a.createElement(h,i(i({ref:t},d),{},{components:n})):a.createElement(h,i({ref:t},d))}));function m(e,t){var n=arguments,r=t&&t.mdxType;if("string"==typeof e||r){var o=n.length,i=new Array(o);i[0]=p;var l={};for(var s in t)hasOwnProperty.call(t,s)&&(l[s]=t[s]);l.originalType=e,l.mdxType="string"==typeof e?e:r,i[1]=l;for(var u=2;u=0||(r[n]=e[n]);return r}(e,t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(r[n]=e[n])}return r}var s=a.createContext({}),u=function(e){var t=a.useContext(s),n=t;return e&&(n="function"==typeof e?e(t):i(i({},t),e)),n},d=function(e){var t=u(e.components);return a.createElement(s.Provider,{value:t},e.children)},c={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},p=a.forwardRef((function(e,t){var n=e.components,r=e.mdxType,o=e.originalType,s=e.parentName,d=l(e,["components","mdxType","originalType","parentName"]),p=u(n),m=r,h=p["".concat(s,".").concat(m)]||p[m]||c[m]||o;return n?a.createElement(h,i(i({ref:t},d),{},{components:n})):a.createElement(h,i({ref:t},d))}));function m(e,t){var n=arguments,r=t&&t.mdxType;if("string"==typeof e||r){var o=n.length,i=new Array(o);i[0]=p;var l={};for(var s in t)hasOwnProperty.call(t,s)&&(l[s]=t[s]);l.originalType=e,l.mdxType="string"==typeof e?e:r,i[1]=l;for(var u=2;u=d)&&Object.keys(n.O).every((function(e){return n.O[e](f[r])}))?f.splice(r--,1):(t=!1,d0&&e[i-1][2]>d;i--)e[i]=e[i-1];e[i]=[f,a,d]},n.n=function(e){var c=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(c,{a:c}),c},f=Object.getPrototypeOf?function(e){return Object.getPrototypeOf(e)}:function(e){return e.__proto__},n.t=function(e,a){if(1&a&&(e=this(e)),8&a)return e;if("object"==typeof e&&e){if(4&a&&e.__esModule)return e;if(16&a&&"function"==typeof e.then)return e}var d=Object.create(null);n.r(d);var b={};c=c||[null,f({}),f([]),f(f)];for(var t=2&a&&e;"object"==typeof t&&!~c.indexOf(t);t=f(t))Object.getOwnPropertyNames(t).forEach((function(c){b[c]=function(){return e[c]}}));return b.default=function(){return e},n.d(d,b),d},n.d=function(e,c){for(var f in c)n.o(c,f)&&!n.o(e,f)&&Object.defineProperty(e,f,{enumerable:!0,get:c[f]})},n.f={},n.e=function(e){return Promise.all(Object.keys(n.f).reduce((function(c,f){return n.f[f](e,c),c}),[]))},n.u=function(e){return"assets/js/"+({192:"b84c401c",358:"8caf5144",365:"ceaa4c24",387:"c50c920e",428:"42faf210",578:"05cc7069",634:"00778435",710:"d8bcf37c",720:"f94d816f",804:"5c65e767",824:"8fb4e078",996:"a8a7ed98",1235:"d75a6d43",1265:"ad820646",1307:"cf8cc687",1312:"3cf3f69d",1406:"eb2864df",1430:"d37109ed",1477:"b2f554cd",1664:"d88cf764",1801:"73891349",1810:"891ae17b",1883:"de5b8704",1890:"b7992291",1977:"5b6eab1f",2012:"42c7ba3f",2013:"c268139f",2020:"2895e284",2047:"7a4c4d02",2064:"e680130f",2140:"25289d1f",2152:"19667349",2176:"0e4b1fab",2212:"1b59971a",2240:"381009a3",2245:"88f6781a",2410:"ae0b2600",2432:"1dee71fb",2484:"97ad368c",2499:"c237f4aa",2529:"c1bc302a",2601:"7db33633",2632:"7bd006db",2691:"a38a8c5b",2826:"67a7304f",2938:"2d3a4042",2987:"b03dc5af",3066:"e1c92344",3112:"7e722b0b",3237:"1df93b7f",3350:"c6c85278",3365:"1157a7ce",3510:"06509df0",3608:"9e4087bc",3624:"427f9d2c",3626:"06d7c6a6",3778:"34c5a6f4",3834:"c2b05852",3839:"e2da632e",3847:"d50786d6",3981:"05e07559",3991:"ce43e1ab",4038:"145263d1",4057:"5eb252c6",4059:"dd1ba448",4090:"5a121968",4095:"21369d76",4135:"9bd82af6",4182:"400a63e5",4204:"bccda9ab",4275:"22af830d",4289:"3bf54008",4315:"7db104a6",4318:"bb5b1104",4362:"fa4938a4",4390:"c69b0f77",4394:"cd5829fe",4444:"ad56e53d",4460:"3763debd",4480:"520e741b",4606:"f72aea3c",4614:"1f0a6afa",4645:"168821de",4735:"0217f38f",4807:"fafed6e2",4858:"25010c81",4905:"e3292145",4934:"f96f683d",5017:"41411fc6",5028:"6490f083",5114:"897da7a1",5154:"632d44fb",5235:"cd83c525",5237:"a5e0de92",5272:"456160f4",5298:"7154c974",5397:"dd823266",5405:"254b729f",5411:"f36f9871",5496:"6a1de0a6",5568:"4f0692d8",5609:"34340b99",5701:"2efd7a92",5777:"c5e87426",5846:"6595de78",5886:"e61e02b7",6173:"4b699842",6270:"a4717cd4",6338:"ffa6c9f2",6536:"15498faf",6579:"614cf394",6582:"44007af7",6623:"597354b5",6704:"4f4cba50",6763:"4b587976",6876:"59676426",7002:"6749d6f8",7047:"e6cfed0b",7062:"58bb1d0f",7084:"9f56bb44",7123:"f586e6d6",7175:"7ab326c8",7181:"e93d9e53",7279:"5669fd4b",7322:"4e1c8eee",7415:"d93177cc",7476:"1170099e",7536:"d53926b7",7576:"765754c9",7708:"63a834f3",7783:"6f70a0e0",7863:"962797aa",7894:"7a5c4182",7918:"17896441",7990:"676a7c04",7997:"3ac44098",8022:"285fefd4",8039:"a916eec8",8159:"8c3bc699",8163:"b234e68d",8206:"ca5dcf79",8380:"9510f52e",8478:"eb57aecd",8553:"da118e27",8659:"97b2708e",8842:"219c64ca",8956:"d7dd9adb",8994:"67fcd22c",9027:"0682386b",9068:"4d09f4bc",9099:"38c5d5d8",9164:"549b9273",9242:"9d61e91f",9275:"1beda944",9333:"98389a97",9342:"9bdd131d",9406:"9e74f5e4",9411:"d8dbc24c",9492:"0ff9b5aa",9511:"06450c56",9514:"1be78505",9731:"af2e0600",9807:"e659d385",9826:"afef7079",9861:"9d708b7f",9862:"a8144ba4",9909:"326e4659",9912:"b391e7a8"}[e]||e)+"."+{192:"3b4391ac",358:"cd07b03a",365:"208b88cd",387:"2fda8160",428:"99aa1d9a",578:"e5bf95ba",634:"144909c3",710:"3c7a3975",720:"617a5c49",804:"d8ddc54e",824:"476c181c",996:"e8a652f9",1235:"3d81cdb5",1265:"4cd25066",1307:"0692381b",1312:"fb8a04b6",1406:"92e3326f",1430:"fb61b49a",1477:"5a819867",1664:"38ead513",1801:"9bb852e8",1810:"5506248a",1883:"f941ca2c",1890:"77506ebb",1977:"21bb9a3d",2012:"481927e7",2013:"0ab3014a",2020:"debf42df",2047:"c68e5f5f",2064:"346364d7",2140:"2974b270",2152:"a2deaa39",2176:"5ccd38d7",2212:"c7f01392",2240:"75583de8",2245:"7de19d51",2410:"f3a6b8a4",2432:"4dba0703",2484:"e32698a3",2499:"d8df595c",2529:"62247e14",2601:"c0fe04ac",2632:"61f00b2b",2691:"ba9e841a",2826:"05b201c8",2938:"d526a09b",2987:"49fe48f3",3066:"8a4fec1a",3112:"e0a05e85",3237:"2e36eb0d",3350:"d6fdf0d4",3365:"74a8a9ec",3510:"c8d6d11f",3608:"5768e1ae",3624:"9986d4f8",3626:"adcf8197",3778:"28c035ea",3834:"4a30be67",3839:"6bd0aee8",3847:"1bc50e04",3981:"2b51e72d",3991:"654bb1f8",4038:"ad11ce93",4057:"19b53464",4059:"60fa7182",4090:"531eb7ec",4095:"b4443603",4135:"cca116d8",4182:"d1fb484e",4204:"2d60aacb",4275:"211a283d",4289:"33d40e44",4315:"2ea32fa4",4318:"9ad83bf0",4362:"4bf68ac5",4390:"9196cd40",4394:"5e5bbed8",4444:"b9403d1d",4460:"354d20b3",4480:"350bac7a",4606:"4198a58c",4608:"53ff98dc",4614:"829eb37c",4645:"72e442ba",4735:"ba243b97",4807:"ecdffa79",4858:"1e4ca3ef",4905:"00f913ce",4934:"eb070f32",5017:"99cf8172",5028:"9c40b57f",5114:"88a44163",5154:"1fcc21f6",5186:"7c6e5928",5235:"9eb06137",5237:"caa5d94e",5272:"261354fd",5298:"4d781fc8",5397:"da648faa",5405:"e2ceb146",5411:"4d145122",5496:"59ce1a8e",5568:"04f14d33",5609:"f8020f56",5701:"f2c74736",5777:"415fede1",5846:"4d679714",5886:"a83005cf",6173:"234d2853",6270:"ffe37a89",6338:"0b060d09",6536:"17980858",6579:"31e02b5c",6582:"1b3b1faa",6623:"1c61344b",6704:"b6bf895e",6763:"30c6f7c0",6876:"0144f703",7002:"802de2ed",7047:"0a3e3466",7062:"c2d770bb",7084:"64123872",7123:"ccfc7a3a",7175:"19ce2668",7181:"9a6cf45e",7279:"6f88c1cb",7322:"d069c8d5",7415:"a26d01b2",7476:"61d121c2",7536:"9342aeb5",7576:"ad7515ef",7708:"c3862b34",7783:"9ab4b19f",7863:"8ce47750",7894:"8bd7be8f",7918:"9b2a3826",7990:"b007d088",7997:"3ead5c3f",8022:"55c0e181",8039:"84c79500",8159:"dedbff1c",8163:"607d1176",8206:"bfb9111a",8380:"0abdb5ca",8478:"83c0b51a",8553:"0b42d340",8659:"1411a7b9",8842:"b83f66ad",8956:"d74bf9f0",8994:"06a184fd",9027:"4cc65f14",9068:"f88cc8ad",9099:"87099580",9164:"699e5e93",9242:"0e3bc77b",9275:"4522b634",9333:"4401ab62",9342:"5603e220",9406:"d898be80",9411:"f60f7d6b",9492:"a5f6f190",9511:"07c27b96",9514:"b5efdd25",9558:"3f400288",9731:"4aa6b3e4",9807:"44205dd4",9826:"3d1f78b9",9861:"978de107",9862:"6a4500c6",9909:"cd069e7f",9912:"65cc517f"}[e]+".js"},n.miniCssF=function(e){return"assets/css/styles.53ee0a34.css"},n.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),n.o=function(e,c){return Object.prototype.hasOwnProperty.call(e,c)},a={},d="trends-in-web-dev-website:",n.l=function(e,c,f,b){if(a[e])a[e].push(c);else{var t,r;if(void 0!==f)for(var o=document.getElementsByTagName("script"),i=0;i=d)&&Object.keys(n.O).every((function(e){return n.O[e](f[r])}))?f.splice(r--,1):(t=!1,d0&&e[i-1][2]>d;i--)e[i]=e[i-1];e[i]=[f,a,d]},n.n=function(e){var c=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(c,{a:c}),c},f=Object.getPrototypeOf?function(e){return Object.getPrototypeOf(e)}:function(e){return e.__proto__},n.t=function(e,a){if(1&a&&(e=this(e)),8&a)return e;if("object"==typeof e&&e){if(4&a&&e.__esModule)return e;if(16&a&&"function"==typeof e.then)return e}var d=Object.create(null);n.r(d);var b={};c=c||[null,f({}),f([]),f(f)];for(var t=2&a&&e;"object"==typeof t&&!~c.indexOf(t);t=f(t))Object.getOwnPropertyNames(t).forEach((function(c){b[c]=function(){return e[c]}}));return b.default=function(){return e},n.d(d,b),d},n.d=function(e,c){for(var f in c)n.o(c,f)&&!n.o(e,f)&&Object.defineProperty(e,f,{enumerable:!0,get:c[f]})},n.f={},n.e=function(e){return Promise.all(Object.keys(n.f).reduce((function(c,f){return n.f[f](e,c),c}),[]))},n.u=function(e){return"assets/js/"+({192:"b84c401c",358:"8caf5144",365:"ceaa4c24",387:"c50c920e",428:"42faf210",578:"05cc7069",634:"00778435",710:"d8bcf37c",720:"f94d816f",804:"5c65e767",824:"8fb4e078",996:"a8a7ed98",1235:"d75a6d43",1265:"ad820646",1307:"cf8cc687",1312:"3cf3f69d",1406:"eb2864df",1430:"d37109ed",1477:"b2f554cd",1664:"d88cf764",1801:"73891349",1810:"891ae17b",1883:"de5b8704",1890:"b7992291",1977:"5b6eab1f",2012:"42c7ba3f",2013:"c268139f",2020:"2895e284",2047:"7a4c4d02",2064:"e680130f",2140:"25289d1f",2152:"19667349",2176:"0e4b1fab",2212:"1b59971a",2240:"381009a3",2245:"88f6781a",2410:"ae0b2600",2432:"1dee71fb",2484:"97ad368c",2499:"c237f4aa",2529:"c1bc302a",2601:"7db33633",2632:"7bd006db",2691:"a38a8c5b",2826:"67a7304f",2938:"2d3a4042",2987:"b03dc5af",3066:"e1c92344",3112:"7e722b0b",3237:"1df93b7f",3350:"c6c85278",3365:"1157a7ce",3510:"06509df0",3608:"9e4087bc",3624:"427f9d2c",3626:"06d7c6a6",3778:"34c5a6f4",3834:"c2b05852",3839:"e2da632e",3847:"d50786d6",3981:"05e07559",3991:"ce43e1ab",4038:"145263d1",4057:"5eb252c6",4059:"dd1ba448",4090:"5a121968",4095:"21369d76",4135:"9bd82af6",4182:"400a63e5",4204:"bccda9ab",4275:"22af830d",4289:"3bf54008",4315:"7db104a6",4318:"bb5b1104",4362:"fa4938a4",4390:"c69b0f77",4394:"cd5829fe",4444:"ad56e53d",4460:"3763debd",4480:"520e741b",4606:"f72aea3c",4614:"1f0a6afa",4645:"168821de",4735:"0217f38f",4807:"fafed6e2",4858:"25010c81",4905:"e3292145",4934:"f96f683d",5017:"41411fc6",5028:"6490f083",5114:"897da7a1",5154:"632d44fb",5235:"cd83c525",5237:"a5e0de92",5272:"456160f4",5298:"7154c974",5397:"dd823266",5405:"254b729f",5411:"f36f9871",5496:"6a1de0a6",5568:"4f0692d8",5609:"34340b99",5701:"2efd7a92",5777:"c5e87426",5846:"6595de78",5886:"e61e02b7",6173:"4b699842",6270:"a4717cd4",6338:"ffa6c9f2",6536:"15498faf",6579:"614cf394",6582:"44007af7",6623:"597354b5",6704:"4f4cba50",6763:"4b587976",6876:"59676426",7002:"6749d6f8",7047:"e6cfed0b",7062:"58bb1d0f",7084:"9f56bb44",7123:"f586e6d6",7175:"7ab326c8",7181:"e93d9e53",7279:"5669fd4b",7322:"4e1c8eee",7415:"d93177cc",7476:"1170099e",7536:"d53926b7",7576:"765754c9",7708:"63a834f3",7783:"6f70a0e0",7863:"962797aa",7894:"7a5c4182",7918:"17896441",7990:"676a7c04",7997:"3ac44098",8022:"285fefd4",8039:"a916eec8",8159:"8c3bc699",8163:"b234e68d",8206:"ca5dcf79",8380:"9510f52e",8478:"eb57aecd",8553:"da118e27",8659:"97b2708e",8842:"219c64ca",8956:"d7dd9adb",8994:"67fcd22c",9027:"0682386b",9068:"4d09f4bc",9099:"38c5d5d8",9164:"549b9273",9242:"9d61e91f",9275:"1beda944",9333:"98389a97",9342:"9bdd131d",9406:"9e74f5e4",9411:"d8dbc24c",9492:"0ff9b5aa",9511:"06450c56",9514:"1be78505",9731:"af2e0600",9807:"e659d385",9826:"afef7079",9861:"9d708b7f",9862:"a8144ba4",9909:"326e4659",9912:"b391e7a8"}[e]||e)+"."+{192:"3b4391ac",358:"cd07b03a",365:"208b88cd",387:"2fda8160",428:"99aa1d9a",578:"e5bf95ba",634:"144909c3",710:"3c7a3975",720:"617a5c49",804:"d8ddc54e",824:"476c181c",996:"e8a652f9",1235:"3d81cdb5",1265:"4cd25066",1307:"0692381b",1312:"fb8a04b6",1406:"92e3326f",1430:"fb61b49a",1477:"5a819867",1664:"38ead513",1801:"9bb852e8",1810:"5506248a",1883:"f941ca2c",1890:"77506ebb",1977:"21bb9a3d",2012:"481927e7",2013:"0ab3014a",2020:"debf42df",2047:"c68e5f5f",2064:"346364d7",2140:"2974b270",2152:"a2deaa39",2176:"5ccd38d7",2212:"c7f01392",2240:"75583de8",2245:"7de19d51",2410:"f3a6b8a4",2432:"4dba0703",2484:"e32698a3",2499:"d8df595c",2529:"62247e14",2601:"c0fe04ac",2632:"61f00b2b",2691:"ba9e841a",2826:"05b201c8",2938:"d526a09b",2987:"49fe48f3",3066:"8a4fec1a",3112:"e0a05e85",3237:"2e36eb0d",3350:"d6fdf0d4",3365:"74a8a9ec",3510:"c8d6d11f",3608:"5768e1ae",3624:"9986d4f8",3626:"adcf8197",3778:"28c035ea",3834:"4a30be67",3839:"6bd0aee8",3847:"1bc50e04",3981:"2b51e72d",3991:"654bb1f8",4038:"ad11ce93",4057:"19b53464",4059:"60fa7182",4090:"531eb7ec",4095:"b4443603",4135:"cca116d8",4182:"d1fb484e",4204:"2d60aacb",4275:"211a283d",4289:"33d40e44",4315:"2ea32fa4",4318:"9ad83bf0",4362:"4bf68ac5",4390:"9196cd40",4394:"5e5bbed8",4444:"b9403d1d",4460:"354d20b3",4480:"350bac7a",4606:"4198a58c",4608:"53ff98dc",4614:"829eb37c",4645:"72e442ba",4735:"ba243b97",4807:"ecdffa79",4858:"1e4ca3ef",4905:"00f913ce",4934:"eb070f32",5017:"99cf8172",5028:"9c40b57f",5114:"88a44163",5154:"1fcc21f6",5186:"7c6e5928",5235:"9eb06137",5237:"caa5d94e",5272:"261354fd",5298:"4d781fc8",5397:"da648faa",5405:"e2ceb146",5411:"4d145122",5496:"59ce1a8e",5568:"04f14d33",5609:"f8020f56",5701:"f2c74736",5777:"415fede1",5846:"4d679714",5886:"a83005cf",6173:"234d2853",6270:"ffe37a89",6338:"0b060d09",6536:"17980858",6579:"31e02b5c",6582:"1b3b1faa",6623:"1c61344b",6704:"b6bf895e",6763:"30c6f7c0",6876:"0144f703",7002:"802de2ed",7047:"0a3e3466",7062:"c2d770bb",7084:"64123872",7123:"ccfc7a3a",7175:"19ce2668",7181:"8957deaf",7279:"6f88c1cb",7322:"d069c8d5",7415:"a26d01b2",7476:"61d121c2",7536:"9342aeb5",7576:"ad7515ef",7708:"c3862b34",7783:"9ab4b19f",7863:"8ce47750",7894:"8bd7be8f",7918:"9b2a3826",7990:"b007d088",7997:"3ead5c3f",8022:"55c0e181",8039:"84c79500",8159:"dedbff1c",8163:"607d1176",8206:"bfb9111a",8380:"0abdb5ca",8478:"83c0b51a",8553:"0b42d340",8659:"1411a7b9",8842:"b83f66ad",8956:"d74bf9f0",8994:"06a184fd",9027:"4cc65f14",9068:"f88cc8ad",9099:"87099580",9164:"699e5e93",9242:"0e3bc77b",9275:"4522b634",9333:"4401ab62",9342:"5603e220",9406:"d898be80",9411:"f60f7d6b",9492:"a5f6f190",9511:"07c27b96",9514:"b5efdd25",9558:"3f400288",9731:"4aa6b3e4",9807:"44205dd4",9826:"3d1f78b9",9861:"978de107",9862:"6a4500c6",9909:"cd069e7f",9912:"65cc517f"}[e]+".js"},n.miniCssF=function(e){return"assets/css/styles.53ee0a34.css"},n.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),n.o=function(e,c){return Object.prototype.hasOwnProperty.call(e,c)},a={},d="trends-in-web-dev-website:",n.l=function(e,c,f,b){if(a[e])a[e].push(c);else{var t,r;if(void 0!==f)for(var o=document.getElementsByTagName("script"),i=0;i Archive | Trends in Web Dev - + - + \ No newline at end of file diff --git a/docs/2020fa/HackOurCampus/index.html b/docs/2020fa/HackOurCampus/index.html index 1a523d52d..73b5006c1 100644 --- a/docs/2020fa/HackOurCampus/index.html +++ b/docs/2020fa/HackOurCampus/index.html @@ -5,13 +5,13 @@ Full Stack Integration Workshop | Trends in Web Dev - +
Version: 2020fa

Full Stack Integration Workshop

Slides

GitHub Repo

Prerequisites

For this workshop you will need the following programs installed:

  • Node.js: Install Node LTS from here
  • Yarn: Follow the instructions here (we will use yarn over npm)
  • Flask: in terminal run pip install flask
  • Google Cloud CLI: Select your operating system here and follow directions in Before you begin.
  • Firebase: Create an account on Firebase

Deployment

To deploy your web application means to put it on a Web server so others can access it via the internet. We will be demonstrating how to deploy your projects (both frontend and backend) using Firebase.

note

Wait, what is Firebase?

Firebase is a platform built by Google which consists of authentication, hosting, file storage, cloud functions, a realtime NoSQL database, and more. You can learn more about its features at firebase.google.com

Backend Deployment

We will be deploying our backend Flask app on Google Cloud Run. For the backend, we will be using the following sample Flask endpoint (refer to the backend workshop for more info):

app.py
from flask import Flask, request, jsonify

app = Flask(__name__)

# All the products we are selling
PRODUCTS = [
{ 'category': 'Sporting Goods', 'price': '$49.99', 'stocked': True, 'name': 'Football' },
{ 'category': 'Sporting Goods', 'price': '$9.99', 'stocked': True, 'name': 'Baseball' },
{ 'category': 'Sporting Goods', 'price': '$29.99', 'stocked': False, 'name': 'Basketball' },
{ 'category': 'Electronics', 'price': '$999.99', 'stocked': True, 'name': 'iPad Pro' },
{ 'category': 'Electronics', 'price': '$399.99', 'stocked': False, 'name': 'iPhone 5' },
{ 'category': 'Electronics', 'price': '$199.99', 'stocked': True, 'name': 'Nexus 7' }
]

# endpoint to handle GET requests at /products, we will return all the entries in PRODUCTS
@app.route('/products')
def get_all_products():
return jsonify({'products': PRODUCTS})

if __name__ == '__main__':
app.run(threaded=True, host='0.0.0.0', port=8080)

Firebase Setup

  1. Navigate to Firebase and create a new project
  2. Initialize cloud storage and firestore
  3. Enable billing โ€“ it wonโ€™t actually charge you
  4. Navigate to Google Cloud Platform search for Cloud Run API, and enable it
  5. Go to IAM on the navigation bar on the left, and add the cloud build and cloud run permissions to the <project-number>-compute@developer.gserviceaccount.com
  6. Go to service accounts and under actions create a new key for the same account โ€“ move this file to your project directory and name it key.json

Deployment Process

  1. First, you should have downloaded the gcloud command line interface (CLI) as per the pre-requisites
  2. In your project directory, type gcloud auth login and login with the same account used to create the project
  3. Type gcloud config set project <project-id>
  4. Then, modify the provided cloudbuild.yaml file to use your project name in place of ours, and your developer account email instead of ours
  5. Run gcloud builds submit --config cloudbuild.yaml .

You should get a link like https://todo-RANDOMHASH-uc.a.run.app/products. Copy/save this outputted URL because you will using this in the frontend API call.

Frontend Deployment

Example Code

For the frontend, we used the example code here taken from React docs here, but instead of declaring all the products in the App component, we make a GET call to our backend /products endpoint in the FilterableProductTable to fetch the products list. The relevant changes are below:

note

If you want to learn more about setting up a frontend React application, check out lecture 5, 6, 7, 8.

App.js
import React from 'react';
import FilterableProductTable from './FilterableProductTable';

const App = () => (
<div className="App">
<FilterableProductTable />
</div>
);

export default App;
FilterableProductTable.jsx
import React from 'react';

const FilterableProductTable = () => {
const [products, setProducts] = useState([]);
const [filterText, setFilterText] = useState('');
const [inStockOnly, setInStockOnly] = useState(false);

const handleFilterTextChange = (e) => setFilterText(e.target.value);
const handleCheckBoxChange = (e) => setInStockOnly(e.target.checked);

// this function fetches the products data from our backend endpoint
useEffect(() => {
// add the BACKEND_URL you received from deploying your backend
fetch('[BACKEND_URL]/products')
.then((resp) => resp.json())
.then(({ products }) => setProducts(products));
}, []);

return (
<div>
<SearchBar
filterText={filterText}
inStockOnly={inStockOnly}
handleFilterTextChange={handleFilterTextChange}
handleCheckBoxChange={handleCheckBoxChange}
/>
<ProductTable
products={products}
filterText={filterText}
inStockOnly={inStockOnly}
/>
</div>
);
};

export default FilterableProductTable;
tip

In testing, we can add this line to package.json to proxy our requests to a locally deployed backend:

  "proxy": "http://localhost:8080",

The port is 8080 because our backend is running on port 8080 of localhost (equivalently, 0.0.0.0).

Deployment Process

To deploy frontend to Firebase enter the following commands into terminal:

yarn global add firebase-tools
yarn build
firebase login
firebase init
<answer the questions>
firebase deploy

yarn build will create a build directory containing a production build of your application.

firebase login will prompt you to log in by opening up a web browser if you're not already signed it.

firebase init will ask you the following questions:

  1. Which Firebase CLI features do you want to set up for this folder? Select Hosting.
  2. Associate with a Firebase project. Select your Firebase project
  3. What do you want as your public directory? build
  4. Configure as a single-page app (rewrite all urls to /index.html)? Yes
  5. Overwrite index.html? No

Running firebase deploy will push your build assets to Firebase remote server and give you a URL to your live Firebase app site! Now you can share this site and access it over the internet.

- + \ No newline at end of file diff --git a/docs/2020fa/assignment1/index.html b/docs/2020fa/assignment1/index.html index e21c06bb9..09636207f 100644 --- a/docs/2020fa/assignment1/index.html +++ b/docs/2020fa/assignment1/index.html @@ -5,13 +5,13 @@ Assignment 1 | Trends in Web Dev - +
Version: 2020fa

Assignment 1

For the first assignment, you will be setting up a basic server with 3 simple GET endpoints.

Set up your project

First, let's set up your first Node project!

To create a project, first make a folder for your assignment code using mkdir a1 in terminal and change into that directory with cd a1. You can then use the command code . to open that folder in VSCode (install VSCode here). You can also just open it in VSCode from the application directly.

Set up the yarn project by running yarn init -y in that a1 folder. Add the necessary dependencies express, typescript, ts-node, @types/node, @types/express by running:

yarn init -y
yarn add express
yarn add --dev typescript ts-node @types/express
info

Check out lecture1 for more details on how this setup works!

Now you can create an index.ts file to add your routes and assignment code in.

Endpoints

1. Simple URL

The first endpoint will be a simple GET request to /book. You will send your favorite book's name to the frontend.

2. Query Parameters

The second endpoint will be a GET request to /book with a query parameter called name. If the name is "Megan", you will return "Cracking the Coding Interview". If the name is "Ashneel", you will return "The Pragmatic Programmer". Otherwise, return the name of any book you choose!

Example: a request to /book?name=megan will return "Cracking the Coding Interview."

P.S. Don't worry, these aren't actually our favorite books.

3. Route Parameters

The third endpoint will be a GET request to /book/:userid. This will simply send "userid is really cool!" to the frontend.

Example: a request to /book/ashneel will send "ashneel is really cool!" to the frontend.

Testing your code

To test your code run ts-node index.ts. Your server should be up and running (if not, debug!) and you should be able to navigate to localhost:<PORT>/book where PORT is the port number express is listening for requests on (we usually use 8080) and specify query/path parameters that give the intended results.

Getting help

If you're stuck, we have TA office hours every week day so feel free to come and ask questions! In addition, you can join the Piazza and post your question. Please make it public if it's just a general question with no code screenshots, otherwise make it private.

Submission

Please submit to CMS the index.ts file where you have defined your routes by Tuesday 9/29 at 3:59pm although you are allowed max 3 slip days (out of 6 total) per an assignment. (Note: we are still getting CMS set up. If we are unable to have CMS up by Monday, we will notify you of another mode of submission or extend the deadline)

- + \ No newline at end of file diff --git a/docs/2020fa/assignment2/index.html b/docs/2020fa/assignment2/index.html index 2d248adf4..699246225 100644 --- a/docs/2020fa/assignment2/index.html +++ b/docs/2020fa/assignment2/index.html @@ -5,7 +5,7 @@ Assignment 2 | Trends in Web Dev - + @@ -42,7 +42,7 @@ in the code so we can test it against our own database. Don't worry, we won't fill your database with junk! ๐Ÿ˜Š๐Ÿ˜Š๐Ÿ˜Š

caution

DO NOT submit your node_modules. We WILL deduct points for this. Lots of points.

- + \ No newline at end of file diff --git a/docs/2020fa/assignment3/index.html b/docs/2020fa/assignment3/index.html index 175cd65ec..5f12dd7ab 100644 --- a/docs/2020fa/assignment3/index.html +++ b/docs/2020fa/assignment3/index.html @@ -5,7 +5,7 @@ Assignment 3 | Trends in Web Dev - + @@ -15,7 +15,7 @@ in the format {name: string, age: number, breed: string} and maps it to an array of sentences in the format "name is age years old, and is a breed."

Please use object destructuring to get object fields.

NOTE

A "prototype" is an instance of an object in JavaScript/TypeScript. In documentation, methods are often denoted like: Type.prototype.method()

Remember, anything in JavaScript/TypeScript can be an object!

So, we can do: 5.toExponential(10) or let x = 5; x.toExponential()

Your goal is to display "5 years" for the value 5.6

Take a look at this documentation and choose an appropriate method to use!

EXAMPLE

const doggos = [
{ name: 'Sparky', age: 3.3, breed: 'Pomeranian Husky' },
{ name: 'Oreo', age: 5.4, breed: 'Dalmatian' },
{ name: 'Stella', age: 4.3, breed: 'Alaskan Klee Kai' },
];

makeSentences(doggos);

should output

[
'Sparky is 3 years old and is a Pomeranian Husky',
'Oreo is 5 years old and is a Dalmatian',
'Stella is 4 years old and is a Alaskan Klee Kai',
];

Starter code:

// TODO: You should replace this any with an accurate object type in your submission!
type Doggo = any;

export const makeSentences = (array: Doggo[]): string[] => {
/* TODO: add your code */
};

Optional Challenge: In English, "a" becomes "an" before vowels. Create a function such that makeCorrectSentences(doggos) correctly handles this case.

Submission

Please submit to CMS your index.ts file containing your implementations of each of the functions described above.

- + \ No newline at end of file diff --git a/docs/2020fa/assignment4/index.html b/docs/2020fa/assignment4/index.html index 5e8daacf5..3fea1f14a 100644 --- a/docs/2020fa/assignment4/index.html +++ b/docs/2020fa/assignment4/index.html @@ -5,7 +5,7 @@ Assignment 4 | Trends in Web Dev - + @@ -15,7 +15,7 @@ It will be passed a prop called info that is an object which contains some information about the song. The fields in this object will be title, and artist.

You can test this component by importing the Song component in App.tsx and creating a component:

<Song info={{ title: 'Never Gonna Give You Up', artist: 'Rick Astley' }} />

Notice here that info acts a single prop. Song should expect only one prop, that being info.

Part 2: Adding songs

Create a component, Playlist, that contains all of the songs you've added. This will do the following:

  • Maintain a state containing a list of songs, which are objects containing the info about each song.
  • Have two input fields, one for title and one for artist.
  • Have a button which allows you to submit the new song and adds to the list.

Part 3: Adding Playlist to App.ts

Initialize your Playlist component in App.ts. This can be done by importing Playlist and creating a component:

<Playlist />

Part 4: Optional Challenge

Make it so that checking a box next to a song removes it from the Playlist.

Submission

Submit a zip file of everything in your project directory EXCEPT node_modules. You WILL lose points for including node_modules.

- + \ No newline at end of file diff --git a/docs/2020fa/assignment5/index.html b/docs/2020fa/assignment5/index.html index 99885813e..41ee71284 100644 --- a/docs/2020fa/assignment5/index.html +++ b/docs/2020fa/assignment5/index.html @@ -5,7 +5,7 @@ Assignment 5 | Trends in Web Dev - + @@ -27,7 +27,7 @@ will display all the other friends.

Part 3

Use conditional rendering to account for the case in which the filtered list is empty. Display a message such as "No friends found" if the filtered list in either component is empty.

Submission

Submit to CMS a zip file of everything in your project (create-react-app) directory but remove node_modules . Failure to remove node_modules will result in a 10 point deduction.

- + \ No newline at end of file diff --git a/docs/2020fa/assignments/index.html b/docs/2020fa/assignments/index.html index c6df90263..23bcb7d9f 100644 --- a/docs/2020fa/assignments/index.html +++ b/docs/2020fa/assignments/index.html @@ -5,13 +5,13 @@ Assignments | Trends in Web Dev - +
Version: 2020fa

Assignments

Assignments will be released here after lecture!

Assignment 1: Due on CMS 10/2 at 11:59pm

Assignment 2: Due on CMS 10/13 at 3:59pm

Assignment 3: Due on CMS 10/20 at 3:59pm

Assignment 4: Due on CMS 10/27 at 3:59pm

Assignment 5: Due on CMS 11/3 at 3:59pm

Team Matching Form: Fill out form by 11/7 at 11:59PM (no slip days)

Milestone 0: Due on CMS 11/10 at 3:59PM (no slip days)

Milestone 1: Due on CMS 12/1 at 3:59PM

Milestone 2: Due on CMS 12/8 at 3:59PM

Final Submission/Milestone 3: Due on CMS 12/15 at 3:59PM

- + \ No newline at end of file diff --git a/docs/2020fa/finalproject/index.html b/docs/2020fa/finalproject/index.html index f0df3a5cb..c950391f8 100644 --- a/docs/2020fa/finalproject/index.html +++ b/docs/2020fa/finalproject/index.html @@ -5,7 +5,7 @@ Final Project | Trends in Web Dev - + @@ -30,7 +30,7 @@ group members and netIDs, a link to the deployed site, a link to the GitHub repo if you used GitHub, and anything else that you think is important for us to know.

As always, do not include your node_modules.

Tips for Success!

  • Get in contact with your partner early!
    • Milestone 0 is intended for you to get some discussion on what you want to build before you start implementation. Make sure you are both aligned on what needs to be built to avoid issues later on. Better initial planning means less frustrations later on.
  • Be realistic.
    • We know you are ambitious but also understand your own capabilities. Building something too complex may be too overwhelming. You are allowed to change ideas, but that would be time wasted on the old project.
  • Use GitHub
    • GitHub is the best tool for sharing code between you and your partner/team members. Please use GitHub instead of emailing code back and forth to each other.
  • Use branches!
    • When developing a feature, you should open up a new GitHub branch rather than committing and pushing directly to master. This will allow you to develop your feature independently of the current state of master (and what your partner is doing) and only merge in when you are sure your feature is done and works.
    • Branches can also protect you from weird frustrating merge conflicts (so you can focus on developing awesome features!)
  • Pair programming is fun!
    • Ideally, you should both be actively involved in the whole development process. A good way to achieve this is to step up a time to pair program and code together!
  • Also refer to tips in How to Lose in CS 2112
- + \ No newline at end of file diff --git a/docs/2020fa/introduction/index.html b/docs/2020fa/introduction/index.html index f39cf7577..01823b330 100644 --- a/docs/2020fa/introduction/index.html +++ b/docs/2020fa/introduction/index.html @@ -5,7 +5,7 @@ Syllabus | Trends in Web Dev - + @@ -14,7 +14,7 @@ is due right before class of the following week at 3:59pm unless otherwise stated. You will have 6 slip days total to use on the assignments, but for each assignment, you may only use up to 2 slip days. Use these judiciously because we will not be handling extensions outside of slip days.

Assignments must be submitted on CMS. We will not take submissions emailed to us. If you are not on the CMS please email Megan (my474@cornell.edu) or Ashneel (ad665@cornell.edu)

What will be taught?

By the end of the course, students will be have a much more in-depth understanding of JavaScript as it pertains to many common software libraries used in web development. These libraries include (but are not limited to) React, Express, Node.js, NPM, Express, and Firebase. The exact technologies can shift from semester to semester as demands from students, and in the industry, evolve and change. What is in demand now may not be desired in two years from now. The primary technologies that this class is powered by can shift from semester to semester to reflect what employers are looking for.

Throughout this course, students will work as individuals and in groups to apply these skills to projects. These are both skills that are extremely important to employers: being able to function independently on assigned tasks, and being able to collaborate with different people on a wide variety of tasks. The idea is to closely resemble milestones and checkpoints in project development, which occur with one to many people.

What are the prerequisites?

This course will be covering both client-facing and server-side technologies. CS 1110 or equivalent programming experience is a pre-requisite.

Please complete the pre-assessment here. It should take less than an hour! This is not meant as a test, but rather a way of ensuring that you are familiar with the foundational material in the course. Upload your submissions as a zip to your application at https://bit.ly/web-dev-fa20. This preassessment is mandatory; those who do not submit it will not be admitted in the course.

When is it?

The first class meeting will be Tuesday, September 15th and end on Tuesday, November 24th (right before Thanksgiving). This course will be held Tuesday evenings from 4:30 - 5:30 PM on Zoom.

Where does it meet?

Zoom

What is the expected workload?

Students should expect to work anywhere from 5-6 hours per week in this course.

What software will be supported in the course?

You are free to use whichever text editor or programming IDE of your choice. However, course staff will not be able to directly help you with problems that relate to your editor or IDE.

How many credits is it?

Two credits S/U, although students are allowed to audit this course for 0 credits.

What's the grading policy?

Attendance - 20% (based on weekly lecture quizzes, can miss 1 of 11 without penalty)

Filling out Feedback - 10%

Final project - 20%

Assignments - 50%

Keep in mind that you only need a C- (70) or higher to pass. If you ever feel that you are falling behind, please feel free to talk to us and we will try our best to find a solution. You can reach us at my474@cornell.edu or ad665@cornell.edu.

Who should I contact with questions?

Email Megan (my474@cornell.edu) or Ashneel (ad665@cornell.edu).

Policies

Academic Integrity

As a programming course, you may find yourself in a position to copy or appropriate code that someone else has written. Please cite any code or media that you do not have direct authorship of on any assignments submitted to the course. Code should receive a citation to the original author as a comment in your source code, while media citations (images, videos) should be visible on the page that they appear.

Accessibility

We seek to make this class as inclusive as possible for all students. All lectures will be video recorded for instructor use only. If you have accommodations with Student Disability Services and require access to these recordings, or any other class accommodations, please speak to an instructor before the first lecture and we will work with you to make arrangements as necessary.

Cell Phones

Cell phones are distracting to you, people around you, and instructors. If you plan on taking this course and sitting in lectures on your phone and not paying attention, you may be asked to leave. Please be respectful of other people.

- + \ No newline at end of file diff --git a/docs/2020fa/lecture0/index.html b/docs/2020fa/lecture0/index.html index bbb6d29cb..7d627c972 100644 --- a/docs/2020fa/lecture0/index.html +++ b/docs/2020fa/lecture0/index.html @@ -5,13 +5,13 @@ Lecture 0 | Trends in Web Dev - +
Version: 2020fa

Lecture 0

Lecture Video

Lecture Slides

No homework this week! We're still getting CMS/Piazza set up

JavaScript

What is JavaScript

  • JavaScript is the defacto language of the web
  • Commonly used in conjunction with HTML/CSS
  • Became really popular for powering client-side logic through AJAX
    • Previously, languages like PHP had to communicate with the server before coming back with a response
  • These days JavaScript is everywhere!
note

Java is to JavaScript as car is to carpet. They are very different languages!

Basic JavaScript Syntax

Variables

There are three ways to create variables in JS:

  1. var x = 5
  2. let x = 5
  3. const x = 5

We prefer using const for immutability although let is also accepted. Never use var.

if statements

if (condition) {
// executes if condition is true
} else if (condition2) {
// executes if condition is false but condition2 is true
} else {
// executes if condition is false
}

for loops

regular counter for loop
for (let i = 0; i < 5; i++) {
console.log(i);
}
for of loop

We can use for..of loops to loop through elements of an array.

const arr = [10, 20, 30, 40];
for (const val of arr) {
console.log(val); // prints values: 10, 20, 30, 40
}

for in loop

We can use for..in loops to loop through keys of an object.

const object = { a: 1, b: 2, c: 3 };

for (const property in object) {
console.log(`${property}: ${object[property]}`);
}

// expected output:
// "a: 1"
// "b: 2"
// "c: 3"

while loops

let n = 0;

while (n < 3) {
console.log(n);
n++;
}

// expected output:
// "0"
// "1"
// "2"

function declaration

We can use the function key word to define a function!

function calcRectArea(width, height) {
return width * height;
}

console.log(calcRectArea(5, 6)); // 30

or we can use arrow functions:

const calcRectArea = (width, height) => {
return width * height;
};

More details on arrow functions in a few weeks when we talk about ES6!

tip

JavaScript is a super powerful language and this was just a small sample of its language features. Check out Mozilla Developer Network (MDN) for the best JavaScript documentation: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide

TypeScript

TypeScript is a typed superset of JavaScript that compiles to plain JavaScript. Superset means TypeScript has everything in JavaScript and more. (Built by Microsoft!)

JavaScript Types

JavaScript has 6 primitive types:

  • Boolean
  • String
  • Number
  • Symbol
  • undefined
  • BigInt

All JavaScript values are those 6 primitive types or a:

  • object
  • function (JavaScript is functional!)
  • null

How are types used?

In JavaScript we had:

let str = 'Hello, trends';
let num = 42;
let truth = true;

const someFunc = (x, s, b) => {
// do some operations...
return x;
};

Notice we don't have any types here! JavaScript is weakly typed.

let str: string = 'Hello, trends';
let num: number = 42;
let truth: boolean = false;
const someFunc = (x: number, s: string, b: boolean): number => {
// do some operations...
return x;
};

TypeScript allows us to add type information!

Why TypeScript?

JavaScript code can be ambiguous. We had the function:

const someFunc = (x, s, b) => {
// do some operations...
return x;
};

What are x, s, b? What should I pass in for those? What should I expect returned?

Adding the TypeScript types makes this code self-documenting:

const someFunc = (x: number, s: string, b: boolean): number => {
// do some operations...
return x;
};

JavaScript variables can also change type which can be undesirable, unexpected, and error-prone.

let str = 'Hello, trends';
let num = 42;
let truth = true;
str = 13;

None of these variables have to be any specific type! I can have str be a string and then a number.

In the end, we want to use TypeScript because it is:

  • Easier to read
  • Easier and faster to implement
  • Easier to refactor
  • Less buggy

TypeScript Types

Basic Syntax:

let <var_name>: <type> = <something>;

We can also use const but again no var.

Basic Types

// Boolean
let isDone: boolean = false;
// Number can be decimal, or in any base!
let decimal: number = 4.2;
let binary: number = 0b1010;
let hex: number = 0xf00d;
// String
let lang: string = 'typescript';
let templateStr: string = `We love ${lang}`;
// Boolean
let isDone: boolean = false;
// Number can be decimal, or in any base!
let decimal: number = 4.2;
let binary: number = 0b1010;
let hex: number = 0xf00d;
// String
let lang: string = 'typescript';
let templateStr: string = `We love ${lang}`;

Any

Any is a wildcard and it can be anything. any places no restrictions on type.

// Any: can be anything!
let notSure: any = 4;
notSure = 'maybe a string instead';
notSure = false; // okay, definitely a boolean

If you were to use any everywhere though you might as well just use JavaScript

let anyList: any[] = [4, 'maybe a string', false];

But it can be useful in specifying collections of items of different types!

Functions

Functions can have types too!

// un-typed
const myFunc = (x, y) => x + y;
// typed
const myFunc = (x: number, y: number): number => x + y;

myFunc has type (x: number, y: number): number.

TypeScript can do some limited type inference so if you leave out the return type number, TypeScript can infer it since we are just adding two numbers which can only produce a number. If TypeScript can't infer the type, it defaults as any.

We can also have optional parameters:

const introduce = (name: string, github?: string): string => {
return github
? `Hi, I'm ${name}. Checkout my GitHub @${github}`
: `Hi, I'm ${name}. I don't have a GitHub.`;
};

github? designates github as an optional parameter that defaults to undefined.

Literal Types

Literal Types are types that can be a literal set of possibilities that you specify. TypeScript allows number and string literal types:

String Literal Types
// String literal type
type TrafficLightColors = 'red' | 'green' | 'yellow';

Any variable with TrafficLightColors type can only take on values "red", "green", "yellow".

let light1: TrafficLightColors = 'red';
light1 = 'blue'; // TypeError
Numeric Literal Types
// Numeric literal type
type DiceRoll = 1 | 2 | 3 | 4 | 5 | 6;
const rollDice = (): DiceRoll => {
// ...
};

Union Types

With union types, a variable can be of one type or another type.

const union: number | string = 5; // number
const union2: number | string = 'hello'; // string

type TrafficLightColors = 'red' | 'green' | 'yellow';
type PrimaryColors = 'red' | 'green' | 'blue';

// "red" | "green" | "yellow" | "blue"
type union = PrimaryColors | TrafficLightColors;

Intersection Types

With union types, a variable must be of one type and another type.

// Intersection Type
type TrafficLightColors = 'red' | 'green' | 'yellow';
type PrimaryColors = 'red' | 'green' | 'blue';
type intersect = PrimaryColors & TrafficLightColors; // "red" | "green"
tip

There's also so much more to TypeScript. Checkout TypeScript docs to learn more! https://www.typescriptlang.org/docs/

Demo

We went through a demo of writing and running code in TypeScript using the preassessment as an example. First install typescript and ts-node:

yarn global add typescript
yarn global add ts-node

We used the following example code:

script.ts
const mySum = (inputArray: number[]): number => {
let sum: number = 0;
for (const num of inputArray) {
sum += num;
}
return sum;
};

console.log(mySum([1, 2, 3])); // expected 6

const isLeapYear = (year: number): boolean => {
return (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;
};

console.log(isLeapYear(2000)); // is a leap year
console.log(isLeapYear(2100)); // is NOT a leap year;

const perfectSquares = (arr: number[]): number[] => {
const ans: number[] = [];
for (const num of arr) {
if (Math.sqrt(num) % 1 === 0) {
ans.push(num);
}
}
return ans;
};

console.log(perfectSquares([1, 4, 9])); // expected same as input
console.log(perfectSquares([1, 5, 9])); // expected [1, 9]

Run it with ts-node script.ts

- + \ No newline at end of file diff --git a/docs/2020fa/lecture1/index.html b/docs/2020fa/lecture1/index.html index d6d3f8ba7..85cde432a 100644 --- a/docs/2020fa/lecture1/index.html +++ b/docs/2020fa/lecture1/index.html @@ -5,7 +5,7 @@ Lecture 1 | Trends in Web Dev - + @@ -17,7 +17,7 @@ PATH is a path on the server. HANDLER is the function executed when the route is matched. The following code sends โ€œhello worldโ€ as a result of a GET request to โ€˜/' endpoint.

const express = require('express');
const app = express();

// respond with "hello world" when a GET request is made to the homepage
app.get('/', function (req, res) {
res.send('hello world');
});

Custom URLs

We can respond differently for requests to different URLs. For example, if we wanted โ€˜/home' to respond with โ€œWelcome Home!โ€ we could add a second route.

app.get('/home', function (req, res) {
res.send('Welcome Home!');
});

Both of these blocks of code respond to GET requests, because we are using Express's app.get() function. We tell express what route we want to trigger the response and give it a function that should be run to respond.

https://expressjs.com/en/starter/basic-routing.html

POST methods

POST method is generally used to submit data to an endpoint.

The following uses Express's app.post() method to send a POST request to โ€˜/' and responds with โ€˜Got a POST request'. Notice that app.post() has a second argument that is a function with two parameters, req and res. Usually, when you call POST you want to send data with the request. You would send that data as the req parameter.

app.post('/', function (req, res) {
res.send('Got a POST request');
});

Regular Expressions

You can match patterns in text rather than specific characters. For example, what if you want to return the same page for all URLs of /users/[SOME NUMBER]? We can use regular expressions so that the route will be used for any value. We can use the route string /users/\d+ to match any number. Regular expressions are outside the scope of this class, but you may find more information in the references below.

Parameterized Routes

Take a look at this route, paying special attention to the : characters. Those denote parameters in the route.

app.get('/users/:userId/books/:bookId', function (req, res) {
res.send(req.params);
});

For example, if you navigate to the page /users/34/books/12973, you would now be able to use those IDs in your code. req.params.userId would now equal 34 and req.params.bookId would now equal 12973. This allows you to respond differently depending on IDs passed to you by the front end.

In the following code snippet we use app.get() to query a messages endpoint and we want to get a specific message. We call this query parameter messageId and can use it in the function we pass to app.get() to return that message.

const messages = {...}
app.get('/messages/:messageId', (req, res) => {
return res.send(messages[req.params.messageId]);
});

References

Don't know where to start? Check out Express's official getting started page. The rest of the pages have great content and will help you along your journey.

For a more in-depth exploration of this topic, check out this tutorial.

This is a useful tool to test your Express regular expressions to make sure that the routes match what you expect.

For an interactive way to learn regular expressions, consider this site.

Coding Demo

We demoed how to set up a yarn project and create some basic getter HTTP routes.

Set up a yarn project by running yarn init. It will ask you a few questions and you can press return to accept all the defaults:

yarn init v1.22.5
question name (lec1code):
question version (1.0.0):
question description:
question entry point (index.js):
question repository url:
question author:
question license (MIT):
question private:
success Saved package.json
โœจ Done in 37.88s.
tip

You can also use yarn init -y to set up a project with all the default values!

This will generate a package.json file with all the inputs you provided.

We also want to add several dependencies starting with express. Add express with yarn add express. Since we are also using TypeScript we want to install the typescript and ts-node packages as well using yarn add typescript ts-node. Finally to get some nice autocompletion features, we want to install @types/node and @types/express as devDependencies using yarn add -D @types/node @types/express.

Your package.json should now look like the following:

package.json
{
"name": "lec1code",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"dependencies": {
"express": "^4.17.1",
"ts-node": "^9.0.0",
"typescript": "^4.0.3"
},
"devDependencies": {
"@types/express": "^4.17.8",
"@types/node": "^14.11.2"
}
}

Now we can define some basic express routes in a file index.ts:

index.ts
import * as express from 'express';

const app = express();

app.get('/home', function (req, res) {
res.send('Welcome home!');
});

// example using path parameters
app.get('/users/:name/:lname', function (req, res) {
res.send('Hello ' + req.params.name + ' ' + req.params.lname);
});

// example using query parameters
app.get('/users', function (req, res) {
res.send('Hello ' + req.query.name + ' ' + req.query.lname);
});

// tell express to listen for requests on port 8080
app.listen(8080, function () {
console.log('server started');
});

Add the following to your package.json:

// other package.json properties...
"scripts": {
"start": "ts-node index.ts"
},

This will allow you to run ts-node without having it installed globally (which is bad practice). Now you can run the app with yarn start.

Now when you go to localhost:8080/home you should see Welcome home!. At localhost:8080/users/<your_name>/<your_last_name> or localhost:8080/users/?name=<your_name>&lname=<your_last_name> you should see Hello <your_name> <your_last_name>.

- + \ No newline at end of file diff --git a/docs/2020fa/lecture10/index.html b/docs/2020fa/lecture10/index.html index df89630b9..2203b691c 100644 --- a/docs/2020fa/lecture10/index.html +++ b/docs/2020fa/lecture10/index.html @@ -5,13 +5,13 @@ Lecture 10 | Trends in Web Dev - +
Version: 2020fa

Lecture 10

Final Project due December 15 at 3:59pm (late deadline December 18 3:59pm ET slip days permitting)

Lecture Video

Lecture Slides

How to prettify your app!

There are a lot of different options for styling your app. You can write the CSS stylesheets yourself and import them into your React application or you can use a framework! Frameworks like MaterialUI, Bootstrap, SemanticUI have developed โœจ styled โœจ React Components for you to use easily! It's as easy as importing and using!

In this lecture we went through how to add one framework, MaterialUI, to our Thinking in React example from lecture 7.

MaterialUI

MaterialUI is a framework developed from Google for applying some default (Google-like) styling to your React components!

Getting Started

When using MaterialUI or really any framework, package, or external tool, you should first look at the documentation. The documentation usually gives you tips on how to install and use the package.

For MaterialUI, we see that we first need to install it since it's an external package. Run

yarn add @material-ui/core

Next, we also want to use the classic Roboto font and the documentation says we need to add this line

<link
rel="stylesheet"
href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"
/>

to our public/index.html.

Inputs

Let's first convert our input fields (the search bar and the checkbox) into MaterialUI. We can navigate to the components page of MaterialUI by clicking the hamburger icon (three lines) on the top left of the page and scrolling down to Components.

We see that there is a TextField component. On that page we can see several variations of TextField and the code associated with it. There is also a Checkbox component that can replace our original checkbox.

Adding these to the SearchBar component in FilterableProductTable.tsx, the final file looks like this:

FilterableProductTable.tsx
import React, { ChangeEvent, useState } from 'react';
import ProductTable, { Product } from './Starter';
import { TextField, Checkbox } from '@material-ui/core/';

type TableProps = {
readonly products: Product[];
};

type SearchProps = {
readonly filterText: string;
readonly inStockOnly: boolean;
readonly handleFilterTextChange: (e: ChangeEvent<HTMLInputElement>) => void;
readonly handleCheckBoxChange: (e: ChangeEvent<HTMLInputElement>) => void;
};

const SearchBar = ({
filterText,
inStockOnly,
handleFilterTextChange,
handleCheckBoxChange,
}: SearchProps) => (
<form>
<TextField
label="Search"
variant="outlined"
placeholder="Search for a product!"
value={filterText}
onChange={handleFilterTextChange}
/>
<p>
<Checkbox
inputProps={{ 'aria-label': 'uncontrolled-checkbox' }}
value={inStockOnly}
onChange={handleCheckBoxChange}
/>{' '}
Only show products in stock
</p>
</form>
);

const FilterableProductTable = ({ products }: TableProps) => {
const [filterText, setFilterText] = useState('');
const [inStockOnly, setInStockOnly] = useState(false);

const handleFilterTextChange = (e: ChangeEvent<HTMLInputElement>) =>
setFilterText(e.target.value);
const handleCheckBoxChange = (e: ChangeEvent<HTMLInputElement>) =>
setInStockOnly(e.target.checked);

return (
<div>
<SearchBar
filterText={filterText}
inStockOnly={inStockOnly}
handleFilterTextChange={handleFilterTextChange}
handleCheckBoxChange={handleCheckBoxChange}
/>
<ProductTable
products={products}
filterText={filterText}
inStockOnly={inStockOnly}
/>
</div>
);
};

export default FilterableProductTable;

Notice that we imported each component from @material-ui/core before using it.

Starter.tsx

We apply the same methodology to converting the product table to MaterialUI. MaterialUI has several Table components that can replace the traditional HTML table elements we use in Starter.tsx.

Starter.tsx
import React, { ReactElement } from 'react';
import {
Table,
TableHead,
TableBody,
TableRow,
TableCell,
createStyles,
withStyles,
WithStyles,
} from '@material-ui/core';

export type Product = {
readonly category: string;
readonly price: string;
readonly stocked: boolean;
readonly name: string;
};

const ProductRow = (product: Product) => {
const name = product.stocked ? (
product.name
) : (
<span style={{ color: 'red' }}>{product.name}</span>
);
return (
<TableRow>
<TableCell>{name}</TableCell>
<TableCell>{product.price}</TableCell>
</TableRow>
);
};

interface RowProps extends WithStyles<typeof styles> {
readonly category: string;
}

const styles = createStyles({
ProductCategoryRow: {
textAlign: 'center',
fontWeight: 'bold',
},
});

const _ProductCategoryRow = ({ category, classes }: RowProps) => (
<TableRow>
<TableCell colSpan={2} className={classes.ProductCategoryRow}>
{category}
</TableCell>
</TableRow>
);

const ProductCategoryRow = withStyles(styles)(_ProductCategoryRow);

type Props = {
readonly products: Product[];
readonly filterText: string;
readonly inStockOnly: boolean;
};

const ProductTable = ({ products, filterText, inStockOnly }: Props) => {
const rows: ReactElement[] = [];
let lastCategory: string | null = null;

products.forEach((product) => {
if (product.name.indexOf(filterText) === -1) {
return;
}
if (inStockOnly && !product.stocked) {
return;
}
if (product.category !== lastCategory) {
rows.push(
<ProductCategoryRow
category={product.category}
key={product.category}
/>,
);
}
rows.push(<ProductRow key={product.name} {...product} />);
lastCategory = product.category;
});

return (
<Table>
<TableHead>
<TableRow>
<TableCell>Name</TableCell>
<TableCell>Price</TableCell>
</TableRow>
</TableHead>
<TableBody>{rows}</TableBody>
</Table>
);
};

export default ProductTable;

Notice we also used MaterialUI's higher order component withStyles to inject our own styles into ProductCategoryRow. Customizing our own styles is a little bit tricky with a framework since the framework styles generally override our styles. MaterialUI recommends withStyles for custom styles here. It also has some guides on using withStyles with TypeScript since withStyles passes the styles down as a prop classes so we need to add that to our RowType.

Other Resources for Styling

React Native

What is React Native?

React Native allows for cross platform mobile development using a webdev framework we already know--React!

React Native allows you to build UIs independent of the platform. Usually when developing an app you have to develop an Android version (using Java/Kotlin) and iOS version (using Objective-C/Swift) separately. React Native takes care of this conversion for you.

Core React Native Components

Since React Native is really just React, many of the same concepts (useState, props, React Hooks, etc) still apply to React. However, instead of HTML we have Views. A view is the basic building block of UI in mobile development. Views can display images, hold text, handle user input, etc.

Some core React Native components are:

  • <View>: A container that supports layout with flexbox, style, some touch handling, and accessibility controls. Similar to a non-scrolling <div>.
  • <Text>: Displays, styles, and nests strings of text and even handles touch events. Similar to a <p>
  • <Image>: Displays images like <img>
  • <ScrollView>: A generic scrolling container than can hold nested components and views. Similar to a <div>.
  • <TextInput>: User text input field similar to <input type="text" />.
  • ... and you can also define your own custom components (and use those built by the community)!

How to start a React Native Project?

React Native uses Expo framework to develop, build, and iterate on iOS, Android and webapps. Expo provides a UI for you to view your changes and if you download the Expo app (Android, iOS) you can see those changes on your phone as well! After all, we're doing mobile development.

To start a React Native project run the following:

yarn add expo-cli
yarn expo init <project name>
yarn start

yarn expo init is similar to create-react-app in that it generates boilerplate code for you.

Demo

As part of the demo we built the simple TODO list app from assignment 4 in React Native! The code is here:

App.tsx
import React, { useState } from 'react';
import { StyleSheet, Text, View, TextInput, Button } from 'react-native';

export default function App(): React.ReactElement {
const [item, setItem] = useState<string>('');
const [items, setItems] = useState<string[]>([]);

const updateItems = (): void => {
setItems([...items, item]);
setItem('');
};

return (
<View style={styles.container}>
<TextInput
placeholder="Add an item"
style={styles.input}
value={item}
onChangeText={(text) => setItem(text)}
/>
<Button title="Add item" onPress={() => updateItems()} />
{items.map((i, index) => (
<Text key={index}> {i} </Text>
))}
</View>
);
}

const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},

input: {
borderWidth: 1,
width: 150,
},
});

Once you yarn start, you should be taken to Expo where you can view your changes on your browser. On the bottom left, there should also be a QR code. If you download the Expo app and scan the QR code with your phone camera (Android, iOS), it should take you to the Expo app where you can see your application in mobile form!

Learn more

This was a very cursory introduction to React Native. You can learn more by referring to React docs.

React Docs: https://reactnative.dev/docs/getting-started

Expo Docs: https://docs.expo.io/

- + \ No newline at end of file diff --git a/docs/2020fa/lecture2/index.html b/docs/2020fa/lecture2/index.html index 14257dcc1..0e0b6691f 100644 --- a/docs/2020fa/lecture2/index.html +++ b/docs/2020fa/lecture2/index.html @@ -5,7 +5,7 @@ Lecture 2 | Trends in Web Dev - + @@ -28,7 +28,7 @@ each song in the system should be. This is our first powerful use of TypeScript in web development: if data doesn't conform to this spec, we'll immediately know since TypeScript's compiler will give an error, saving us a lot of debug time and headaches.

app.get specifies that any GET requests sent to the endpoint /getSongs will send back the array of songs, which is initially the empty array [].

Lastly, app.listen starts the server on port 8080 asking it to listen for requests.

Let's test these endpoints by running yarn start.

App started! should be printed on the terminal showing that the port is up and running and listening for requests.

Use your web browser to navigate to localhost:8080/getSongs. You should see the value of songs, the empty array [], on the page.

You can terminate the running of the script using Ctrl + C.

Postman

Instead of always going to the endpoint in the browser, a robust way of testing our endpoints is to use Postman.

Postman is a software that allows you to simulate requests that could be sent by a user to your backend. It is useful for testing and ensuring that the behavior of your requests (including necessary headers) is what you expect.

Download Postman here.

Once you have Postman set up, make a request to the /getSongs endpoint by setting the request type as GET and the url as localhost:8080/getSongs. You should see [] in the response body.

POST Request

Usually when you want to send a POST request you also want to send information with it. Situationally, you want to do this using request bodies rather than query parameters.

Add the following to your index.ts file after your app.get call:

index.ts
app.post('/addSong', (req, res) => {
const song: songtype = { name: req.body.name, rating: req.body.rating };
console.log(song);
songs.push(song);
res.send(`Song ${req.body.name} added!`);
});

Previously, we may have considered using query parameters for sending data for the backend. There's nothing wrong with that; we could have used /addSong?name=Despacito&rating=5. However, this can lead to extremely long URLs, and limit us from sending more complicated data. That's where request bodies come in handy. We can instead send request data in JSON format to the backend, allowing us to use the data more easily and integrate it seamlessly with our backend (which happens to be in TypeScript, so we can easily deal with it).

This tells express to listen for POST requests at endpoint /addSong. req.body is a JS object, and we access its properties req.body.name and req.body.rating to add a new song to our array of songs. We also make sure that this is compatible with a songtype: after all, the rating of a song can't be a word, and the TypeScript compiler will yell at us if we mess this up!

However, we can't test request bodies quite as easily through the browser; we can check that this endpoint is working using Postman. Set the request type to POST and URL as localhost:8080/addSong. To send a request body, first go to Headers and add a new key Content-Type with value application/json. This says we are sending JSON input (essentially, an object or dictionary) in our request body. In the Body section, select the raw radio button and enter the following in the text field:

{
"name": "Despacito",
"rating": 5
}

We will be sending name with a value of "Despacito" and rating with a value of 5 in the request body.

Sending this request, you should see the corresponding song printed out to the console by the endpoint.

Now, let's create another POST endpoint to update a song's rating. This will also use a request body with just a name field, which should match the song we want to update.

index.ts
app.post('/updateRating', (req, res) => {
for (const song of songs) {
if (song.name === req.body.name) {
song.rating = req.body.rating;
}
}
res.send('Rating updated!');
console.log(songs);
});

DELETE Request

When creating APIs, we use the DELETE request method to quite simply delete a specific resource. This should be pretty straightforward: we simply take the name of the song to delete through the request body, and create a new version of the songs without the specified song. We then send text to the requester that it was deleted.

index.ts
app.delete('/removeSong', (req, res) => {
const newSongs = [];
for (let song of songs) {
if (song.name !== req.body.name) {
newSongs.push(song);
}
}
songs = newSongs;
res.send(`Song ${req.body.name} deleted!`);
});

And with that, we're done!

Intro to Databases and Firebase

The music streaming API we just made "works": we can add songs and then get them while running the Express server. But it has one fatal flaw: try stopping the server and then running it again. You'll see that all the music is gone! We need some kind of persistent storage for this data: throughโ€”you guessed itโ€”a database.

Why do we need a database for our backend?

  • Data stored within Node.js is per instance
  • Most applications require persistence
  • Data analysis
  • Performant data location
  • Offloading unneeded data from memory

MySQL + Relational Databases

  • Stores data in tables, utilizing rows and tables.
  • Is relational (think a tuple)
  • Has a schema

NoSQL and Firestore

We will focus on NoSQL

  • Many NoSQL implementations are schema-less or have a partial schema
  • Firestore is a cloud-hosted NoSQL database
  • Very flexible and can be used with most popular languages
  • Uses documents to store data
  • Efficient querying for data

SQL vs NoSQL

  • SQL databases have a predefined schema, whereas NoSQL databases can abide to any structure you want it to.
  • NoSQL databases are better suited for large sets of data, but not for complex queries.
  • SQL databases tend to be less expensive for smaller datasets, but also less flexible.
  • SQL has strong consistency whereas NoSQL has eventual consistency (i.e. there may be some delay in getting the response back)
  • SQL is vertically scalable (need more computing power on one machine to store more data) while NoSQL is horizontally scalable (can distribute storage and compute power on multiple machines)

What is Firebase?

  • Firebase is a Backend as a Service (BaaS) offered by Google
    • Allows you to store data
    • Host websites
    • Authentication
  • NoSQL DB
    • Not only SQL
    • Non relational

Why Firebase?

  • Real-time operations
  • Firebase Authentication
  • Built-in analytics
  • Also supports hosting/deployment
  • Integration with other Google services
  • Structure weโ€™re familiar with!
- + \ No newline at end of file diff --git a/docs/2020fa/lecture3/index.html b/docs/2020fa/lecture3/index.html index 75018e79d..d00b971c7 100644 --- a/docs/2020fa/lecture3/index.html +++ b/docs/2020fa/lecture3/index.html @@ -5,7 +5,7 @@ Lecture 3 | Trends in Web Dev - + @@ -37,7 +37,7 @@ that the code below does not care about what are the fields of a post, because Firestore doesn't require you to have a predefined set of fields. This gives you flexibility when writing your backend code.

index.ts
// in typescript import packages as modules
import admin from 'firebase-admin';
import express from 'express'; // also install type aliases for Request, Response
import bodyParser from 'body-parser';

const serviceAccount = require('./service-account.json');

admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: '[DATABASE_URL]',
});

const db = admin.firestore();

const app = express();
const port = 8080;
app.use(bodyParser.json());

// Define a type for our Post document stored in Firebase
type Post = {
content: string;
name: string;
};

// Add id field to our Post type
type PostWithID = Post & {
id: string;
};

app.get('/', (_, res) => res.send('Hello World!'));

const postsCollection = db.collection('posts');

// create a post
app.post('/post', async function (req, res) {
const post: Post = req.body;
const myDoc = postsCollection.doc();
await myDoc.set(post);
res.send(myDoc.id);
});

// read all posts
app.get('/post', async function (_, res) {
// we don't use the first request parameter
const allPostsDoc = await postsCollection.get();
const posts: PostWithID[] = [];
for (let doc of allPostsDoc.docs) {
let post: PostWithID = doc.data() as PostWithID;
post.id = doc.id;
posts.push(post);
}
res.send(posts);
});

// read posts by name
app.get('/post/:name', async function (req, res) {
const namePostsDoc = await postsCollection
.where('name', '==', req.params.name)
.get();
const posts: PostWithID[] = [];
for (let doc of namePostsDoc.docs) {
let post: PostWithID = doc.data() as PostWithID;
post.id = doc.id;
posts.push(post);
}
res.send(posts);
});

// sorted posts by name
app.get('/postsorted', async function (_, res) {
// we don't use the first request parameter
const sortedPosts = await postsCollection.orderBy('name', 'desc').get();
const posts: PostWithID[] = [];
for (let doc of sortedPosts.docs) {
let post: PostWithID = doc.data() as PostWithID;
post.id = doc.id;
posts.push(post);
}
res.send(posts);
});

// update a post
app.post('/post/:id', async function (req, res) {
const id: string = req.params.id;
const newPost = req.body;
await postsCollection.doc(id).update(newPost);
res.send('UPDATED');
});

// delete a post
app.delete('/post/:id', async function (req, res) {
const id: string = req.params.id;
await postsCollection.doc(id).delete();
res.send('DELETED');
});

app.listen(port, () => console.log(`Example app listening on port ${port}!`));
- + \ No newline at end of file diff --git a/docs/2020fa/lecture4/index.html b/docs/2020fa/lecture4/index.html index e40537762..8b963ed59 100644 --- a/docs/2020fa/lecture4/index.html +++ b/docs/2020fa/lecture4/index.html @@ -5,7 +5,7 @@ Lecture 4 | Trends in Web Dev - + @@ -57,7 +57,7 @@ The series is comprehensive and will teach you everything you want to know.

Demo Code

Need more examples? This code from the lecture's live coding demo rewrites some functions from the preassessment first using loops then with map, filter, and reduce.

index.ts
// getSqrts: takes in an array and returns an array with all the square roots
// of those numbers
// example: [1, 4, 9] => [1, 2, 3]
const getSqrts = (arr: number[]): number[] => {
const result: number[] = [];
for (const num of arr) {
result.push(Math.sqrt(num));
}
return result;
};
const getSqrtsMap = (arr: number[]): number[] => {
return arr.map(Math.sqrt);
};
// perfectSquares: takes in an array and returns an array with only the
// elements that are perfect squares
// example: [1, 2, 3] => [1]
const perfectSquares = (arr: number[]): number[] => {
const result: number[] = [];
for (const num of arr) {
if (Math.sqrt(num) % 1 === 0) {
result.push(num);
}
}
return result;
};
const isPerfectSquare = (num: number) => Math.sqrt(num) % 1 === 0;
const perfectSquaresFilter = (arr: number[]): number[] => {
return arr.filter(isPerfectSquare);
};
// mySum: takes in an array and returns the sum of the elements
// example: [1, 2, 3] => 6
const mySum = (arr: number[]): number => {
let sum = 0;
for (const num of arr) {
sum += num;
}
return sum;
};
const mySumReduce = (arr: number[]): number => {
return arr.reduce((acc: number, curr: number) => acc + curr);
};
// testing!
const input = [1, 2, 3];
console.log(getSqrts(input));
console.log(getSqrtsMap(input));
console.log(perfectSquares(input));
console.log(perfectSquaresFilter(input));
console.log(mySum(input));
console.log(mySumReduce(input));
- + \ No newline at end of file diff --git a/docs/2020fa/lecture5/index.html b/docs/2020fa/lecture5/index.html index f40b51ca2..e0bca5427 100644 --- a/docs/2020fa/lecture5/index.html +++ b/docs/2020fa/lecture5/index.html @@ -5,7 +5,7 @@ Lecture 5 | Trends in Web Dev - + @@ -22,7 +22,7 @@ to show how you can achieve this in different ways.

import React from 'react';
// Suppose you have a ContactCard component defined there.
import ContactCard from './ContactCard';

const data = [
{ name: 'Sam1', email: 'foo@bar.com' },
{ name: 'Sam2', email: 'bar@baz.com' },
{ name: 'Sam3', email: 'baz@foo.com' },
];

const ListBySimpleMap = () => (
<div>
{data.map((contact) => (
<ContactCard
key={contact.name}
name={contact.name}
email={contact.email}
/>
))}
</div>
);

const ListBySimpleMapWithObjectDestructing = () => (
<div>
{data.map(({ name, email }) => (
<ContactCard key={name} name={name} email={email} />
))}
</div>
);

const ListBySimpleMapWithSpread = () => (
<div>
{data.map((contact) => (
<ContactCard key={contact.name} {...contact} />
))}
</div>
);

Note that we always need a key prop. Without this, React will give you warnings in the console. React needs a unique key for each item in the list to help it avoid rerendering everything when only one item in the list changes.

- + \ No newline at end of file diff --git a/docs/2020fa/lecture6/index.html b/docs/2020fa/lecture6/index.html index a96a5436b..0dde1ddde 100644 --- a/docs/2020fa/lecture6/index.html +++ b/docs/2020fa/lecture6/index.html @@ -5,7 +5,7 @@ Lecture 6 | Trends in Web Dev - + @@ -34,7 +34,7 @@ based on conditions a lot easier. In this small example, we went from five lines of code in the component to just one!

Composition vs. Inheritance

Composition and inheritance are two programming techniques for defining how classes relate to objects. (Think of classes as the blueprint for a house and objects the actual houses created from that blueprint)

Composition

Composition defines a class as the sum of its individual parts. This is a "has-a" relationship (e.g. a car has a steering wheel, has a window, etc). In Java (and other object oriented languages), these components are represented as instance variables.

Inheritance

Inheritance derives one class from another. If class A is the parent of class B and C, B and C inherit the properties/functions of A. This is a "is-a" relationship (e.g. car is a vehicle, circle is a shape.)

React uses Composition

โ€œReact has a powerful composition model, and we recommend using composition instead of inheritance to reuse code between components.โ€ -- React Docs

Containment

Components may not know their children ahead of time.

Children are the components you put within another component:

<ComponentA>{/* anything here is a child of Component A */}</ComponentA>

Use the children prop to pass in children components.

Container.tsx
import React, { ReactNode } from 'react';
type Props = { readonly children: ReactNode };
const Container = (props: Props) => (
<div className="Border">{props.children}</div>
);
const App = () => (
<div className="App">
<Container>
<p>Hello!</p>
<p>Bye!</p>
</Container>
</div>
);

props.children will have the paragraph elements.

We didn't actually get to this live demo, adapted from this tutorial in the React docs, during lecture but it is very simple if you want to try it out yourself. We also show how to import styles.

Container.tsx
import React, { ReactNode } from 'react';
import './Container.css'; // this is how we import styles

type Props = { readonly children: ReactNode };

const Container = (props: Props) => (
<div className="Border">{props.children}</div>
);

export default Container;
Container.css
.Border {
border: 4px solid black;
background-color: azure;
}

Less common but you also may want multiple "holes" in your component (for example, a left and right child):

SplitPane.tsx
import React, { ReactNode } from 'react';
import './SplitPane.css';

type Props = { readonly left: ReactNode; readonly right: ReactNode };

const SplitPane = (props: Props) => (
<div>
<div className="LeftPane">{props.left}</div>
<div className="RightPane">{props.right}</div>
</div>
);

export default SplitPane;
SplitPane.css
/* these colors are ugly I know */
.LeftPane {
float: left;
width: 50%;
background-color: red;
}

.RightPane {
float: right;
width: 50%;
background-color: aquamarine;
}
App.jsx
import React from 'react';
import SplitPane from './SplitPane';
import Container from './Container';

const App = () => {
return (
<div className="App">
<Container>
<p>Hello, world!</p>
</Container>
<SplitPane
left={<div>I'm on the left!</div>}
right={<div>I'm on the right!</div>}
/>
</div>
);
};

export default App;

Lifting State Up

This section was a live demo, adapted from this tutorial in the React docs.

App.tsx
import React, { useState } from 'react';
import './App.css';
import FahrenheitInput from './FahrenheitInput';
import CelsiusInput from './CelsiusInput';

function App() {
const [temperature, setTemperature] = useState(-40);

return (
<div className="App">
<label>Fahrenheit:</label>
<FahrenheitInput
temperature={temperature}
callback={(temp) => setTemperature(temp)}
/>
<br />
<label>Celcius:</label>
<CelsiusInput
temperature={temperature}
callback={(temp) => setTemperature(temp)}
/>
<br />
{temperature >= 100 ? (
<span>Water would boil here!</span>
) : (
<span>Water would not boil here!</span>
)}
<br />
<span>Water would {temperature >= 0 && 'not'} freeze here!</span>
</div>
);
}

export default App;
CelsiusInput.tsx
import React, { ChangeEvent } from 'react';

type Props = {
readonly temperature: number;
readonly callback: (temperature: number) => void;
};

const CelsiusInput = ({ temperature, callback }: Props) => {
const handlechange = (e: ChangeEvent<HTMLInputElement>) =>
callback(parseInt(e.target.value) || 0);

return <input value={temperature} onChange={handlechange} />;
};

export default CelsiusInput;
FahrenheitInput.tsx
import React from 'react';

type Props = {
readonly temperature: number;
readonly callback: (temperature: number) => void;
};

const FahrenheitInput = ({ temperature, callback }: Props) => {
const handlechange = (e: ChangeEvent<HTMLInputElement>) =>
callback((((parseInt(e.target.value) || 0) - 32) * 5) / 9);

return <input value={(temperature * 9) / 5 + 32} onChange={handlechange} />;
};

export default FahrenheitInput;

Check lecture video for a more detailed explanation.

- + \ No newline at end of file diff --git a/docs/2020fa/lecture7/index.html b/docs/2020fa/lecture7/index.html index e1da80fb0..a1b9f6ed0 100644 --- a/docs/2020fa/lecture7/index.html +++ b/docs/2020fa/lecture7/index.html @@ -5,7 +5,7 @@ Lecture 7 | Trends in Web Dev - + @@ -65,7 +65,7 @@ parent component
  • Connect component to the backend (more on this in Lecture 8!)
  • Filterable Product Table Example

    This section contains the code from the live demo presented during class. Watch the lecture video linked at the top for an explanation of the code, intended to teach how to think in the React development paradigm.

    App.tsx (root component)
    import React from 'react';
    import FilterableProductTable from './FilterableProductTable';

    const PRODUCTS = [
    {
    category: 'Sporting Goods',
    price: '$49.99',
    stocked: true,
    name: 'Football',
    },
    {
    category: 'Sporting Goods',
    price: '$9.99',
    stocked: true,
    name: 'Baseball',
    },
    {
    category: 'Sporting Goods',
    price: '$29.99',
    stocked: false,
    name: 'Basketball',
    },
    {
    category: 'Electronics',
    price: '$99.99',
    stocked: true,
    name: 'iPod Touch',
    },
    {
    category: 'Electronics',
    price: '$399.99',
    stocked: false,
    name: 'iPhone 5',
    },
    {
    category: 'Electronics',
    price: '$199.99',
    stocked: true,
    name: 'Nexus 7',
    },
    ];

    const App = () => (
    <div className="App">
    <FilterableProductTable products={PRODUCTS} />
    </div>
    );

    export default App;
    FilterableProductTable.tsx
    import React, { ChangeEvent, useState } from 'react';
    import ProductTable, { Product } from './Starter';

    type TableProps = {
    readonly products: Product[];
    };

    type SearchProps = {
    readonly filterText: string;
    readonly inStockOnly: boolean;
    readonly handleFilterTextChange: (e: ChangeEvent<HTMLInputElement>) => void;
    readonly handleCheckBoxChange: (e: ChangeEvent<HTMLInputElement>) => void;
    };

    const SearchBar = ({
    filterText,
    inStockOnly,
    handleFilterTextChange,
    handleCheckBoxChange,
    }: SearchProps) => (
    <form>
    <input
    type="text"
    placeholder="Search..."
    value={filterText}
    onChange={handleFilterTextChange}
    />
    <p>
    <input
    type="checkbox"
    checked={inStockOnly}
    onChange={handleCheckBoxChange}
    />{' '}
    Only show products in stock
    </p>
    </form>
    );

    const FilterableProductTable = ({ products }: TableProps) => {
    const [filterText, setFilterText] = useState('');
    const [inStockOnly, setInStockOnly] = useState(false);

    const handleFilterTextChange = (e: ChangeEvent<HTMLInputElement>) =>
    setFilterText(e.target.value);
    const handleCheckBoxChange = (e: ChangeEvent<HTMLInputElement>) =>
    setInStockOnly(e.target.checked);

    return (
    <div>
    <SearchBar
    filterText={filterText} // states passed as prop to SearchBar
    inStockOnly={inStockOnly} // states passed as prop to SearchBar
    handleFilterTextChange={handleFilterTextChange} // pass down callbacks to update search state
    handleCheckBoxChange={handleCheckBoxChange}
    />
    <ProductTable
    products={products} // JSON API model
    filterText={filterText} // states passed as prop to SearchBar
    inStockOnly={inStockOnly} // states passed as prop to SearchBar
    />
    </div>
    );
    };

    export default FilterableProductTable;
    Starter.tsx
    // Contains all the base components (we can put multiple components in a jsx file
    // for convenience, though this is not usually good practice).
    import React, { ReactElement } from 'react';
    // These components will be starter code because they are most self-explanatory
    // and purely presentational. We will go over this code briefly in lecture.
    // Students encouraged to read this on their own time.

    // Export since this is used in FilterableProductTable as well
    export type Product = {
    readonly category: string;
    readonly price: string;
    readonly stocked: boolean;
    readonly name: string;
    };

    const ProductRow = (product: Product) => {
    const name = product.stocked ? (
    product.name
    ) : (
    <span style={{ color: 'red' }}>{product.name}</span>
    );
    return (
    <tr>
    <td>{name}</td>
    <td>{product.price}</td>
    </tr>
    );
    };

    type RowProps = {
    readonly category: string;
    };

    const ProductCategoryRow = ({ category }: RowProps) => (
    <tr>
    <th colSpan={2}>{category}</th>
    </tr>
    );

    type Props = {
    readonly products: Product[];
    readonly filterText: string;
    readonly inStockOnly: boolean;
    };

    const ProductTable = ({ products, filterText, inStockOnly }: Props) => {
    const rows: ReactElement[] = [];
    let lastCategory: string | null = null;

    products.forEach((product) => {
    if (product.name.indexOf(filterText) === -1) {
    return;
    }
    if (inStockOnly && !product.stocked) {
    return;
    }
    if (product.category !== lastCategory) {
    rows.push(
    <ProductCategoryRow
    category={product.category}
    key={product.category}
    />,
    );
    }
    rows.push(<ProductRow key={product.name} {...product} />);
    lastCategory = product.category;
    });

    return (
    <table>
    <thead>
    <tr>
    <th>Name</th>
    <th>Price</th>
    </tr>
    </thead>
    <tbody>{rows}</tbody>
    </table>
    );
    };

    // Here we can export all these components at once!
    // Notice also the name of the file does not match any single component name.
    // export { ProductRow, ProductCategoryRow, ProductTable };
    export default ProductTable; // but we only need ProductTable here (in FilterableProductTable.jsx)
    - + \ No newline at end of file diff --git a/docs/2020fa/lecture8/index.html b/docs/2020fa/lecture8/index.html index e5f8a2951..bae0a6ed1 100644 --- a/docs/2020fa/lecture8/index.html +++ b/docs/2020fa/lecture8/index.html @@ -5,7 +5,7 @@ Lecture 8 | Trends in Web Dev - + @@ -55,7 +55,7 @@ you have.

    You update your data by calling an endpoint within useEffect and setting your data to the response that you get back.

    You can call endpoints using fetch() or axios and handle the responses asynchronously.

    Demo Code

    Backend

    index.ts (backend)
    import admin from 'firebase-admin';
    import express from 'express';

    const serviceAccount = require('./service-account.json');

    admin.initializeApp({
    credential: admin.credential.cert(serviceAccount),
    databaseURL: '[DATABASE_URL]',
    });

    const db = admin.firestore();

    const app = express();
    const port = 8080;
    app.use(express.json());

    type Song = {
    name: string;
    artist: string;
    rating: number;
    };

    type SongWithID = Song & {
    id: string;
    };

    const songsCollection = db.collection('songs');

    app.get('/getSongs', async (req, res) => {
    const songs = await songsCollection.get();
    res.json(
    songs.docs.map((doc): SongWithID => {
    const song = doc.data() as Song;
    return { ...song, id: doc.id };
    }),
    );
    });

    app.post('/createSong', async (req, res) => {
    const newSong: Song = req.body;
    const addedSong = await songsCollection.add(newSong);
    res.send(addedSong.id);
    });

    app.post('/updateRating', async (req, res) => {
    const { id, rating } = req.query;
    await songsCollection.doc(id as string).update({ rating });
    res.send('Song rating updated!');
    });

    app.listen(port, () => console.log(`Example app listening on port ${port}!`));
    SongList.tsx (frontend)
    import React, { useState, useEffect } from 'react';
    import { Song } from './Song';
    import { SongAdder } from './SongAdder';
    import axios from 'axios';

    type Song = {
    readonly name: string;
    readonly artist: string;
    readonly rating: number;
    };

    type SongWithID = Song & {
    readonly id: string;
    };

    export const SongList = () => {
    const [songs, setSongs] = useState<readonly SongWithID[]>([]);

    // GET request using fetch
    const fetchSongs = () => {
    fetch('/getSongs')
    .then((res) => res.json())
    .then((json) => setSongs(json));
    };

    // GET request using axios and async/await
    // const fetchSongs = async () => {
    // const res = await axios.get<readonly SongWithID[]>('/getSongs');
    // setSongs(res.data)
    // }

    useEffect(() => fetchSongs(), []);

    // POST request using fetch
    const addSong = (name: string, artist: string, rating: number) => {
    const body: Song = { name, artist, rating };
    fetch('/createSong', {
    method: 'POST',
    headers: {
    'Content-Type': 'application/json',
    },
    body: JSON.stringify(body),
    })
    .then((res) => res.text())
    .then((id) => setSongs([...songs, { ...body, id }]));
    };

    // POST request using axios and async/await
    // const addSong = async (name: string, artist: string, rating: number) => {
    // const body: Song = { name, artist, rating };
    // const { data: id } = await axios.post<string>('/createSong', body);
    // setSongs([...songs, { name, artist, rating, id }])
    // }

    // POST request (update) using fetch
    const updateRating = (id: string, rating: number) => {
    fetch(`/updateRating?id=${id}&rating=${rating}`, {
    method: 'POST',
    }).then((res) =>
    setSongs(
    songs.map((song) => (song.id === id ? { ...song, rating } : song)),
    ),
    );
    };

    // POST request (update) using axios and async/await
    // const updateRating = async (id: string, rating: number) => {
    // await axios.post(`/updateRating?id=${id}&rating=${rating}`);
    // setSongs(
    // songs.map((song) => (song.id === id ? { ...song, rating } : song))
    // );
    // };

    return (
    <div>
    {songs.map((song) => (
    <div>
    {' '}
    <Song key={song.id} {...song} updateRating={updateRating} /> <br />{' '}
    </div>
    ))}
    <SongAdder callback={addSong} />
    </div>
    );
    };
    SongAdder.tsx (frontend)
    import React, { useState } from 'react';

    type Props = {
    readonly callback: (name: string, artist: string, rating: number) => void;
    };

    export const SongAdder = ({ callback }: Props) => {
    const [name, setName] = useState('');
    const [artist, setArtist] = useState('');
    const [rating, setRating] = useState(0);

    return (
    <div>
    <h3> Add a new song! </h3>
    <input
    placeholder="Song name"
    onChange={(e) => setName(e.target.value)}
    /> <br />
    <input
    placeholder="Artist name"
    onChange={(e) => setArtist(e.target.value)}
    />{' '}
    <br />
    <input
    placeholder="Rating"
    onChange={(e) => setRating(parseInt(e.target.value))}
    /> <br />
    <button onClick={(e) => callback(name, artist, rating)}> Add song</button>
    </div>
    );
    };
    Song.tsx (frontend)
    import React, { useState } from 'react';

    type Props = {
    readonly id: string;
    readonly name: string;
    readonly artist: string;
    readonly rating: number;
    readonly updateRating: (newId: string, newRating: number) => void;
    };

    export const Song = ({ id, name, artist, rating, updateRating }: Props) => {
    const [newRating, setNewRating] = useState(rating);

    return (
    <div>
    <div>
    {' '}
    The song {name} by {artist} currently has a rating of {rating}/5{' '}
    </div>
    <input
    placeholder="New rating"
    onChange={(e) => setNewRating(parseInt(e.target.value))}
    />
    <button onClick={(e) => updateRating(id, newRating)}>
    {' '}
    Update Rating{' '}
    </button>
    </div>
    );
    };
    - + \ No newline at end of file diff --git a/docs/2020fa/lecture9/index.html b/docs/2020fa/lecture9/index.html index cd16b4dad..c5309c8f4 100644 --- a/docs/2020fa/lecture9/index.html +++ b/docs/2020fa/lecture9/index.html @@ -5,7 +5,7 @@ Lecture 9 | Trends in Web Dev - + @@ -16,7 +16,7 @@ Moreover, many people reuse their passwords for some if not all other, so you could also compromise something like someone's banking credentials or emails.

    While it is safer to store hashed passwords, it is very easy to do it incorrectly, which is why we teach firebase authentication!

    2. Input Sanitization

    Input sanitization 'cleanses' inputs so that they can not be used in unintended ways.

    For example, it may seem reasonable to add every comment on a blog into a database entry like this: โ€œINSERT INTO 'comments' (comment) VALUES 'โ€ + user_input + โ€œ';โ€, but it is actually incredibly unsafe if user_input were something like '; DROP TABLE comments;.

    Luckily for us, React DOM escapes values embedded in JSX before rendering them by default, preventing injection attacks.

    3. Don't Rely on Client Side Verification

    In general you cannot assume your endpoint will only be used by your frontend.

    Recall that we used Postman at the beginning of the semester to test Express endpoints. As such, it is always important to verify your inputs and authenticate your requests!

    4. Separation of Privileges

    Going hand-in-hand with the last point, when a request is made to your backend, make sure that the source of your request has the sufficient permissions to perform whatever action is being asked!

    For example, if the OfficeHours app recieved a request to edit the times to someone's office hours, the backend would check that this person is indeed the TA who owns that OH and not a student.

    Let's hack our app!

    In lecture, we showed how to hack our very simple application by sending in bad unauthenticated input data. This caused our frontend to break a little bit. Check the lecture video for details!

    We fixed the backend by using Firebase Admin's verifyIdToken function to check whether the user had logged in or not.

    Backend

    On the backend, we had the post endpoints check the idtoken in the request headers.

    backend/index.ts
    // setup, GET route
    app.post('/createSong', async (req, res) => {
    admin
    .auth()
    .verifyIdToken(req.headers.idtoken as string)
    .then(async () => {
    const newSong = req.body;
    const addedSong = await songsCollection.add(newSong);
    res.send(addedSong.id);
    })
    .catch(() => {
    console.log('auth error');
    });
    });

    app.post('/updateRating', async (req, res) => {
    admin
    .auth()
    .verifyIdToken(req.headers.idtoken as string)
    .then(async () => {
    const id = req.query.id as string;
    const rating = req.query.rating;
    await songsCollection.doc(id).update({ rating });
    res.send('Song rating updated!');
    })
    .catch(() => {
    console.log('auth error');
    });
    });

    We call admin.auth().verifyIdToken(req.headers.idtoken as string) and only then do we allow the input to be saved. Otherwise, we log the error.

    Frontend

    Now when we send the request back, we need to send the idtoken with the request header. So in the addSong and updateRating methods that called the POST endpoints above, we send call firebase.auth().currentUser?.getIdToken(true) to get the user's idtoken. Then we pass it in as a field to headers in the fetch. However, if the currentUser is undefined for some reason (what the ? catches), then we console that the user isn't authenticated. Everything else should be familiar from last lecture.

    frontend/src/SongList.tsx
    const addSong = (name: string, artist: string, rating: number) => {
    firebase
    .auth()
    .currentUser?.getIdToken(true)
    .then((idtoken) => {
    fetch('/createSong', {
    method: 'POST',
    headers: {
    'Content-Type': 'application/json',
    idtoken,
    },
    body: JSON.stringify({ name, artist, rating }),
    })
    .then((res) => res.text())
    .then((id) => setSongs([...songs, { name, artist, rating, id }]));
    })
    .catch(() => {
    console.log('not authenticated');
    });
    };

    const updateRating = (id: string, rating: number) => {
    firebase
    .auth()
    .currentUser?.getIdToken(true)
    .then((idtoken) => {
    fetch(`/updateRating?id=${id}&rating=${rating}`, {
    method: 'POST',
    headers: {
    idtoken,
    },
    }).then(() =>
    setSongs(
    songs.map((song) =>
    song.id === id
    ? { name: song.name, artist: song.artist, rating, id }
    : song,
    ),
    ),
    );
    })
    .catch(() => {
    console.log('not authenticated');
    });
    };

    Security Best Practices

    There is a lot more we could've done in terms of security here.

    1. We should send back error messages to the user about why requests are failing instead of just doing console.log() since the user won't see that. This would be bad user experience since they don't have feedback about why things aren't working.
    2. We should also do input validation on the backend to ensure we are getting our expected inputs. What if we aren't passing an object that looks like:
    {
    "artist": string
    "title": string
    "rating": number [in 1-5]
    }

    Deployment

    To deploy your web application means to put it on a Web server so others can access it via the internet. We will deploy both our frontend and backend on Heroku. There are a variety of services you can use for deployment including Firebase, Amazon AWS, Microsoft Azure, etc, but we decided to show you Heroku deployment since it is easier and requires less setup.

    We will be taking our songs app we have been building throughout the course and deploying it to a remote server. To deploy your own app (for final project for example), you can follow the same steps usually.

    Deployment Setup

    Backend

    index.ts

    To deploy to Heroku we need some additional setup. We will be serving our frontend via our backend so express should grab files from the frontend/build folder. In the frontend folder, when you run yarn build, you will get a build directory containing all the optimized, bundled frontend React code ready to be pushed to a remote server. We will include the line app.use(express.static(path.join(__dirname, '../frontend/build'))); to do this. (Note: this requires us to import path from 'path';)

    We will also be calling upon our express app to use CORS to allow cross origin requests. If your backend requests are being blocked be of some CORS cross origin policy issue you probably forgot to include the app.use(cors()); line. (Note: this requires us to import cors from 'cors';)

    The beginning of your backend/index.ts should look like the below. Note the CORS line (line 12) and express static serving line (line 13) we described above and the relevant import statements.

    backend/index.ts
    import express from 'express';
    import admin from 'firebase-admin';
    import cors from 'cors';
    import path from 'path';

    const serviceAccount = require('./serviceAccount.json');

    admin.initializeApp({
    credential: admin.credential.cert(serviceAccount),
    databaseURL: '', // add dbURL
    });

    const app = express();
    app.use(cors());
    app.use(express.static(path.join(__dirname, '../frontend/build')));
    app.use(express.json());
    const db = admin.firestore();

    const songsCollection = db.collection('songs');

    app.get('/getSongs', async (req, res) => {
    const songs = await songsCollection.get();
    res.json(songs.docs.map((song) => ({ ...song.data(), id: song.id })));
    });

    // other routes...
    app.listen(process.env.PORT || 8080, () => console.log('backend started'));
    note

    Notice we are having app.listen listen for either port 8080 or process.env.PORT. Previously, we hardcoded 8080 for this parameter. process.env.PORT will be defined by Heroku and we want the app to listen for requests on that port in the deployed site.

    backend/package.json

    Our backend/package.json will look like this since we need to compile the TypeScript to JavaScript first. The build step calls tsc the TypeScript compiler to compile the index.ts to index.js. You should see a new index.js file after you run yarn build.

    note

    We are using the same tsconfig.json as found in lecture2.

    backend/package.json
    {
    "name": "backend",
    "version": "1.0.0",
    "main": "index.js",
    "scripts": {
    "build": "tsc -p tsconfig.json",
    "start": "node index.js"
    },
    "license": "MIT",
    "dependencies": {
    "cors": "^2.8.5",
    "express": "^4.17.1",
    "firebase-admin": "^9.4.1"
    },
    "devDependencies": {
    "heroku": "^7.47.3",
    "ts-node": "^9.0.0",
    "typescript": "^4.1.2",
    "@types/cors": "^2.8.8",
    "@types/express": "^4.17.9",
    "@types/node": "^14.14.10"
    }
    }

    Frontend

    No further setup required in our frontend folder.

    Root

    package.json

    Our root package.json will look like the following. We are using yarn workspaces as described above and the heroku-postbuild script will build both workspaces to prep them for push to remote server. (We discussed yarn workspaces run build above) heroku-postbuild is a special command recognized by Heroku to allow you to customize the build process.

    /package.json
    {
    "name": "songs-app",
    "private": true,
    "scripts": {
    "heroku-postbuild": "yarn workspaces run build"
    },
    "workspaces": ["backend", "frontend"],
    "version": "1.0.0",
    "main": "index.js",
    "license": "MIT",
    "devDependencies": {
    "heroku": "^7.47.3"
    }
    }
    note

    The root package.json can have dependencies and yarn will install them with yarn install just like everything else. You will just need to run yarn add -W <pkg_name> since just using yarn add will show a warning message asking if you are sure about adding a dependency to the root. We add Heroku in this case to help with deployment!

    Read more about heroku-postbuild here.

    Procfile

    We will also have Procfile that tells Heroku what script to run to start the application. In this case we will be using the backend node index.js to start the backend listening for requests and serving the frontend assets from the frontend/build folder.

    Procfile
    web: yarn workspace backend start

    Read more about the Heroku Procfile here.

    Deploy time

    We will now show the series of commands to deploy to Heroku. We will be using Heroku Git to deploy.

    yarn add heroku
    git init
    git add .
    git commit -m "COMMIT MESSAGE"
    yarn heroku login
    yarn heroku create <optional project name>
    git push heroku master
    (optional) yarn heroku open

    Visiting the URL should take you to the same application you had locally. Now you can share that link with your friends so they can visit your website too! Take a look at our deployed site here: https://webdev-demo-fa20.herokuapp.com/ (If you have issues deploying feel free to submit this link for the attendance question, but please do come to office hours first!)

    note

    If you run into issues that the app isn't authorized to use authentication, go to your Firebase project on firebase.google.com > Go to Console > [your project] > Authentication [on sidebar] > Sign-in Method > Authorized domains > Add Domain and enter the URL of your deployed site.

    info

    In your final submission of your final project, we want you to follow these steps to deploy your app to Heroku and place the link in the README.md. If you prefer to deploy on another platform feel free but we do not guarantee we know how to debug any issues that come up.

    - + \ No newline at end of file diff --git a/docs/2020fa/setup-editor/index.html b/docs/2020fa/setup-editor/index.html index 123c9b423..406ccee33 100644 --- a/docs/2020fa/setup-editor/index.html +++ b/docs/2020fa/setup-editor/index.html @@ -5,7 +5,7 @@ Setup your editor | Trends in Web Dev - + @@ -13,7 +13,7 @@
    Version: 2020fa

    Setup your editor

    For convenience, we assume you will use VSCode. If you are using WebStorm and Atom, you are likely to find some extensions that provide similar functionalities.

    To install extensions in VS Code, navigate to the left-hand sidebar, and click the building blocks icon at the bottom. This should take you to the Extensions marketplace.

    ESLint

    Installing ESLint in VSCode will give you real-time linter feedback in any JavaScript code you write, allowing you to quickly pinpoint many problems and have readable, proper formatting.

    Once installed, add these lines to your VSCode Settings (refer to this link if you need help getting there or alternatively open the command palette in VSCode with CMD/CTRL + SHIFT + P and search settings.json):

      // Other settings ...
    "eslint.alwaysShowStatus": true,
    "eslint.autoFixOnSave": true,
    "eslint.enable": true,
    "eslint.packageManager": "yarn"

    Bracket Pair Colorizer

    Highlights matching brackets to make code easier to read.

    npm

    This will be useful later when inspecting package.json files.

    - + \ No newline at end of file diff --git a/docs/2020fa/setup-environment/index.html b/docs/2020fa/setup-environment/index.html index e5c03f4f5..4ecc24b29 100644 --- a/docs/2020fa/setup-environment/index.html +++ b/docs/2020fa/setup-environment/index.html @@ -5,13 +5,13 @@ Setup your development environment | Trends in Web Dev - +
    Version: 2020fa

    Setup your development environment

    Install Node.js

    Go to this website and follow the instructions.

    For consistency, we will use Node LTS (current Node 12).

    Install Yarn

    For convenience, we assume you will use Yarn instead of npm.

    Go to this website and follow the instructions.

    - + \ No newline at end of file diff --git a/docs/2020sp/lecture1/index.html b/docs/2020sp/lecture1/index.html index 69a66973b..888165a54 100644 --- a/docs/2020sp/lecture1/index.html +++ b/docs/2020sp/lecture1/index.html @@ -5,7 +5,7 @@ Lecture 1 | Trends in Web Dev - + @@ -17,7 +17,7 @@ PATH is a path on the server. HANDLER is the function executed when the route is matched. The following code sends โ€œhello worldโ€ as a result of a GET request to โ€˜/' endpoint.

    const express = require('express');
    const app = express();

    // respond with "hello world" when a GET request is made to the homepage
    app.get('/', function (req, res) {
    res.send('hello world');
    });

    Custom URLs

    We can respond differently for requests to different URLs. For example, if we wanted โ€˜/home' to respond with โ€œWelcome Home!โ€ we could add a second route.

    app.get('/home', function (req, res) {
    res.send('Welcome Home!');
    });

    Both of these blocks of code respond to GET requests, because we are using Express's app.get() function. We tell express what route we want to trigger the response and give it a function that should be run to respond.

    https://expressjs.com/en/starter/basic-routing.html

    POST methods

    POST method is generally used to submit data to an endpoint.

    The following uses Express's app.post() method to send a POST request to โ€˜/' and responds with โ€˜Got a POST request'. Notice that app.post() has a second argument that is a function with two parameters, req and res. Usually, when you call POST you want to send data with the request. You would send that data as the req parameter.

    app.post('/', function (req, res) {
    res.send('Got a POST request');
    });

    Regular Expressions

    You can match patterns in text rather than specific characters. For example, what if you want to return the same page for all URLs of /users/[SOME NUMBER]? We can use regular expressions so that the route will be used for any value. We can use the route string /users/\d+ to match any number. Regular expressions are outside the scope of this class, but you may find more information in the references below.

    Parameterized Routes

    Take a look at this route, paying special attention to the : characters. Those denote parameters in the route.

    app.get('/users/:userId/books/:bookId', function (req, res) {
    res.send(req.params);
    });

    For example, if you navigate to the page /users/34/books/12973, you would now be able to use those IDs in your code. req.params.userId would now equal 34 and req.params.bookId would now equal 12973. This allows you to respond differently depending on IDs passed to you by the front end.

    In the following code snippet we use app.get() to query a messages endpoint and we want to get a specific message. We call this query parameter messageId and can use it in the function we pass to app.get() to return that message.

    const messages = {...}
    app.get('/messages/:messageId', (req, res) => {
    return res.send(messages[req.params.messageId]);
    });

    References

    Don't know where to start? Check out Express's official getting started page. The rest of the pages have great content and will help you along your journey.

    For a more in-depth exploration of this topic, check out this tutorial.

    This is a useful tool to test your Express regular expressions to make sure that the routes match what you expect.

    For an interactive way to learn regular expressions, consider this site.

    - + \ No newline at end of file diff --git a/docs/2020sp/lecture10/index.html b/docs/2020sp/lecture10/index.html index 9a682d6fa..66c1139b3 100644 --- a/docs/2020sp/lecture10/index.html +++ b/docs/2020sp/lecture10/index.html @@ -5,13 +5,13 @@ Lecture 10 | Trends in Web Dev - +
    Version: 2020sp

    Lecture 10

    Final Project extended to May 9th 7:59pm EDT (late deadline May 12 7:59pm EDT slip days permitting)

    Feedback Form

    Lecture Video

    Lecture Slides

    React Native

    What is React Native?

    React Native allows for cross platform mobile development using a webdev framework we already know--React!

    React Native allows you to build UIs independent of the platform. Usually when developing an app you have to develop an Android version (using Java/Kotlin) and iOS version (using Objective-C/Swift) separately. React Native takes care of this conversion for you.

    Core React Native Components

    Since React Native is really just React, many of the same concepts (useState, props, React Hooks, etc) still apply to React. However, instead of HTML we have Views. A view is the basic building block of UI in mobile development. Views can display images, hold text, handle user input, etc.

    Some core React Native components are:

    • <View>: A container that supports layout with flexbox, style, some touch handling, and accessibility controls. Similar to a non-scrolling <div>.
    • <Text>: Displays, styles, and nests strings of text and even handles touch events. Similar to a <p>
    • <Image>: Displays images like <img>
    • <ScrollView>: A generic scrolling container than can hold nested components and views. Similar to a <div>.
    • <TextInput>: User text input field similar to <input type="text" />.
    • ... and you can also define your own custom components (and use those built by the community)!

    How to start a React Native Project?

    React Native uses Expo framework to develop, build, and iterate on iOS, Android and webapps. Expo provides a UI for you to view your changes and if you download the Expo app (Android, iOS) you can see those changes on your phone as well! After all, we're doing mobile development.

    To start a React Native project run the following:

    yarn global add expo-cli
    expo init <project name>
    yarn start

    expo init is similar to create-react-app in that it generates boilerplate code for you.

    Demo

    As part of the demo we built the simple TODO list app from assignment 4 in React Native! The code is here:

    App.tsx
    import React, { useState } from 'react';
    import { StyleSheet, Text, View, TextInput, Button } from 'react-native';

    export default function App(): React.ReactElement {
    const [item, setItem] = useState<string>('');
    const [items, setItems] = useState<string[]>([]);

    const updateItems = (): void => {
    setItems([...items, item]);
    setItem('');
    };

    return (
    <View style={styles.container}>
    <TextInput
    placeholder="Add an item"
    style={styles.input}
    value={item}
    onChangeText={(text) => setItem(text)}
    />
    <Button title="Add item" onPress={() => updateItems()} />
    {items.map((i, index) => (
    <Text key={index}> {i} </Text>
    ))}
    </View>
    );
    }

    const styles = StyleSheet.create({
    container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
    },

    input: {
    borderWidth: 1,
    width: 150,
    },
    });

    Once you yarn start, you should be taken to Expo where you can view your changes on your browser. On the bottom left, there should also be a QR code. If you download the Expo app and scan the QR code with your phone camera (Android, iOS), it should take you to the Expo app where you can see your application in mobile form!

    Learn more

    This was a very cursory introduction to React Native. You can learn more by referring to React docs.

    React Docs: https://reactnative.dev/docs/getting-started

    Expo Docs: https://docs.expo.io/

    - + \ No newline at end of file diff --git a/docs/2020sp/lecture2/index.html b/docs/2020sp/lecture2/index.html index 54c69ee3f..bbc167cdd 100644 --- a/docs/2020sp/lecture2/index.html +++ b/docs/2020sp/lecture2/index.html @@ -5,13 +5,13 @@ Lecture 2 | Trends in Web Dev - +
    Version: 2020sp

    Lecture 2

    Lecture Slides

    No assignment this week; enjoy your break!

    How to set up a Node project

    Initializing a Node project

    Here I will be walking you through how to set up a Node project.

    Navigate to an empty folder where you want your project to be located. I will assume this folder is called helloworld.

    Run yarn init in this folder on the terminal. (Use cd to navigate to you helloworld folder in terminal)

    Upon running yarn init, you should be prompted to answer eight questions. Hit enter each time, we will use the default answer for all of these questions:

    yarn init v1.19.2
    question name (helloworld):
    question version (1.0.0):
    question description:
    question entry point (index.js):
    question repository url:
    question author:
    question license (MIT):
    question private:
    success Saved package.json
    โœจ Done in 16.19s.

    In the end, these questions create a package.json file. Your package.json should like this:

    package.json
    {
    "name": "helloworld",
    "version": "1.0.0",
    "main": "index.js",
    "license": "MIT"
    }

    The package.json is very important. It contains all the metadata about your project. Notice that the name, version, main, and license match the default responses we gave yarn init. For future projects, you may want to have more specific values for these keys, but for now the defaults will suffice.

    package.json essentially acts as an ingredients list for your project. It contains all dependencies your project needs and instructions for how to start and build your project.

    Installing Packages

    Lets try installing a package. In the first assignment we asked you to use express. Install express by running:

    yarn add express

    After installation completes, take a look at your package.json:

    {
    "name": "test",
    "version": "1.0.0",
    "main": "index.js",
    "license": "MIT",
    "dependencies": {
    "express": "^4.17.1"
    }
    }

    Notice that express was added to our dependencies. Now express is available for you to use in your project!

    Every time you add a dependency with yarn add <pkg_name>, <pkg_name> will be added to your dependencies in package.json if it can be found. It will also be added to your node_modules.

    Take a look inside your node_modules folder. This is where all your packages will be installed. Notice that even though you just installed one package, multiple packages are in package.json. This is because express itself has several dependencies that also got installed.

    You can find packages to use on npmjs.com.

    Don't Submit node_modules!!

    node_modules can potentially hundreds of megabytes of data on packages you installed. It is important to never submit this with your assignment or push it up to any remote repositories such as GitHub. Before submitting an assignment, remember to remove node_modules from the folder and then zip it and submit the zip file. You will be penalized if node_modules is submitted. Don't worry we will be able to recover your dependencies simply by running yarn install.

    Removing packages

    Lets say you made a typo installing express and you instead ran

    yarn add experss

    Your package.json should look like this:

    {
    "name": "test",
    "scripts": {
    "start": "node app.js"
    },
    "version": "1.0.0",
    "main": "index.js",
    "license": "MIT",
    "dependencies": {
    "express": "^4.17.1",
    "experss": ""
    }
    }

    Unfortunately, some malicious developer capitalized on this typo mistake and made experss an actual package. How do you remove experss?

    There are two ways. First, you can just run yarn remove experss. This will remove experss from your package.json and your node_modules folder.

    yarn install

    Another way is to delete experss manually from your package.json. (Just delete the line that has experss). This won't actually get rid of the package from your node_modules.

    To update your node_modules, first delete your node_modules folder and then run

    yarn install

    This fetches all your dependencies again based on your package.json and since experss is no longer there, it is not installed.

    More Express and HTTP Methods

    Now that we have our Node project set up, we can start writing our first script using JavaScript and express.

    GET Request

    Lets make a simple endpoint that gets the value of a variable x.

    Create a new file called index.js and add the following:

    index.js
    const express = require('express');
    const app = express();
    let x = 0;

    app.get('/getX', function (req, res) {
    res.send(x + '');
    });

    app.listen(8080, function () {
    console.log('Hello, World!');
    });

    This should be familiar. The first line loads in the express dependency and the second line initializes express. We declare a variable called x that we will be accessing and updating in this example.

    app.get specifies that any GET requests sent to the endpoint /getX will send back the value of x.

    Lastly, app.listen starts the server on port 8080 asking it to listen for requests.

    Lets test this script by running node index.js in the terminal at your helloworld directory.

    Hello, World! should be printed on the terminal showing that the port is up and running and listening for requests.

    Use your web browser to navigate to localhost:8080/getX. You should see the value of x, 0, on the page.

    You can terminate the running of the script using CTRL/Command+C.

    Postman

    Instead of always going to the endpoint in the browser, a robust way of testing our endpoints is to use Postman.

    Postman is a software that allows you to simulate requests that could be sent by a user to your backend. It is useful for testing and ensuring that the behavior of your requests (including necessary headers) is what you expect.

    Download Postman here.

    Once you have Postman set up, make a request to the /getX endpoint by setting the request type as GET and the url as localhost:8080/getX. You should see 0 in the response body.

    POST Request

    Another type of request we talked about was a POST request, used to send data to the backend. Here we will use a POST request to update our variable x.

    Add the following to your index.js file after your app.get call:

    index.js
    app.post('/addOne', function (req, res) {
    if (req.query.variable === 'x') {
    x += 1;
    res.send(x + '');
    } else {
    res.send('We do not have that variable! :(');
    }
    });

    This code block tells express that if it receives a POST request at /addOne it should check if a query parameter was provided and if that parameter is x, increment x and send that new value of x back. Essentially, if we navigate to localhost:8080/addOne?variable=x, x will be incremented and the new value will be sent back. Notice that we are using a query variable variable. If we don't provide this query parameter or don't set it to x, the result shown will be We do not have that variable! :(

    Using Postman, we can check this endpoint by setting the request type as POST and sending the request to localhost:8080/addOne?variable=x. You should see x incremented. Sending a GET request to localhost:8080/getX should return the same value. Alternatively, if we try sending a POST request to localhost:8080/addOne?variable=y or localhost:8080/addOne, you will see We do not have that variable! :(.

    Request Bodies

    Usually when you want to send a POST request you also want to send information with it. We can do this using request bodies.

    To do this, we will need to use another package body-parser. body-parser should already be installed with express, but if not you can run yarn add body-parser.

    We can import it into our script using const bodyParser = require('body-parser'); and tell express to use it to parse JSON input using app.use(bodyParser.json());.

    Your index.js should now look like the following:

    index.js
    const express = require('express');
    const bodyParser = require('body-parser');

    const app = express();
    app.use(bodyParser.json());

    let x = 0;

    app.get('/getX', function (req, res) {
    res.send(x + '');
    });

    app.post('/add1', function (req, res) {
    if (req.query.variable === 'x') {
    x += 1;
    res.send(x + '');
    } else {
    res.send("We don't have that variable! :(");
    }
    });

    app.listen(8080, function () {
    console.log('Hello, World!');
    });

    Now we can add the following function:

    app.post('/updateVar', function (req, res) {
    x = req.body.x;
    res.send(x + '');
    });

    This tells express to listen for POST requests at endpoint /updateVar. req.body.x is the request body and we update our local variable x to that value and send it back.

    We can check that this endpoint is working using Postman. Set the request type to POST and url as localhost:8080/updateVar. To send a request body, first go to Headers and add a new key Content-Type with value application/json. This says we are sending JSON input (essentially, an object of dictionary) in our request body. In the Body section, select the raw radio button and enter the following in the text field:

    {
    "x": 3000
    }

    We will be sending x with a value of 3000 in the request body.

    Sending this request, you should see 3000 printed in the request body.

    Intro to Databases and Firebase

    Why do we need a database for our backend?

    • Data stored within Node.js is per instance
    • Most applications require persistence
    • Data analysis
    • Performant data location
    • Offloading unneeded data from memory

    MySQL + Relational Databases

    • Stores data in tables, utilizing rows and tables.
    • Is relational (think a tuple)
    • Has a schema

    NoSQL and Firestore

    We will focus on NoSQL

    • Many NoSQL implementations are schema-less or have a partial schema
    • Firestore is a cloud-hosted NoSQL database
    • Very flexible and can be used with most popular languages
    • Uses documents to store data
    • Efficient querying for data

    SQL vs NoSQL

    • SQL databases have a predefined schema, whereas NoSQL databases can abide to any structure you want it to.
    • NoSQL databases are better suited for large sets of data, but not for complex queries.
    • SQL databases tend to be less expensive for smaller datasets, but also less flexible.
    • SQL has strong consistency whereas NoSQL has eventual consistency (i.e. there may be some delay in getting the response back)
    • SQL is vertically scalable (need more computing power on one machine to store more data) while NoSQL is horizontally scalable (can distribute storage and compute power on multiple machines)

    What is Firebase?

    • Firebase is a Backend as a Service (BaaS) offered by Google
      • Allows you to store data
      • Host websites
      • Authentication
    • NoSQL DB
      • Not only SQL
      • Non relational

    Why Firebase?

    • Real-time operations
    • Firebase Authentication
    • Built-in analytics
    • Also supports hosting/deployment
    • Integration with other Google services
    • Structure weโ€™re familiar with!
    - + \ No newline at end of file diff --git a/docs/2020sp/lecture3/index.html b/docs/2020sp/lecture3/index.html index 76be7a8ee..46a7674cd 100644 --- a/docs/2020sp/lecture3/index.html +++ b/docs/2020sp/lecture3/index.html @@ -5,7 +5,7 @@ Lecture 3 | Trends in Web Dev - + @@ -27,7 +27,7 @@ Note that the code below does not care about what are the fields of a post, because Firestore doesn't require you to have a predefined set of field. This gives you flexibility when writing your backend code.

    index.ts
    // in typescript import packages as modules
    import admin from 'firebase-admin';
    import express from 'express'; // also install type aliases for Request, Response
    import bodyParser from 'body-parser';

    const serviceAccount = require('./service-account.json');

    admin.initializeApp({
    credential: admin.credential.cert(serviceAccount),
    databaseURL: 'https://dti-web-dev-sp19-db-demo.firebaseio.com',
    });

    const db = admin.firestore();

    const app = express();
    const port: number = 8080;
    app.use(bodyParser.json());

    // Define a type for our Post document stored in Firebase
    type Post = {
    content: string;
    name: string;
    };

    type PostWithID = Post & {
    id: string;
    };

    app.get('/', (_, res) => res.send('Hello World!'));

    const postsCollection = db.collection('posts');

    // create a post
    app.post('/post', function (req, res) {
    const post: Post = req.body;
    const myDoc = postsCollection.doc();
    myDoc.set(post);
    res.send(myDoc.id);
    });

    // read all posts
    app.get('/post', async function (_, res) {
    // we don't use the first request parameter
    const allPostsDoc = await postsCollection.get();
    const posts: PostWithID[] = [];
    for (let doc of allPostsDoc.docs) {
    let post: PostWithID = doc.data() as PostWithID;
    post.id = doc.id;
    posts.push(post);
    }
    res.send(posts);
    });

    // read posts by name
    app.get('/post/:name', async function (req, res) {
    const namePostsDoc = await postsCollection
    .where('name', '==', req.params.name)
    .get();
    const posts: PostWithID[] = [];
    for (let doc of namePostsDoc.docs) {
    let post: PostWithID = doc.data() as PostWithID;
    post.id = doc.id;
    posts.push(post);
    }
    res.send(posts);
    });

    // sorted posts by name
    app.get('/postsorted', async function (_, res) {
    // we don't use the first request parameter
    const sortedPosts = await postsCollection.orderBy('name', 'desc').get();
    const posts: PostWithID[] = [];
    for (let doc of sortedPosts.docs) {
    let post: PostWithID = doc.data() as PostWithID;
    post.id = doc.id;
    posts.push(post);
    }
    res.send(posts);
    });

    // update a post
    app.post('/post/:id', async function (req, res) {
    const id: string = req.params.id;
    const newPost = req.body;
    await postsCollection.doc(id).update(newPost);
    res.send('UPDATED');
    });

    // delete a post
    app.delete('/post/:id', async function (req, res) {
    const id: string = req.params.id;
    await postsCollection.doc(id).delete();
    res.send('DELETED');
    });

    app.listen(port, () => console.log(`Example app listening on port ${port}!`));

    For TypeScript, we need to compile this down to JavaScript before we run it:

    tsc index.ts
    node index.js

    The first command compiles index.ts to a JS file of the same name. The second runs that JS file.

    - + \ No newline at end of file diff --git a/docs/2020sp/lecture4/index.html b/docs/2020sp/lecture4/index.html index 60af4ac03..a5539e4ef 100644 --- a/docs/2020sp/lecture4/index.html +++ b/docs/2020sp/lecture4/index.html @@ -5,7 +5,7 @@ Lecture 4 | Trends in Web Dev - + @@ -55,7 +55,7 @@ warnings when you accidentally write some wrong code.

    Resources

    I recommend the You Don't Know JS series by Kyle Simpson. The ebooks are available for free on GitHub. The series is comprehensive and will teach you everything you want to know.

    - + \ No newline at end of file diff --git a/docs/2020sp/lecture5/index.html b/docs/2020sp/lecture5/index.html index e864a4b48..eb503e0e9 100644 --- a/docs/2020sp/lecture5/index.html +++ b/docs/2020sp/lecture5/index.html @@ -5,7 +5,7 @@ Lecture 5 | Trends in Web Dev - + @@ -22,7 +22,7 @@ to show how you can achieve this in different ways.

    import React from 'react';
    // Suppose you have a ContactCard component defined there.
    import ContactCard from './ContactCard';

    const data = [
    { name: 'Sam1', email: 'foo@bar.com' },
    { name: 'Sam2', email: 'bar@baz.com' },
    { name: 'Sam3', email: 'baz@foo.com' },
    ];

    const ListBySimpleMap = () => (
    <div>
    {data.map((contact) => (
    <ContactCard
    key={contact.name}
    name={contact.name}
    email={contact.email}
    />
    ))}
    </div>
    );

    const ListBySimpleMapWithObjectDestructing = () => (
    <div>
    {data.map(({ name, email }) => (
    <ContactCard key={name} name={name} email={email} />
    ))}
    </div>
    );

    const ListBySimpleMapWithSpread = () => (
    <div>
    {data.map((contact) => (
    <ContactCard key={contact.name} {...contact} />
    ))}
    </div>
    );

    Note that we always need a key prop. Without this, React will give you warnings in the console. React needs a unique key for each item in the list to help it avoid rerendering everything when only one item in the list changes.

    - + \ No newline at end of file diff --git a/docs/2020sp/lecture6/index.html b/docs/2020sp/lecture6/index.html index 0893c7fe7..696765ac3 100644 --- a/docs/2020sp/lecture6/index.html +++ b/docs/2020sp/lecture6/index.html @@ -5,7 +5,7 @@ Lecture 6 | Trends in Web Dev - + @@ -34,7 +34,7 @@ based on conditions a lot easier. In this small example, we went from five lines of code in the component to just one!

    Composition vs. Inheritance

    Composition and inheritance are two programming techniques for defining how classes relate to objects. (Think of classes as the blueprint for a house and objects the actual houses created from that blueprint)

    Composition

    Composition defines a class as the sum of its individual parts. This is a "has-a" relationship (e.g. a car has a steering wheel, has a window, etc). In Java (and other object oriented languages), these components are represented as instance variables.

    Inheritance

    Inheritance derives one class from another. If class A is the parent of class B and C, B and C inherit the properties/functions of A. This is a "is-a" relationship (e.g. car is a vehicle, circle is a shape.)

    React uses Composition

    โ€œReact has a powerful composition model, and we recommend using composition instead of inheritance to reuse code between components.โ€ -- React Docs

    Containment

    Components may not know their children ahead of time.

    Children are the components you put within another component:

    <ComponentA>{/* anything here is a child of Component A */}</ComponentA>

    Use the children prop to pass in children components.

    Container.tsx
    import React, { ReactNode } from 'react';
    type Props = { readonly children: ReactNode };
    const Container = (props: Props) => (
    <div className="Border">{props.children}</div>
    );
    const App = () => (
    <div className="App">
    <Container>
    <p>Hello!</p>
    <p>Bye!</p>
    </Container>
    </div>
    );

    props.children will have the paragraph elements.

    We didn't actually get to this live demo, adapted from this tutorial in the React docs, during lecture but it is very simple if you want to try it out yourself. We also show how to import styles.

    Container.tsx
    import React, { ReactNode } from 'react';
    import './Container.css'; // this is how we import styles

    type Props = { readonly children: ReactNode };

    export default (props: Props) => <div className="Border">{props.children}</div>;
    Container.css
    .Border {
    border: 4px solid black;
    background-color: azure;
    }

    Less common but you also may want multiple "holes" in your component (for example, a left and right child):

    SplintPane.tsx
    import React, { ReactNode } from 'react';
    import './SplitPane.css';

    type Props = { readonly left: ReactNode; readonly right: ReactNode };

    export default (props: Props) => (
    <div>
    <div className="LeftPane">{props.left}</div>
    <div className="RightPane">{props.right}</div>
    </div>
    );
    SplitPane.css
    /* these colors are ugly I know */
    .LeftPane {
    float: left;
    width: 50%;
    background-color: red;
    }

    .RightPane {
    float: right;
    width: 50%;
    background-color: aquamarine;
    }
    import React from 'react';
    import SplitPane from './SplitPane';
    import Container from './Container';

    export default () => {
    return (
    <div className="App">
    <Container>
    <p>Hello, world!</p>
    </Container>
    <SplitPane
    left={<div>I'm on the left!</div>}
    right={<div>I'm on the right!</div>}
    />
    </div>
    );
    };

    Lifting State Up

    This section was a live demo, adapted from this tutorial in the React docs.

    App.js
    import React, { useState } from 'react';
    import './App.css';
    import FahrenheitInput from './FahrenheitInput';
    import CelsiusInput from './CelsiusInput';

    function App() {
    const [temperature, setTemperature] = useState(-40);

    return (
    <div className="App">
    <label>Fahrenheit:</label>
    <FahrenheitInput
    temperature={temperature}
    callback={(temp) => setTemperature(temp)}
    />
    <br />
    <label>Celcius:</label>
    <CelsiusInput
    temperature={temperature}
    callback={(temp) => setTemperature(temp)}
    />
    <br />
    {temperature >= 100 ? (
    <span>Water would boil here!</span>
    ) : (
    <span>Water would not boil here!</span>
    )}
    <br />
    <span>Water would {temperature >= 0 && 'not'} freeze here!</span>
    </div>
    );
    }

    export default App;
    CalciusInput.tsx
    import React, { ChangeEvent } from 'react';

    type Props = {
    readonly temperature: number;
    readonly callback: (temperature: number) => void;
    };

    export default ({ temperature, callback }: Props) => {
    const handlechange = (e: ChangeEvent<HTMLInputElement>) =>
    callback(parseInt(e.target.value) || 0);

    return <input value={temperature} onChange={handlechange} />;
    };
    FahrenheitInput.tsx
    import React from 'react';

    type Props = {
    readonly temperature: number;
    readonly callback: (temperature: number) => void;
    };

    export default ({ temperature, callback }: Props) => {
    const handlechange = (e: ChangeEvent<HTMLInputElement>) =>
    callback((((parseInt(e.target.value) || 0) - 32) * 5) / 9);

    return <input value={(temperature * 9) / 5 + 32} onChange={handlechange} />;
    };

    Check lecture video for a more detailed explanation.

    - + \ No newline at end of file diff --git a/docs/2020sp/lecture7/index.html b/docs/2020sp/lecture7/index.html index b6e905009..84e529ebc 100644 --- a/docs/2020sp/lecture7/index.html +++ b/docs/2020sp/lecture7/index.html @@ -5,7 +5,7 @@ Lecture 7 | Trends in Web Dev - + @@ -61,7 +61,7 @@ parent component
  • Connect component to the backend (more on this in Lecture 8!)
  • Filterable Product Table Example

    This section contains the code from the live demo presented during class. Watch the lecture video linked at the top for an explanation of the code, intended to teach how to think in the React development paradigm.

    App.js (root component)
    import React from 'react';
    import FilterableProductTable from './FilterableProductTable';

    const PRODUCTS = [
    {
    category: 'Sporting Goods',
    price: '$49.99',
    stocked: true,
    name: 'Football',
    },
    {
    category: 'Sporting Goods',
    price: '$9.99',
    stocked: true,
    name: 'Baseball',
    },
    {
    category: 'Sporting Goods',
    price: '$29.99',
    stocked: false,
    name: 'Basketball',
    },
    {
    category: 'Electronics',
    price: '$99.99',
    stocked: true,
    name: 'iPod Touch',
    },
    {
    category: 'Electronics',
    price: '$399.99',
    stocked: false,
    name: 'iPhone 5',
    },
    { category: 'Electronics', price: '$199.99', stocked: true, name: 'Nexus 7' },
    ];

    const App = () => (
    <div className="App">
    <FilterableProductTable products={PRODUCTS} />
    </div>
    );

    export default App;
    FilterableProductTable.jsx
    import React, { useState } from 'react';
    import ProductTable from './Starter';

    const SearchBar = ({
    filterText,
    inStockOnly,
    handleFilterTextChange,
    handleCheckBoxChange,
    }) => (
    <form>
    <input
    type="text"
    placeholder="Search..."
    value={filterText}
    onChange={handleFilterTextChange}
    />
    <p>
    <input
    type="checkbox"
    checked={inStockOnly}
    onChange={handleCheckBoxChange}
    />{' '}
    Only show products in stock
    </p>
    </form>
    );

    const FilterableProductTable = ({ products }) => {
    const [filterText, setFilterText] = useState('');
    const [inStockOnly, setInStockOnly] = useState(false);

    const handleFilterTextChange = (e) => setFilterText(e.target.value);
    const handleCheckBoxChange = (e) => setInStockOnly(e.target.checked);

    return (
    <div>
    <SearchBar
    filterText={filterText} // states passed as prop to SearchBar
    inStockOnly={inStockOnly} // states passed as prop to SearchBar
    handleFilterTextChange={handleFilterTextChange} // only step 5
    handleCheckBoxChange={handleCheckBoxChange} // only step 5
    />
    <ProductTable
    products={products} // JSON API model
    filterText={filterText} // states passed as prop to SearchBar
    inStockOnly={inStockOnly} // states passed as prop to SearchBar
    />
    </div>
    );
    };

    export default FilterableProductTable;
    Starter.jsx
    // Contains all the base components (we can put multiple components in a jsx file
    // for convenience, though this is not usually good practice).
    import React from 'react';

    // These components will be starter code because they are most self-explanatory
    // and purely presentational. We will go over this code briefly in lecture.
    // Students encouraged to read this on their own time.

    const ProductRow = ({ product }) => {
    const name = product.stocked ? (
    product.name
    ) : (
    <span style={{ color: 'red' }}>{product.name}</span>
    );
    return (
    <tr>
    <td>{name}</td>
    <td>{product.price}</td>
    </tr>
    );
    };

    const ProductCategoryRow = ({ category }) => (
    <tr>
    <th colSpan="2">{category}</th>
    </tr>
    );

    const ProductTable = ({ products, filterText, inStockOnly }) => {
    const rows = [];
    let lastCategory = null;

    products.forEach((product) => {
    if (product.name.indexOf(filterText) === -1) {
    return;
    }
    if (inStockOnly && !product.stocked) {
    return;
    }
    if (product.category !== lastCategory) {
    rows.push(
    <ProductCategoryRow
    category={product.category}
    key={product.category}
    />,
    );
    }
    rows.push(<ProductRow product={product} key={product.name} />);
    lastCategory = product.category;
    });

    return (
    <table>
    <thead>
    <tr>
    <th>Name</th>
    <th>Price</th>
    </tr>
    </thead>
    <tbody>{rows}</tbody>
    </table>
    );
    };

    // Here we can export all these components at once!
    // Notice also the name of the file does not match any single component name.
    // export { ProductRow, ProductCategoryRow, ProductTable };
    export default ProductTable; // but we only need ProductTable here (in FilterableProductTable.jsx)
    - + \ No newline at end of file diff --git a/docs/2020sp/lecture8/index.html b/docs/2020sp/lecture8/index.html index a3e5de1c6..9374b69dd 100644 --- a/docs/2020sp/lecture8/index.html +++ b/docs/2020sp/lecture8/index.html @@ -5,7 +5,7 @@ Lecture 8 | Trends in Web Dev - + @@ -40,7 +40,7 @@ you have.

    You update your data by calling an endpoint within useEffect and setting your data to the response that you get back.

    You can call endpoints using fetch() or axios and handle the responses asynchronously.

    Demo Code

    Backend

    index.js (backend)
    const express = require('express');
    const admin = require('firebase-admin');
    const serviceAccount = require('./serviceAccount.json');

    admin.initializeApp({
    credential: admin.credential.cert(serviceAccount),
    databaseURL: 'https://webdev-lec3.firebaseio.com',
    });

    const app = express();
    app.use(express.json());
    const db = admin.firestore();

    const songsCollection = db.collection('songs');

    app.get('/getSongs', async (req, res) => {
    const songs = await songsCollection.get();
    res.json(songs.docs.map((song) => ({ ...song.data(), id: song.id })));
    });

    app.post('/createSong', async (req, res) => {
    const newSong = req.body;
    const addedSong = await songsCollection.add(newSong);
    res.send(addedSong.id);
    });

    app.post('/updateRating', async (req, res) => {
    const { id, rating } = req.query;
    await songsCollection.doc(id).update({ rating });
    res.send('Song rating updated!');
    });

    app.listen(8080, () => console.log('backend started'));
    SongList.jsx (frontend)
    import React, { useState, useEffect } from 'react';
    import Song from './Song';
    import SongAdder from './SongAdder';
    import axios from 'axios';

    export default () => {

    const [songs, setSongs] = useState([]);

    // GET request using fetch
    const fetchSongs = () => {
    fetch('/getSongs')
    .then(res => res.json())
    .then(json => setSongs(json));
    }

    // GET request using axios
    // const fetchSongs = () => {
    // axios.get('/getSongs')
    // .then(res => setSongs(res.data));
    // }

    useEffect(() => fetchSongs(), []);

    // POST requset using fetch
    const addSong = (name, artist, rating) => {
    fetch('/createSong', {
    method: 'POST',
    headers: {
    'Content-Type': 'application/json'
    },
    body: JSON.stringify({ name, artist, rating })
    })
    .then(res => res.text())
    .then(id => setSongs([...songs, { name, artist, rating, id }]))
    }

    // POST request using axios
    // const addSong = (name, artist, rating) => {
    // axios.post('/createSong', { name, artist, rating })
    // .then(res => setSongs([...songs, { name, artist, rating, id: res.data }]))
    // }

    // POST request (update) using fetch
    const updateRating = (id, rating) => {
    fetch(`/updateRating?id=${id}&rating=${rating}`, {
    method: 'POST'
    })
    .then(res => setSongs(songs.map(song => song.id === id ? { name: song.name, artist: song.artist, rating, id } : song)))
    }

    // POST request (update) using axios
    // const updateRating = (id, rating) => {
    // axios.post(`/updateRating?id=${id}&rating=${rating}`)
    // .then(res => setSongs(songs.map(song => song.id === id ? { name: song.name, artist: song.artist, rating, id } : song)))
    // }

    return (
    <div>
    {songs.map(song => (<div> <Song key={song.id} {...song} updateRating={updateRating} /> <br /> </div>))}
    <SongAdder callback={addSong} />
    </div>
    )
    SongAdder.jsx (frontend)
    import React, { useState } from 'react';

    export default ({ callback }) => {
    const [name, setName] = useState('');
    const [artist, setArtist] = useState('');
    const [rating, setRating] = useState(0);

    return (
    <div>
    <h3> Add a new song! </h3>
    <input
    placeholder="Song name"
    onChange={(e) => setName(e.target.value)}
    /> <br />
    <input
    placeholder="Artist name"
    onChange={(e) => setArtist(e.target.value)}
    />{' '}
    <br />
    <input
    placeholder="Rating"
    onChange={(e) => setRating(e.target.value)}
    /> <br />
    <button onClick={(e) => callback(name, artist, rating)}> Add song</button>
    </div>
    );
    };
    Song.jsx (frontend)
    import React, { useState } from 'react';

    export default ({ id, name, artist, rating, updateRating }) => {
    const [newRating, setNewRating] = useState(rating);

    return (
    <div>
    <div>
    {' '}
    The song {name} by {artist} currently has a rating of {rating}/5{' '}
    </div>
    <input
    placeholder="New rating"
    onChange={(e) => setNewRating(e.target.value)}
    />
    <button onClick={(e) => updateRating(id, newRating)}>
    {' '}
    Update Rating{' '}
    </button>
    </div>
    );
    };
    - + \ No newline at end of file diff --git a/docs/2020sp/lecture9/index.html b/docs/2020sp/lecture9/index.html index 6a1308787..f41757f82 100644 --- a/docs/2020sp/lecture9/index.html +++ b/docs/2020sp/lecture9/index.html @@ -5,13 +5,13 @@ Lecture 9 | Trends in Web Dev - +
    Version: 2020sp

    Lecture 9

    Final Project due May 6 7:59pm

    Lecture Video

    Lecture Slides

    Deployment

    To deploy your web application means to put it on a Web server so others can access it via the internet. We will deploy frontend on Firebase and backend on Heroku.

    Frontend Deployment

    To deploy to Firebase enter the following commands into terminal:

    yarn global add firebase-tools
    yarn build
    firebase login
    firebase init
    <answer the questions>
    firebase deploy

    yarn build will create a build directory containing a production build of your application.

    firebase login will prompt you to log in by opening up a web browser if you're not already signed it.

    firebase init will ask you the following questions:

    1. Which Firebase CLI features do you want to set up for this folder? Select Hosting.
    2. Associate with a Firebase project. Select your Firebase project
    3. What do you want as your public directory? build
    4. Configure as a single-page app (rewrite all urls to /index.html)? Yes
    5. Overwrite index.html? No

    Running firebase deploy will push your build assets to Firebase remote server and give you a URL to your live Firebase app site! Now you can share this site and access it over the internet.

    Backend Deployment

    We will deploy backend on Heroku because deploying on Firebase is much more involved

    yarn global add heroku
    git init
    git add .
    git commit -m "COMMIT MESSAGE"
    heroku login
    heroku create <optional project name>
    git push heroku master
    (optional) heroku open

    You can then use the url generated as your backend endpoint in your frontend code.

    Authentication

    One of the best parts about Firebase is you can use Sign in with Google/Facebook/GitHub/etc! This way you don't have to deal with usernames and passwords yourself!

    We did a Live Coding Demo here based on the Songs example from last week. I will include the files changed here.

    To handle authentication we made a wrapper component Authenticated to handle all Authentication:

    Authenticated.jsx
    import React, { useState } from 'react';
    import 'firebase/auth';
    import * as firebase from 'firebase/app';
    import FirebaseAuth from 'react-firebaseui/FirebaseAuth';
    import { useEffect } from 'react';

    const firebaseConfig = {}; // put firebase config in here

    firebase.initializeApp(firebaseConfig);

    export default (props) => {
    const [user, setUser] = useState(null);

    const uiConfig = {
    signInFlow: 'popup',
    signInOptions: [firebase.auth.GoogleAuthProvider.PROVIDER_ID],
    };

    function onAuthStateChange() {
    return firebase.auth().onAuthStateChanged((user) => {
    setUser(user);
    });
    }

    useEffect(() => onAuthStateChange(), []);

    return (
    <div>
    {user && props.children}
    {!user && (
    <FirebaseAuth uiConfig={uiConfig} firebaseAuth={firebase.auth()} />
    )}
    </div>
    );
    };

    We then wrap our whole SongList app in Authenticated.

    App.js
    import React from 'react';
    import logo from './logo.svg';
    import './App.css';
    import SongList from './SongList';
    import Authenticated from './Authenticated';

    function App() {
    return (
    <div className="App">
    <Authenticated>
    <SongList />
    </Authenticated>
    </div>
    );
    }

    export default App;

    If the user is logged in, SongList will show. Otherwise they will be asked to log in.

    We then deployed this app on Firebase for the frontend and Heroku for the backend. Refer to the commands above.

    TypeScript

    TypeScript is a typed superset of JavaScript that compiles to plain JavaScript. Superset means TypeScript has everything in JavaScript and more. (Built by Microsoft!)

    JavaScript Types

    JavaScript has 6 primitive types:

    • Boolean
    • String
    • Number
    • Symbol
    • undefined
    • BigInt

    All JavaScript values are those 6 primitive types or a:

    • object
    • function (JavaScript is functional!)
    • null

    How are types used?

    In JavaScript we had:

    let str = 'Hello, trends';
    let num = 42;
    let truth = true;

    const someFunc = (x, s, b) => {
    // do some operations...
    return x;
    };

    Notice we don't have any types here! JavaScript is weakly typed.

    let str: string = 'Hello, trends';
    let num: number = 42;
    let truth: boolean = false;
    const someFunc = (x: number, s: string, b: boolean): number => {
    // do some operations...
    return x;
    };

    TypeScript allows us to add type information!

    Why TypeScript?

    JavaScript code can be ambiguous. We had the function:

    const someFunc = (x, s, b) => {
    // do some operations...
    return x;
    };

    What are x, s, b? What should I pass in for those? What should I expect returned?

    Adding the TypeScript types makes this code self-documenting:

    const someFunc = (x: number, s: string, b: boolean): number => {
    // do some operations...
    return x;
    };

    JavaScript variables can also change type which can be undesirable, unexpected, and error-prone.

    let str = 'Hello, trends';
    let num = 42;
    let truth = true;
    str = 13;

    None of these variables have to be any specific type! I can have str be a string and then a number.

    In the end, we want to use TypeScript because it is:

    • Easier to read
    • Easier and faster to implement
    • Easier to refactor
    • Less buggy

    TypeScript Types

    Basic Syntax:

    let <var_name>: <type> = <something>;

    We can also use const but again no var.

    Basic Types

    // Boolean
    let isDone: boolean = false;
    // Number can be decimal, or in any base!
    let decimal: number = 4.2;
    let binary: number = 0b1010;
    let hex: number = 0xf00d;
    // String
    let lang: string = 'typescript';
    let templateStr: string = `We love ${lang}`;
    // Boolean
    let isDone: boolean = false;
    // Number can be decimal, or in any base!
    let decimal: number = 4.2;
    let binary: number = 0b1010;
    let hex: number = 0xf00d;
    // String
    let lang: string = 'typescript';
    let templateStr: string = `We love ${lang}`;

    Any

    Any is a wildcard and it can be anything. any places no restrictions on type.

    // Any: can be anything!
    let notSure: any = 4;
    notSure = 'maybe a string instead';
    notSure = false; // okay, definitely a boolean

    If you were to use any everywhere though you might as well just use JavaScript

    let anyList: any[] = [4, 'maybe a string', false];

    But it can be useful in specifying collections of items of different types!

    Functions

    Functions can have types too!

    // un-typed
    const myFunc = (x, y) => x + y;
    // typed
    const myFunc = (x: number, y: number): number => x + y;

    myFunc has type (x: number, y: number): number.

    TypeScript can do some limited type inference so if you leave out the return type number, TypeScript can infer it since we are just adding two numbers which can only produce a number. If TypeScript can't infer the type, it defaults as any.

    We can also have optional parameters:

    const introduce = (name: string, github?: string): string => {
    return github
    ? `Hi, I'm ${name}. Checkout my GitHub @${github}`
    : `Hi, I'm ${name}. I don't have a GitHub.`;
    };

    github? designates github as an optional parameter that defaults to undefined.

    Literal Types

    Literal Types are types that can be a literal set of possibilities that you specify. TypeScript allows number and string literal types:

    String Literal Types
    // String literal type
    type TrafficLightColors = 'red' | 'green' | 'yellow';

    Any variable with TrafficLightColors type can only take on values "red", "green", "yellow".

    let light1: TrafficLightColors = 'red';
    light1 = 'blue'; // TypeError
    Numeric Literal Types
    // Numeric literal type
    type DiceRoll = 1 | 2 | 3 | 4 | 5 | 6;
    const rollDice = (): DiceRoll => {
    // ...
    };

    Union Types

    With union types, a variable can be of one type or another type.

    const union: number | string = 5; // number
    const union2: number | string = 'hello'; // string

    type TrafficLightColors = 'red' | 'green' | 'yellow';
    type PrimaryColors = 'red' | 'green' | 'blue';

    // "red" | "green" | "yellow" | "blue"
    type union = PrimaryColors | TrafficLightColors;

    Intersection Types

    With union types, a variable must be of one type and another type.

    // Intersection Type
    type TrafficLightColors = 'red' | 'green' | 'yellow';
    type PrimaryColors = 'red' | 'green' | 'blue';
    type intersect = PrimaryColors & TrafficLightColors; // "red" | "green"

    Type Inference

    Type inference is determining type information without being told explicitly. TypeScript has limited type inference capabilities. If it can't infer the type the default is any.

    Sometimes type inference is easy:

    // TypeScript can infer these types
    let isDone = false; // boolean
    let decimal = 4.2; // number
    let lang = 'typescript'; // string

    Other times it involves some more advanced reasoning:

    const whatType = (a, b, c) => (a(b + 1) === true ? b : c);

    What are the types of a, b, c and what is the return type?

    First b should be a number because we are adding 1 to it. Knowing b should be a number, a should then be a function taking in a number and returning a boolean. Finally, this function returns either b or c and b is already a number so c must also be number. Thus the return type is number.

    We expect the following types:

    a: number => boolean
    b: number
    c: number
    return: number

    In reality TypeScript infers the following:

    a: any
    b: any
    c: any
    return: any

    Add TypeScript to React!

    You can learn how to add TypeScript to your Create React App application here.

    - + \ No newline at end of file diff --git a/docs/2021fa/assignment1/index.html b/docs/2021fa/assignment1/index.html index 0ccf56dd3..699cdf89b 100644 --- a/docs/2021fa/assignment1/index.html +++ b/docs/2021fa/assignment1/index.html @@ -5,7 +5,7 @@ Assignment 1 | Trends in Web Dev - + @@ -32,7 +32,7 @@ example: with /first/:firstName/last/:lastName, in the request /first/scott/last/wang, 'scott' would be in req.params.firstName. What would req.params.lastName be?

    I still need help. Where should I go?

    Check out the office hours schedule on our syllabus. In addition, you can join the Ed and post your question.

    - + \ No newline at end of file diff --git a/docs/2021fa/assignment2/index.html b/docs/2021fa/assignment2/index.html index 53ffdf47b..f758d3343 100644 --- a/docs/2021fa/assignment2/index.html +++ b/docs/2021fa/assignment2/index.html @@ -5,7 +5,7 @@ Assignment 2 | Trends in Web Dev - + @@ -46,7 +46,7 @@ same case, and it'll be enough for us.

    Getting a bunch of weird TypeScript errors, and I don't think my code is wrong.

    Make sure to include the tsconfig.json from the lecture 3 notes in your project directory.

    Do we need to submit our service-account.json along with the other stuff?

    We're glad you trust us so much, but we'll be testing your endpoints with our own service-account.json and Firestore database, so no need to include it.

    - + \ No newline at end of file diff --git a/docs/2021fa/assignment3/index.html b/docs/2021fa/assignment3/index.html index fde791a83..1995e3413 100644 --- a/docs/2021fa/assignment3/index.html +++ b/docs/2021fa/assignment3/index.html @@ -5,7 +5,7 @@ Assignment 3 | Trends in Web Dev - + @@ -22,7 +22,7 @@ documentation, methods are often denoted like: Type.prototype.method()

    Remember, anything in JavaScript/TypeScript can be an object!

    So, we can do: (5).toExponential(10) or let x = 5; x.toExponential()

    Your goal is to round the age to the nearest tenth.

    For example, you want to display "5.6 years" for the value 5.64 and "5.7 years" for the value 5.65.

    Take a look at this documentation if you are stuck!

    Example for makeSentences

    const doggos = [
    { name: 'Sparky', age: 3.35, breed: 'Pomeranian Husky' },
    { name: 'Oreo', age: 5.42, breed: 'Dalmatian' },
    { name: 'Stella', age: 4, breed: 'Alaskan Klee Kai' },
    ];

    makeSentences(doggos);

    should output

    [
    'Sparky is 3.4 years old and is a Pomeranian Husky',
    'Oreo is 5.4 years old and is a Dalmatian',
    'Stella is 4.0 years old and is a Alaskan Klee Kai',
    ];

    Starter code:

    // TODO: You should replace this any with an accurate object type in your submission!
    type Doggo = any;

    export const makeSentences = (array: Doggo[]): string[] => {
    /* TODO: add your code */
    };

    Don't worry about printing "year" vs "years" or "a" vs "an", unless you want the extra challenge!

    Submission

    Please submit to CMS your index.ts file containing your implementations of each of the functions described above.

    - + \ No newline at end of file diff --git a/docs/2021fa/assignment4/index.html b/docs/2021fa/assignment4/index.html index d7c60ab52..c68f799ab 100644 --- a/docs/2021fa/assignment4/index.html +++ b/docs/2021fa/assignment4/index.html @@ -5,7 +5,7 @@ Assignment 4 | Trends in Web Dev - + @@ -25,7 +25,7 @@ is illegal. Instead, you have to make a copy of the myNumbers array and use setMyNumbers to alter it. (Hint: you can get a copy of a certain array by using the ES6 spreading syntax [...myNumbers])

    - + \ No newline at end of file diff --git a/docs/2021fa/assignment5/index.html b/docs/2021fa/assignment5/index.html index c7b9ec6eb..e90dfb1c1 100644 --- a/docs/2021fa/assignment5/index.html +++ b/docs/2021fa/assignment5/index.html @@ -5,7 +5,7 @@ Assignment 5 | Trends in Web Dev - + @@ -62,7 +62,7 @@ value to simply [], and it assumes that it's just a falsy value. To fix this, make sure to parametrize the useState hook using the type definition you created; for example, const [state, setState] = useState<MyType[]>([]).

    - + \ No newline at end of file diff --git a/docs/2021fa/assignments/index.html b/docs/2021fa/assignments/index.html index 39bb95d45..cbac1f9e0 100644 --- a/docs/2021fa/assignments/index.html +++ b/docs/2021fa/assignments/index.html @@ -5,14 +5,14 @@ Assignments | Trends in Web Dev - +
    Version: 2021fa

    Assignments

    Assignments will be released here after lecture! There will be 5 assignments total, as well as a final project spanning the last few weeks of the class.

    You are allowed max 3 slip days per assignment (out of 6 total for all assignments and the final project).

    Assignment 1: Due on CMS by 10/7 at 6:29pm

    Assignment 2: Due on CMS by 10/21 at 6:29pm

    Assignment 3: Due on CMS by 10/28 at 6:29pm

    Assignment 4: Due on CMS by 11/4 at 6:29pm

    Assignment 5: Due on CMS by 11/11 at 6:29pm extended to 11/14 by 11:59pm

    Final Project: See the page for more details on deadlines!

    - + \ No newline at end of file diff --git a/docs/2021fa/finalproject/index.html b/docs/2021fa/finalproject/index.html index 750dbcb3e..086901298 100644 --- a/docs/2021fa/finalproject/index.html +++ b/docs/2021fa/finalproject/index.html @@ -5,7 +5,7 @@ Final Project | Trends in Web Dev - + @@ -32,7 +32,7 @@ group members and netIDs, a link to the deployed site, a link to the GitHub repo if you used GitHub, and anything else that you think is important for us to know.

    As always, do not include your node_modules.

    Tips for Success!

    • Get in contact with your partner early!
      • Milestone 0 is intended for you to get some discussion on what you want to build before you start implementation. Make sure you are both aligned on what needs to be built to avoid issues later on. Better initial planning means less frustrations later on.
    • Be realistic.
      • We know you are ambitious but also understand your own capabilities. Building something too complex may be too overwhelming. You are allowed to change ideas, but that would be time wasted on the old project.
    • Use GitHub
      • GitHub is the best tool for sharing code between you and your partner/team members. Please use GitHub instead of emailing code back and forth to each other.
    • Use branches!
      • When developing a feature, you should open up a new GitHub branch rather than committing and pushing directly to master. This will allow you to develop your feature independently of the current state of master (and what your partner is doing) and only merge in when you are sure your feature is done and works.
      • Branches can also protect you from weird frustrating merge conflicts (so you can focus on developing awesome features!)
    • Pair programming is fun!
      • Ideally, you should both be actively involved in the whole development process. A good way to achieve this is to step up a time to pair program and code together!
    • Also refer to tips in How to Lose in CS 2112
    - + \ No newline at end of file diff --git a/docs/2021fa/introduction/index.html b/docs/2021fa/introduction/index.html index c1b591ff6..915a89b68 100644 --- a/docs/2021fa/introduction/index.html +++ b/docs/2021fa/introduction/index.html @@ -5,7 +5,7 @@ Syllabus | Trends in Web Dev - + @@ -67,7 +67,7 @@ have accommodations with Student Disability Services and require access to any class accommodations, please speak to an instructor before the first lecture and we will work with you to make arrangements as necessary.

    - + \ No newline at end of file diff --git a/docs/2021fa/lecture1/index.html b/docs/2021fa/lecture1/index.html index 44eacf950..fdd42355c 100644 --- a/docs/2021fa/lecture1/index.html +++ b/docs/2021fa/lecture1/index.html @@ -5,7 +5,7 @@ Lecture 1 | Trends in Web Dev - + @@ -28,7 +28,7 @@ annotations and the use of arrow function syntax! See if you can spot any explicit type annotations that can be inferred instead.

    demo2.ts
    const mySum = (inputArray: number[]): number => {
    let sum: number = 0;
    for (const num of inputArray) {
    sum += num;
    }
    return sum;
    };

    console.log(mySum([1, 2, 3])); // expected 6

    const isLeapYear = (year: number): boolean => {
    return (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;
    };

    console.log(isLeapYear(2000)); // is a leap year
    console.log(isLeapYear(2100)); // is NOT a leap year;

    const perfectSquares = (arr: number[]): number[] => {
    const ans: number[] = [];
    for (const num of arr) {
    if (Math.sqrt(num) % 1 === 0) {
    ans.push(num);
    }
    }
    return ans;
    };

    console.log(perfectSquares([1, 4, 9])); // expected same as input
    console.log(perfectSquares([1, 5, 9])); // expected [1, 9]

    Run it with ts-node script.ts. Voilร ! That's a basic introduction to TypeScript. For more language quirks and useful syntax, visit the TypeScript website and pick the tutorial that best fits you.

    - + \ No newline at end of file diff --git a/docs/2021fa/lecture10/index.html b/docs/2021fa/lecture10/index.html index c92d017b7..e4db2d74d 100644 --- a/docs/2021fa/lecture10/index.html +++ b/docs/2021fa/lecture10/index.html @@ -5,13 +5,13 @@ Lecture 10 | Trends in Web Dev - +
    Version: 2021fa

    Lecture 10

    Lecture Slides

    Final Project Instructions

    Final Project - Milestone 2 due 12/2 by 11:59pm

    Final Project - Milestone 3 due 12/9 by 11:59pm

    React Native

    What is React Native?

    React Native allows for cross platform mobile development using a webdev framework we already know--React!

    React Native allows you to build UIs independent of the platform. Usually when developing an app you have to develop an Android version (using Java/Kotlin) and iOS version (using Objective-C/Swift) separately. React Native takes care of this conversion for you.

    Core React Native Components

    Since React Native is really just React, many of the same concepts (useState, props, React Hooks, etc) still apply to React. However, instead of HTML we have Views. A view is the basic building block of UI in mobile development. Views can display images, hold text, handle user input, etc.

    Some core React Native components are:

    • <View>: A container that supports layout with flexbox, style, some touch handling, and accessibility controls. Similar to a non-scrolling <div>.
    • <Text>: Displays, styles, and nests strings of text and even handles touch events. Similar to a <p>
    • <Image>: Displays images like <img>
    • <ScrollView>: A generic scrolling container than can hold nested components and views. Similar to a <div>.
    • <TextInput>: User text input field similar to <input type="text" />.
    • ... and you can also define your own custom components (and use those built by the community)!

    How to start a React Native Project?

    A popular way to use React Native is through the Expo framework, which allows developing, building, and iterating on iOS, Android and webapps. Expo provides a UI for you to view your changes and if you download the Expo app (Android, iOS) you can see those changes on your phone as well! After all, we're doing mobile development. Expo also has a lot of packages specific to accessing features on mobile devices like the status bar, battery level, I/O devices, etc.

    To start a React Native project run the following:

    yarn global add expo-cli
    expo init <project name>
    yarn start

    expo init is similar to create-react-app in that it generates boilerplate code for you.

    Demo

    As part of the demo we built the simple TODO list app from assignment 4 and battery tracker in React Native!

    Before starting though, we have to install the expo-battery package by running expo install expo-battery. We use expo install to install expo packages instead of yarn add or npm install because it prevents you from installing incompatible versions with your particular Expo SDK version.

    App.tsx
    import { addBatteryLevelListener, getBatteryLevelAsync } from 'expo-battery';
    import { StatusBar } from 'expo-status-bar';
    import React, { useEffect, useState } from 'react';
    import { Button, StyleSheet, Text, View } from 'react-native';
    import TaskList from './TaskList';

    export default function App() {
    const [battery, setBattery] = useState(0);
    const [count, setCount] = useState(0);

    // Example of using a battery package from expo to access mobile features
    useEffect(() => {
    // initially gets battery level
    getBatteryLevelAsync().then(setBattery);
    // subscribe to battery level to keep it in sync
    const listener = addBatteryLevelListener((event) =>
    setBattery(event.batteryLevel),
    );
    // remove the subscription after unmounting
    return listener.remove;
    }, []);

    return (
    <View style={styles.container}>
    <Text>Battery: {battery * 100}%</Text>
    <Text>Count: {count}</Text>
    <Button title="Increment" onPress={() => setCount((x) => x + 1)} />
    <TaskList />
    <StatusBar style="auto" />
    </View>
    );
    }

    // how to style elements
    const styles = StyleSheet.create({
    container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
    gap: '2rem',
    },
    });
    Playlist.tsx
    import {
    Button,
    FlatList,
    Text,
    TextInput,
    View,
    StyleSheet,
    } from 'react-native';
    import React, { useState } from 'react';

    const styles = StyleSheet.create({
    // space out the container a bit
    container: { display: 'flex', gap: '8px' },
    // give our textbox a border
    input: {
    borderWidth: 1,
    },
    });

    export default () => {
    const [tasks, setTasks] = useState<string[]>([]);
    const [input, setInput] = useState('');
    return (
    <View style={styles.container}>
    <Text>Task List</Text>
    {/* flatlist is a canonical way to render a list of items instead of using
    map like you would in regular react */}
    <FlatList data={tasks} renderItem={(info) => <Text>{info.item}</Text>} />
    <TextInput
    style={styles.input}
    placeholder="Item Name"
    onChangeText={setInput}
    value={input}
    ></TextInput>
    <Button
    title="Add"
    onPress={() => {
    setTasks((tasks) => [...tasks, input]);
    setInput('');
    }}
    />
    </View>
    );
    };

    Once you yarn start, you should be taken to Expo where you can view your changes on your browser. On the bottom left, there should also be a QR code. If you download the Expo app and scan the QR code with your phone camera (Android, iOS), it should take you to the Expo app where you can see your application in mobile form!

    Learn more

    This was a very cursory introduction to React Native. You can learn more by referring to React docs.

    React Docs: https://reactnative.dev/docs/2021fa/getting-started

    Expo Docs: https://docs.expo.io/

    Extras - Prettifying your UI

    Material UI is a library that ships a bunch of pre-style and customizable components for you to use in your own React projects. Material UI comes with a variety of components ranging from buttons to icons to drawers, all of which are customizable and come with their own props that can do common tweaks to their existing components to fit your individual need.

    Semantic UI and Bootstrap are two other popular libraries used for styling and do not require projects written in React and provide pre-written classes for styling elements and organizing layout. Nevertheless - they do come with "React component" versions (Semantic UI React and React Bootstrap) similar to Material UI!

    - + \ No newline at end of file diff --git a/docs/2021fa/lecture2/index.html b/docs/2021fa/lecture2/index.html index 17976f618..f3554add6 100644 --- a/docs/2021fa/lecture2/index.html +++ b/docs/2021fa/lecture2/index.html @@ -5,7 +5,7 @@ Lecture 2 | Trends in Web Dev - + @@ -35,7 +35,7 @@ localhost:8080/users/<your_name>/<your_last_name> or localhost:8080/users/?name=<your_name>&lname=<your_last_name> you should see Hello <your_name> <your_last_name>.

    This was Node.js (and Express)!

    - + \ No newline at end of file diff --git a/docs/2021fa/lecture3/index.html b/docs/2021fa/lecture3/index.html index c6ecf49ef..168c3a431 100644 --- a/docs/2021fa/lecture3/index.html +++ b/docs/2021fa/lecture3/index.html @@ -5,7 +5,7 @@ Lecture 3 | Trends in Web Dev - + @@ -58,7 +58,7 @@ that the code below does not care about what are the fields of a post, because Firestore doesn't require you to have a predefined set of fields. This gives you flexibility when writing your backend code.

    index.ts
    import admin from 'firebase-admin';
    import express from 'express';

    // require the service account: note the file path
    const serviceAccount = require('../service-account.json');
    // initialize the firebase app
    admin.initializeApp({
    credential: admin.credential.cert(serviceAccount),
    });

    const db = admin.firestore();
    const app = express();
    const port = 8080;
    // allow request body parsing
    app.use(express.json());

    // check connections
    app.get('/', (_, res) => {
    res.send('connected!');
    });

    // create a post type and post with id
    type Post = {
    content: string;
    name: string;
    };

    type PostWithID = Post & {
    id: string;
    };

    // CRUD with firestore
    let posts1: Post[] = [{ content: 'I miss wellness days', name: 'Becky' }];

    // posts collection from db
    const postsCollection = db.collection('posts');

    // GET requests: get the songs
    app.get('/getPostsLocal', (_, res) => {
    res.send(posts1);
    });

    // use firebase instead
    app.get('/getPostsFirebase', async (_, res) => {
    const postsSnapshot = await postsCollection.get();
    const allPostsDoc = postsSnapshot.docs;
    const posts: PostWithID[] = [];
    for (let doc of allPostsDoc) {
    const post: PostWithID = doc.data() as PostWithID;
    post.id = doc.id;
    posts.push(post);
    }
    res.send(posts);
    });

    // read posts by name
    app.get('/getPosts/:name', async function (req, res) {
    const name = req.params.name;
    const postsSnapshot = await postsCollection.where('name', '==', name).get();
    const allPostsDoc = postsSnapshot.docs;
    const posts: PostWithID[] = [];
    for (let doc of allPostsDoc) {
    const post: PostWithID = doc.data() as PostWithID;
    post.id = doc.id;
    posts.push(post);
    }
    res.send(posts);
    });

    // read posts by id
    app.get('/getPostById/:id', async function (req, res) {
    const id = req.params.id;
    const postsSnapshot = await postsCollection.doc(id).get();
    const post: PostWithID = postsSnapshot.data() as PostWithID;
    res.send(post);
    });

    // sort posts in descending order by name
    app.get('/getPostsSorted', async function (req, res) {
    const postsSnapshot = await postsCollection.orderBy('name', 'desc').get();
    const allPostsDoc = postsSnapshot.docs;
    const posts: PostWithID[] = [];
    for (let doc of allPostsDoc) {
    const post: PostWithID = doc.data() as PostWithID;
    post.id = doc.id;
    posts.push(post);
    }
    res.send(posts);
    });

    // POST method: create a new post
    app.post('/addPostLocal', (req, res) => {
    const post: Post = req.body;
    posts1.push(post);
    res.send(`Post created by ${req.body.name}!`);
    });

    // generate a document with a random name to store the post's data
    app.post('/addPostFirebase', async function (req, res) {
    const post: Post = req.body;
    const postDoc = postsCollection.doc();
    await postDoc.set(post);
    res.send(postDoc.id);
    });

    // POST method: update an existing post
    app.post('/updatePostLocal', (req, res) => {
    for (let post of posts1) {
    if (post.name === req.body.name) {
    post.content = req.body.content;
    }
    }
    console.log(posts1);
    res.send('content updated!');
    });

    // update by id
    app.post('/updatePostFirebase/:id', async function (req, res) {
    const newPost: Post = req.body;
    const id: string = req.params.id;
    await postsCollection.doc(id).update(newPost);
    res.send('updated!');
    });

    // DELETE methdod: delete a post
    app.delete('/removePostLocal', (req, res) => {
    const newPosts = [];
    for (let post of posts1) {
    if (post.name !== req.body.name) {
    newPosts.push(post);
    }
    }
    posts1 = newPosts;
    res.send(`Post by ${req.body.name} deleted!`);
    });

    // delete by id
    app.delete('/removePostFirebase/:id', async function (req, res) {
    const id = req.params.id;
    await postsCollection.doc(id).delete();
    res.send('deleted!');
    });

    app.listen(port, () => console.log(`App started on port ${port}!`));
    - + \ No newline at end of file diff --git a/docs/2021fa/lecture4/index.html b/docs/2021fa/lecture4/index.html index cf756d91c..e05eaaae6 100644 --- a/docs/2021fa/lecture4/index.html +++ b/docs/2021fa/lecture4/index.html @@ -5,7 +5,7 @@ Lecture 4 | Trends in Web Dev - + @@ -58,7 +58,7 @@ The series is comprehensive and will teach you everything you want to know.

    Demo Code

    Need more examples? This code from last year's lecture's live coding demo rewrites some functions from the preassessment first using loops then with map, filter, and reduce.

    index.ts
    // getSqrts: takes in an array and returns an array with all the square roots
    // of those numbers
    // example: [1, 4, 9] => [1, 2, 3]
    const getSqrts = (arr: number[]): number[] => {
    const result: number[] = [];
    for (const num of arr) {
    result.push(Math.sqrt(num));
    }
    return result;
    };
    const getSqrtsMap = (arr: number[]): number[] => {
    return arr.map(Math.sqrt);
    };
    // perfectSquares: takes in an array and returns an array with only the
    // elements that are perfect squares
    // example: [1, 2, 3] => [1]
    const perfectSquares = (arr: number[]): number[] => {
    const result: number[] = [];
    for (const num of arr) {
    if (Math.sqrt(num) % 1 === 0) {
    result.push(num);
    }
    }
    return result;
    };
    const isPerfectSquare = (num: number) => Math.sqrt(num) % 1 === 0;
    const perfectSquaresFilter = (arr: number[]): number[] => {
    return arr.filter(isPerfectSquare);
    };
    // mySum: takes in an array and returns the sum of the elements
    // example: [1, 2, 3] => 6
    const mySum = (arr: number[]): number => {
    let sum = 0;
    for (const num of arr) {
    sum += num;
    }
    return sum;
    };
    const mySumReduce = (arr: number[]): number => {
    return arr.reduce((acc: number, curr: number) => acc + curr);
    };
    // testing!
    const input = [1, 2, 3];
    console.log(getSqrts(input));
    console.log(getSqrtsMap(input));
    console.log(perfectSquares(input));
    console.log(perfectSquaresFilter(input));
    console.log(mySum(input));
    console.log(mySumReduce(input));
    - + \ No newline at end of file diff --git a/docs/2021fa/lecture5/index.html b/docs/2021fa/lecture5/index.html index 03dd3a071..9f83dbb93 100644 --- a/docs/2021fa/lecture5/index.html +++ b/docs/2021fa/lecture5/index.html @@ -5,7 +5,7 @@ Lecture 5 | Trends in Web Dev - + @@ -32,7 +32,7 @@ In this particular example, you should only use name as the key if you know that the property name is unique. However, if there are multiple objects with the same name in the list that are used as a key, it would confuse React.

    - + \ No newline at end of file diff --git a/docs/2021fa/lecture6/index.html b/docs/2021fa/lecture6/index.html index 6a3d0cdde..8754d302e 100644 --- a/docs/2021fa/lecture6/index.html +++ b/docs/2021fa/lecture6/index.html @@ -5,7 +5,7 @@ Lecture 6 | Trends in Web Dev - + @@ -34,7 +34,7 @@ based on conditions a lot easier. In this small example, we went from five lines of code in the component to just one!

    Composition vs. Inheritance

    Composition and inheritance are two programming techniques for defining how classes relate to objects. (Think of classes as the blueprint for a house and objects the actual houses created from that blueprint)

    Composition

    Composition defines a class as the sum of its individual parts. This is a "has-a" relationship (e.g. a car has a steering wheel, has a window, etc). In Java (and other object oriented languages), these components are represented as instance variables.

    Inheritance

    Inheritance derives one class from another. If class A is the parent of class B and C, B and C inherit the properties/functions of A. This is a "is-a" relationship (e.g. car is a vehicle, circle is a shape.)

    React uses Composition

    โ€œReact has a powerful composition model, and we recommend using composition instead of inheritance to reuse code between components.โ€ -- React Docs

    Containment

    Components may not know their children ahead of time.

    Children are the components you put within another component:

    <ComponentA>{/* anything here is a child of Component A */}</ComponentA>

    Use the children prop to pass in children components.

    Container.tsx
    import React, { ReactNode } from 'react';
    type Props = { readonly children: ReactNode };
    const Container = (props: Props) => (
    <div className="Border">{props.children}</div>
    );
    const App = () => (
    <div className="App">
    <Container>
    <p>Hello!</p>
    <p>Bye!</p>
    </Container>
    </div>
    );

    props.children will have the paragraph elements.

    We didn't actually get to this live demo, adapted from this tutorial in the React docs, during lecture but it is very simple if you want to try it out yourself. We also show how to import styles.

    Container.tsx
    import React, { ReactNode } from 'react';
    import './Container.css'; // this is how we import styles

    type Props = { readonly children: ReactNode };

    export default (props: Props) => <div className="Border">{props.children}</div>;
    Container.css
    .Border {
    border: 4px solid black;
    background-color: azure;
    }

    Less common but you also may want multiple "holes" in your component (for example, a left and right child):

    SplintPane.tsx
    import React, { ReactNode } from 'react';
    import './SplitPane.css';

    type Props = { readonly left: ReactNode; readonly right: ReactNode };

    export default (props: Props) => (
    <div>
    <div className="LeftPane">{props.left}</div>
    <div className="RightPane">{props.right}</div>
    </div>
    );
    SplitPane.css
    /* these colors are ugly I know */
    .LeftPane {
    float: left;
    width: 50%;
    background-color: red;
    }

    .RightPane {
    float: right;
    width: 50%;
    background-color: aquamarine;
    }
    import React from 'react';
    import SplitPane from './SplitPane';
    import Container from './Container';

    export default () => {
    return (
    <div className="App">
    <Container>
    <p>Hello, world!</p>
    </Container>
    <SplitPane
    left={<div>I'm on the left!</div>}
    right={<div>I'm on the right!</div>}
    />
    </div>
    );
    };

    Lifting State Up

    This section was a live demo, adapted from this tutorial in the React docs.

    Calculator.tsx
    import { useState } from 'react';
    import TemperatureInput from './TemperatureInput';

    type Scale = 'celsius' | 'fahrenheit';

    const Calculator = () => {
    const [temperature, setTemperature] = useState('');
    const [scale, setScale] = useState<Scale>('celsius');

    const onCelsiusChange = (t: string) => {
    setTemperature(t);
    setScale('celsius');
    };

    const onFahrenheitChange = (t: string) => {
    setTemperature(t);
    setScale('fahrenheit');
    };

    const fahrenheitToCelsius = (t: number) => {
    return ((t - 32) * 5) / 9;
    };

    const celsiusToFahrenheit = (t: number) => {
    return (t * 9) / 5 + 32;
    };

    const tryConvert = (targetScale: Scale) => {
    const temp = parseFloat(temperature);
    if (Number.isNaN(temp)) {
    return '';
    }
    const res = getAppropriateTemperature(temp, targetScale);
    const trimmed = Math.round(res * 1000) / 1000;
    return trimmed.toString();
    };

    const getAppropriateTemperature = (tempNum: number, targetScale: Scale) => {
    if (targetScale === scale) {
    return tempNum;
    } else {
    if (targetScale === 'celsius') {
    return fahrenheitToCelsius(tempNum);
    } else {
    return celsiusToFahrenheit(tempNum);
    }
    }
    };

    return (
    <div>
    <TemperatureInput
    scale="celsius"
    temperature={tryConvert('celsius')}
    onTemperatureChange={onCelsiusChange}
    />
    <TemperatureInput
    scale="fahrenheit"
    temperature={tryConvert('fahrenheit')}
    onTemperatureChange={onFahrenheitChange}
    />
    </div>
    );
    };

    export type { Scale };
    export default Calculator;
    TemperatureInput.tsx
    import { Scale } from './Calculator';

    type Props = {
    readonly scale: Scale;
    readonly temperature: string;
    readonly onTemperatureChange: (t: string) => void;
    };

    const TemperatureInput = ({
    scale,
    temperature,
    onTemperatureChange,
    }: Props) => {
    return (
    <div>
    <legend>Enter temperature in {scale}</legend>
    <input
    value={temperature}
    onChange={(event) => onTemperatureChange(event.target.value)}
    />
    </div>
    );
    };

    export default TemperatureInput;
    - + \ No newline at end of file diff --git a/docs/2021fa/lecture7/index.html b/docs/2021fa/lecture7/index.html index de2d484ff..92ff09acc 100644 --- a/docs/2021fa/lecture7/index.html +++ b/docs/2021fa/lecture7/index.html @@ -5,7 +5,7 @@ Lecture 7 | Trends in Web Dev - + @@ -87,7 +87,7 @@ parent component
  • Connect component to the backend (more on this in Lecture 8!)
  • Filterable Product Table Example

    This section contains the code from the live demo presented during class. Watch the lecture video linked at the top for an explanation of the code, intended to teach how to think in the React development paradigm.

    App.tsx (root component)
    import FilterableProductTable from './FilterableProductTable';

    // Usually types would be placed in another file
    type Product = {
    name: string;
    stocked: boolean;
    price: number;
    };

    const PRODUCTS: Product[] = [
    { price: 49.99, stocked: true, name: 'Football' },
    { price: 9.99, stocked: true, name: 'Baseball' },
    { price: 29.99, stocked: false, name: 'Basketball' },
    { price: 99.99, stocked: true, name: 'iPod Touch' },
    { price: 999.99, stocked: false, name: 'iPhone 13' },
    { price: 699.99, stocked: true, name: 'Pixel 6' },
    ];

    const App = () => (
    <div>
    <FilterableProductTable products={PRODUCTS} />
    </div>
    );

    export type { Product };
    export default App;
    FilterableProductTable.tsx
    import { useEffect, useState } from 'react';
    import { Product } from './App';
    import ProductTable from './ProductTable';
    import SearchBar from './SearchBar';

    type Props = {
    products: Product[];
    };

    const FilterableProductTable = ({ products }: Props) => {
    const [filterText, setFilterText] = useState('');
    const [inStockOnly, setInStockOnly] = useState(false);

    useEffect(() => {
    document.title = filterText
    ? `Searching for: ${filterText}`
    : 'Searching for nothing, yet finding everything';
    }, [filterText]);

    return (
    <div>
    <SearchBar
    filterText={filterText}
    inStockOnly={inStockOnly}
    handleFilterTextChange={(e) => setFilterText(e.target.value)}
    handleCheckBoxChange={(e) => setInStockOnly(e.target.checked)}
    />
    <ProductTable
    products={products}
    filterText={filterText}
    inStockOnly={inStockOnly}
    />
    </div>
    );
    };

    export default FilterableProductTable;
    SearchBar.tsx
    import { ChangeEventHandler } from 'react';

    type Props = {
    filterText: string;
    inStockOnly: boolean;
    handleFilterTextChange: ChangeEventHandler<HTMLInputElement>;
    handleCheckBoxChange: ChangeEventHandler<HTMLInputElement>;
    };

    const SearchBar = ({
    filterText,
    inStockOnly,
    handleFilterTextChange,
    handleCheckBoxChange,
    }: Props) => (
    <form>
    <input
    type="text"
    placeholder="Search..."
    value={filterText}
    onChange={handleFilterTextChange}
    />
    <p>
    <input
    type="checkbox"
    checked={inStockOnly}
    onChange={handleCheckBoxChange}
    />
    Only show products in stock
    </p>
    </form>
    );

    export default SearchBar;
    ProductTable.tsx
    import { Product } from './App';

    // You could separate ProductRow and ProductTable into different files

    type RowProps = {
    product: Product;
    };

    type TableProps = {
    products: Product[];
    filterText: string;
    inStockOnly: boolean;
    };

    const ProductRow = ({ product: { name, stocked, price } }: RowProps) => {
    const nameStyle = stocked ? { color: 'red' } : {};
    return (
    <tr>
    <td style={{ ...nameStyle }}>{name}</td>
    <td>{price}</td>
    </tr>
    );
    };

    const ProductTable = ({ products, filterText, inStockOnly }: TableProps) => {
    const filteredProducts = products.filter(({ name, stocked }) => {
    return name.includes(filterText) && (!inStockOnly || stocked);
    });

    return (
    <table>
    <thead>
    <tr>
    <th>Name</th>
    <th>Price</th>
    </tr>
    </thead>
    <tbody>
    {filteredProducts.map((product) => (
    <ProductRow product={product} key={product.name} />
    ))}
    </tbody>
    </table>
    );
    };

    // Here we can export all these components at once!
    // export { ProductRow, ProductTable };

    // but ProductRow is only used within this file, so we choose to not export it
    export default ProductTable;

    // You could also export a single thing through named exports:
    // export { ProductTable };
    - + \ No newline at end of file diff --git a/docs/2021fa/lecture8/index.html b/docs/2021fa/lecture8/index.html index bc835e106..74d4ceb85 100644 --- a/docs/2021fa/lecture8/index.html +++ b/docs/2021fa/lecture8/index.html @@ -5,7 +5,7 @@ Lecture 8 | Trends in Web Dev - + @@ -60,7 +60,7 @@ you have.

    You update your data by calling an endpoint within useEffect and setting your data to the response that you get back.

    You can call endpoints using fetch() or axios and handle the responses asynchronously.

    Demo Code

    Backend

    index.ts (backend)
    import admin from 'firebase-admin';
    import express from 'express';

    const serviceAccount = require('../service-account.json');

    admin.initializeApp({
    credential: admin.credential.cert(serviceAccount),
    });

    const db = admin.firestore();

    const app = express();
    const port = 8080;
    app.use(express.json());

    type Product = {
    price: string;
    stocked: boolean;
    name: string;
    };

    type ProductWithID = Product & {
    id: string;
    };

    const productsCollection = db.collection('products');

    app.get('/getProducts', async (_, res) => {
    const products = await productsCollection.get();
    res.json(
    products.docs.map((doc): ProductWithID => {
    const product = doc.data() as Product;
    return { ...product, id: doc.id };
    }),
    );
    });

    app.post('/createProduct', async (req, res) => {
    const newProduct = req.body as Product;
    const addedProduct = await productsCollection.add(newProduct);
    res.send(addedProduct.id);
    });

    app.post('/updatePrice', async (req, res) => {
    const { id, rating } = req.query;
    await productsCollection.doc(id as string).update({ rating });
    res.send('Product updated!');
    });

    app.delete('/deleteProduct', async (req, res) => {
    const { id } = req.query;
    await productsCollection.doc(id as string).delete();
    res.send('Product deleted!');
    });

    app.listen(port, () => console.log(`Example app listening on port ${port}!`));

    Frontend

    ProductTable.tsx (frontend)
    // import axios from "axios";

    export type Product = {
    readonly price: string;
    readonly stocked: boolean;
    readonly name: string;
    readonly id: string;
    };

    type ProductRowProps = {
    product: Product;
    };

    // new code 3
    const ProductRow = ({
    product: { name, stocked, price, id },
    }: ProductRowProps) => {
    // DELETE request using fetch
    const deleteProduct = () => {
    fetch(`/deleteProduct?id=${id}`, {
    method: 'DELETE',
    headers: {
    'content-type': 'application/json',
    },
    });
    };

    // DELETE request using axios and async/await
    // const deleteProduct = async () => {
    // await axios.delete('/deleteProduct?id=${id}');
    // }

    const nameStyle = stocked ? {} : { color: 'red' };
    return (
    <tr>
    <td style={{ ...nameStyle }}>{name}</td>
    <td>{price}</td>
    <td>
    <button onClick={deleteProduct}>X</button>
    </td>
    </tr>
    );
    };

    type Props = {
    readonly products: Product[];
    readonly filterText: string;
    readonly inStockOnly: boolean;
    };

    const ProductTable = ({ products, filterText, inStockOnly }: Props) => {
    const filteredProducts = products.filter(({ name, stocked }) => {
    return name.includes(filterText) && (!inStockOnly || stocked);
    });

    return (
    <table>
    <thead>
    <tr>
    <th>Name</th>
    <th>Price</th>
    </tr>
    </thead>
    <tbody>
    {filteredProducts.map((product) => (
    <ProductRow product={product} key={product.id} />
    ))}
    </tbody>
    </table>
    );
    };

    export default ProductTable;
    FilterableProductTable.tsx (frontend)
    // import axios from "axios";
    import { ChangeEvent, useEffect, useState } from 'react';
    import ProductTable, { Product } from './ProductTable';

    type SearchProps = {
    readonly filterText: string;
    readonly inStockOnly: boolean;
    readonly handleFilterTextChange: (e: ChangeEvent<HTMLInputElement>) => void;
    readonly handleCheckBoxChange: (e: ChangeEvent<HTMLInputElement>) => void;
    };

    const SearchBar = ({
    filterText,
    inStockOnly,
    handleFilterTextChange,
    handleCheckBoxChange,
    }: SearchProps) => (
    <form>
    <input
    type="text"
    placeholder="search here"
    value={filterText}
    onChange={handleFilterTextChange}
    />
    <p>
    <input
    type="checkbox"
    checked={inStockOnly}
    onChange={handleCheckBoxChange}
    />
    Only show the products in stock
    </p>
    </form>
    );

    type NewProductProps = {
    products: Product[];
    setProducts: React.Dispatch<React.SetStateAction<Product[]>>;
    };

    const NewProduct = ({ products, setProducts }: NewProductProps) => {
    const [price, setPrice] = useState('');
    const [stocked, setStocked] = useState(true);
    const [name, setName] = useState('');

    // POST request using fetch
    const createProduct = () => {
    const newProduct = { price, stocked, name };
    fetch('/createProduct', {
    method: 'POST',
    headers: {
    'content-type': 'application/json',
    },
    body: JSON.stringify(newProduct),
    })
    .then((res) => res.text())
    .then((data) => {
    const newProductWithID = { ...newProduct, id: data };
    setProducts([...products, newProductWithID]);
    });
    };

    // POST request using axios and async/await
    // const createProduct = async () => {
    // const newProduct = { price, stocked, name };
    // const { data } = await axios.post<string>('/createProduct', newProduct);
    // const newProductWithID = { ...newProduct, id: data };
    // setProducts([...products, newProductWithID]);
    // }

    return (
    <div>
    <p>Add New Product:</p>
    <input
    type="text"
    name="name"
    placeholder="name..."
    value={name}
    onChange={(e) => setName(e.target.value)}
    />
    <input
    type="text"
    name="price"
    placeholder="price..."
    value={price}
    onChange={(e) => setPrice(e.target.value)}
    />
    <select
    name="stock"
    onChange={(e) => setStocked(e.target.selectedIndex === 0)}
    >
    <option value="true">in stock</option>
    <option value="false">out of stock</option>
    </select>
    <button onClick={createProduct}>add a product</button>
    </div>
    );
    };

    const FilterableProductTable = () => {
    const [filterText, setFilterText] = useState('');
    const [inStockOnly, setInStockOnly] = useState(false);
    const [products, setProducts] = useState<Product[]>([]);

    // GET request using fetch
    useEffect(() => {
    fetch('/getProducts')
    .then((res) => res.json())
    .then((data) => {
    setProducts(data);
    });
    }, [products]);

    // GET request using axios and async/await
    // useEffect(() => {
    // (async () => {
    // const { data } = await axios.get<Product[]>("/getProducts");
    // setProducts(data);
    // })();
    // }, [products]);

    const handleFilterTextChange = (e: ChangeEvent<HTMLInputElement>) => {
    setFilterText(e.target.value);
    };

    const handleCheckBoxChange = (e: ChangeEvent<HTMLInputElement>) => {
    setInStockOnly(e.target.checked);
    };

    return (
    <div>
    <SearchBar
    filterText={filterText}
    inStockOnly={inStockOnly}
    handleFilterTextChange={handleFilterTextChange}
    handleCheckBoxChange={handleCheckBoxChange}
    />
    <ProductTable
    products={products}
    filterText={filterText}
    inStockOnly={inStockOnly}
    />
    <NewProduct products={products} setProducts={setProducts} />
    </div>
    );
    };

    export default FilterableProductTable;
    App.tsx (frontend)
    import './App.css';
    import FilterableProductTable from './FilterableProductTable';

    function App() {
    return (
    <div>
    <FilterableProductTable />
    </div>
    );
    }

    export default App;
    - + \ No newline at end of file diff --git a/docs/2021fa/lecture9/index.html b/docs/2021fa/lecture9/index.html index b71bb5bad..f19306871 100644 --- a/docs/2021fa/lecture9/index.html +++ b/docs/2021fa/lecture9/index.html @@ -5,7 +5,7 @@ Lecture 9 | Trends in Web Dev - + @@ -24,7 +24,7 @@ the .gitignore file, it gets excluded from the repository.

    For example, we would add a line containing .env to ignore all files (and folders) named .env. Of course, you could do a lot more complex stuff with the patterns you have at your disposal!

    Learn more about the .gitignore file here.

    Find the .gitignore file for the lecture demo here.

    Feel free to use the following code as-is to load the private key from the environment.

    backend/firebase-config.ts
    import * as admin from 'firebase-admin';
    import { readFileSync } from 'fs';
    import { config } from 'dotenv';

    config();

    const serviceAccountPath = './firebase-adminsdk.json';

    const hydrateServiceAccount = (
    serviceAccountPath: string,
    ): admin.ServiceAccount => {
    const serviceAccount = JSON.parse(
    readFileSync(serviceAccountPath).toString(),
    );
    const privateKey = process.env.FIREBASE_PRIVATE_KEY?.replace(/\\n/g, '\n');
    return { ...serviceAccount, privateKey };
    };

    admin.initializeApp({
    credential: admin.credential.cert(hydrateServiceAccount(serviceAccountPath)),
    });

    const db = admin.firestore();
    const auth = admin.auth();

    export { db, auth };
    note

    firebase-config.ts contains the hydrateServiceAccount function which reads the value from the environment variable, loads up the rest of the firebase-adminsdk json, and combines them together to get a full service account object which you can initialize the Firebase admin with.

    Frontend

    No further setup required in our frontend folder.

    Root

    package.json

    Our root package.json will look like the following. We are using yarn workspaces as described above and the heroku-postbuild script will build both workspaces to prep them for push to remote server. (We discussed yarn workspaces run build above) heroku-postbuild is a special command recognized by Heroku to allow you to customize the build process.

    /package.json
    {
    "name": "products-app",
    "version": "1.0.0",
    "private": true,
    "scripts": {
    "heroku-postbuild": "yarn workspaces run build"
    },
    "workspaces": ["backend", "frontend"],
    "license": "MIT",
    "devDependencies": {
    "heroku": "^7.59.1"
    },
    "resolutions": {
    "@oclif/color": "0.1.0"
    }
    }
    note

    The root package.json can have dependencies and yarn will install them with yarn install just like everything else. You will just need to run yarn add -D -W <pkg_name> since just using yarn add will show a warning message asking if you are sure about adding a dependency to the root. We add Heroku in this case to help with deployment!

    note

    Normally, we don't need the resolution section, but the Heroku CLI is bugged at the time of writing, so we have to add this to our root package.json and then yarn install again as a workaround.

    Read more about heroku-postbuild here.

    Procfile

    We will also have Procfile that tells Heroku what script to run to start the application. In this case we will be using the backend node index.js to start the backend listening for requests and serving the frontend assets from the frontend/build folder.

    Procfile
    web: yarn workspace backend start

    Read more about the Heroku Procfile here.

    Deploy time

    We will now show the series of commands to deploy to Heroku. We will be using Heroku Git to deploy.

    yarn add -D -W heroku
    git init
    git add .
    git commit -m "COMMIT MESSAGE"
    yarn heroku login
    yarn heroku create <optional project name>
    git push heroku master
    (optional) yarn heroku open
    note

    If you run into any bugs when running the code above (probably yarn heroku create <optional project name>), you can create a new project through Heroku's website www.heroku.com

    Visiting the URL should take you to the same application you had locally. Now you can share that link with your friends so they can visit your website too!

    note

    If you run into issues that the app isn't authorized to use authentication, go to your Firebase project on firebase.google.com > Go to Console > [your project] > Authentication [on sidebar] > Sign-in Method > Authorized domains > Add Domain and enter the URL of your deployed site.

    info

    In your final submission of your final project, we want you to follow these steps to deploy your app to Heroku and place the link in the README.md. If you prefer to deploy on another platform feel free but we do not guarantee we know how to debug any issues that come up.

    - + \ No newline at end of file diff --git a/docs/2021fa/setup-editor/index.html b/docs/2021fa/setup-editor/index.html index cd3672326..8d6096085 100644 --- a/docs/2021fa/setup-editor/index.html +++ b/docs/2021fa/setup-editor/index.html @@ -5,7 +5,7 @@ Setup your editor | Trends in Web Dev - + @@ -13,7 +13,7 @@
    Version: 2021fa

    Setup your editor

    For convenience, we assume you will use VSCode. If you are using WebStorm and Atom, you are likely to find some extensions that provide similar functionalities.

    To install extensions in VS Code, navigate to the left-hand sidebar, and click the building blocks icon at the bottom. This should take you to the Extensions marketplace.

    ESLint

    Installing ESLint in VSCode will give you real-time linter feedback in any JavaScript code you write, allowing you to quickly pinpoint many problems and have readable, proper formatting.

    Once installed, add these lines to your VSCode Settings (refer to this link if you need help getting there or alternatively open the command palette in VSCode with CMD/CTRL + SHIFT + P and search settings.json):

      // Other settings ...
    "eslint.alwaysShowStatus": true,
    "editor.codeActionsOnSave": {
    "source.fixAll.eslint": true
    },
    "eslint.packageManager": "yarn"

    Bracket Pair Colorizer

    Highlights matching brackets to make code easier to read.

    npm

    This will be useful later when inspecting package.json files.

    - + \ No newline at end of file diff --git a/docs/2021fa/setup-environment/index.html b/docs/2021fa/setup-environment/index.html index cf3b6edf8..b91a42eca 100644 --- a/docs/2021fa/setup-environment/index.html +++ b/docs/2021fa/setup-environment/index.html @@ -5,14 +5,14 @@ Setup your development environment | Trends in Web Dev - +
    Version: 2021fa

    Setup your development environment

    Install Node.js

    Go to this website and follow the instructions.

    For consistency, we will use Node LTS (currently, as of Spring 2021, this is Node 14).

    Install Yarn

    For convenience, we assume you will use Yarn instead of npm. Yarn gives us the power to create workspaces, which we will discuss in detail later.

    Go to this website and follow the instructions.

    - + \ No newline at end of file diff --git a/docs/2021sp/assignment1/index.html b/docs/2021sp/assignment1/index.html index 9c517ee1b..6eabf616a 100644 --- a/docs/2021sp/assignment1/index.html +++ b/docs/2021sp/assignment1/index.html @@ -5,7 +5,7 @@ Assignment 1 | Trends in Web Dev - + @@ -14,7 +14,7 @@ things like this as a software engineer, it's up to you to make the best possible design decision!

    What's the difference between a route and query parameter?

    Query parameters come after a ? in a route. For example: with /books?name=Megan, Megan' would be in req.query.name. We could make the query parameter name anything we want, from id to pizza and we could access it through req.query.paramName.

    Route parameters are an actual part of the route and come after a :. For example: with /books/:userid, in the request /books/Megan, 'Megan' would be in req.params.userid.

    Two endpoints for the same path?!?!? What's going on?

    This is allowed and not a typo. While you might need to make two endpoints if you need to handle a route's path parameters (say /users and and /users/:id), think about what the query parameters would be from visiting route /book rather than /book?name=megan, and see if you can consolidate the logic into just one endpoint: req.query.name will have some kind of value no matter what, even if you don't pass in a query parameter value.

    Extra aside: you might have heard of REST APIs: they all follow certain design principles that allow developers to know what to expect based on an abstract specification of what the API routes do. Generally, REST APIs use path params to identify a specific resource, and query params to filter those resources.

    I still need help. Where should I go?

    Check out the office hours schedule on our syllabus. In addition, you can join the Ed and post your question.

    - + \ No newline at end of file diff --git a/docs/2021sp/assignment2/index.html b/docs/2021sp/assignment2/index.html index f1b2396ee..ad900aa65 100644 --- a/docs/2021sp/assignment2/index.html +++ b/docs/2021sp/assignment2/index.html @@ -5,7 +5,7 @@ Assignment 2 | Trends in Web Dev - + @@ -46,7 +46,7 @@ same case, and it'll be enough for us.

    Getting a bunch of weird TypeScript errors, and I don't think my code is wrong.

    Make sure to include the tsconfig.json from the lecture 3 notes in your project directory.

    Do we need to submit our service-account.json along with the other stuff?

    We're glad you trust us so much, but we'll be testing your endpoints with our own service-account.json and Firestore database, so no need to include it.

    - + \ No newline at end of file diff --git a/docs/2021sp/assignment3/index.html b/docs/2021sp/assignment3/index.html index e12881f37..7f6003b50 100644 --- a/docs/2021sp/assignment3/index.html +++ b/docs/2021sp/assignment3/index.html @@ -5,7 +5,7 @@ Assignment 3 | Trends in Web Dev - + @@ -23,7 +23,7 @@ documentation, methods are often denoted like: Type.prototype.method()

    Remember, anything in JavaScript/TypeScript can be an object!

    So, we can do: (5).toExponential(10) or let x = 5; x.toExponential()

    Your goal is to round the age to the nearest tenth.

    For example, you want to display "5.6 years" for the value 5.64.

    Take a look at this documentation if you are stuck!

    EXAMPLE

    const doggos = [
    { name: 'Sparky', age: 3.35, breed: 'Pomeranian Husky' },
    { name: 'Oreo', age: 5.42, breed: 'Dalmatian' },
    { name: 'Stella', age: 4, breed: 'Alaskan Klee Kai' },
    ];

    makeSentences(doggos);

    should output

    [
    'Sparky is 3.4 years old and is a Pomeranian Husky',
    'Oreo is 5.4 years old and is a Dalmatian',
    'Stella is 4.0 years old and is a Alaskan Klee Kai',
    ];

    Starter code:

    // TODO: You should replace this any with an accurate object type in your submission!
    type Doggo = any;

    export const makeSentences = (array: Doggo[]): string[] => {
    /* TODO: add your code */
    };

    Don't worry about printing "year" vs "years" or "a" vs "an", unless you want the extra challenge!

    Submission

    Please submit to CMS your index.ts file containing your implementations of each of the functions described above.

    - + \ No newline at end of file diff --git a/docs/2021sp/assignment4/index.html b/docs/2021sp/assignment4/index.html index a1ea44ca3..258bdf69e 100644 --- a/docs/2021sp/assignment4/index.html +++ b/docs/2021sp/assignment4/index.html @@ -5,7 +5,7 @@ Assignment 4 | Trends in Web Dev - + @@ -25,7 +25,7 @@ is illegal. Instead, you have to make a copy of the myNumbers array and use setMyNumbers to alter it. (Hint: you can get a copy of a certain array by using the ES6 spreading syntax [...myNumbers])

    - + \ No newline at end of file diff --git a/docs/2021sp/assignment5/index.html b/docs/2021sp/assignment5/index.html index 76c49a783..62777a75d 100644 --- a/docs/2021sp/assignment5/index.html +++ b/docs/2021sp/assignment5/index.html @@ -5,7 +5,7 @@ Assignment 5 | Trends in Web Dev - + @@ -55,7 +55,7 @@ value to simply [], and it assumes that it's just a falsy value. To fix this, make sure to parametrize the useState hook using the type definition you created; for example, const [state, setState] = useState<myType[]>([]).

    - + \ No newline at end of file diff --git a/docs/2021sp/assignments/index.html b/docs/2021sp/assignments/index.html index 0da78c35d..f439c0b3b 100644 --- a/docs/2021sp/assignments/index.html +++ b/docs/2021sp/assignments/index.html @@ -5,14 +5,14 @@ Assignments | Trends in Web Dev - +
    Version: 2021sp

    Assignments

    Assignments will be released here after lecture! There will be 5 assignments total, as well as a final project spanning the last few weeks of the class.

    You are allowed max 3 slip days (out of 6 total for all assignments and the final project) per an assignment.

    Assignment 1: Due on CMS 3/13 at 3:59pm (Wellness days)

    Assignment 2: Due on CMS 3/18 at 3:59pm

    Assignment 3: Due on CMS 3/25 at 3:59pm

    Assignment 4: Due on CMS 4/1 at 3:59pm

    Assignment 5: Due on CMS 4/8 at 3:59pm

    The class is scheduled to finish well before finals week.

    - + \ No newline at end of file diff --git a/docs/2021sp/finalproject/index.html b/docs/2021sp/finalproject/index.html index 09d15d594..2c6717deb 100644 --- a/docs/2021sp/finalproject/index.html +++ b/docs/2021sp/finalproject/index.html @@ -5,7 +5,7 @@ Final Project | Trends in Web Dev - + @@ -30,7 +30,7 @@ group members and netIDs, a link to the deployed site, a link to the GitHub repo if you used GitHub, and anything else that you think is important for us to know.

    As always, do not include your node_modules.

    Tips for Success!

    • Get in contact with your partner early!
      • Milestone 0 is intended for you to get some discussion on what you want to build before you start implementation. Make sure you are both aligned on what needs to be built to avoid issues later on. Better initial planning means less frustrations later on.
    • Be realistic.
      • We know you are ambitious but also understand your own capabilities. Building something too complex may be too overwhelming. You are allowed to change ideas, but that would be time wasted on the old project.
    • Use GitHub
      • GitHub is the best tool for sharing code between you and your partner/team members. Please use GitHub instead of emailing code back and forth to each other.
    • Use branches!
      • When developing a feature, you should open up a new GitHub branch rather than committing and pushing directly to master. This will allow you to develop your feature independently of the current state of master (and what your partner is doing) and only merge in when you are sure your feature is done and works.
      • Branches can also protect you from weird frustrating merge conflicts (so you can focus on developing awesome features!)
    • Pair programming is fun!
      • Ideally, you should both be actively involved in the whole development process. A good way to achieve this is to step up a time to pair program and code together!
    • Also refer to tips in How to Lose in CS 2112
    - + \ No newline at end of file diff --git a/docs/2021sp/introduction/index.html b/docs/2021sp/introduction/index.html index 9915f1423..8d0e7fc2a 100644 --- a/docs/2021sp/introduction/index.html +++ b/docs/2021sp/introduction/index.html @@ -5,7 +5,7 @@ Syllabus | Trends in Web Dev - + @@ -16,7 +16,7 @@ is due right before class of the following week at 3:59pm unless otherwise stated. You will have 6 slip days total to use on the assignments, but for each assignment, you may only use up to 2 slip days. Use these judiciously because we will not be handling extensions outside of slip days.

    Assignments must be submitted on CMS. We will not take submissions emailed to us. If you are not on the CMS please email Peter (plw53@cornell.edu) or Becky (bh456@cornell.edu)

    What will be taught?

    By the end of the course, students will be have a much more in-depth understanding of JavaScript as it pertains to many common software libraries used in web development. These libraries include (but are not limited to) React, Express, Node.js, Yarn / npm, Express, and Firebase. The exact technologies can shift from semester to semester as demands from students, and in the industry, evolve and change. What is in demand now may not be desired in two years from now. The primary technologies that this class is powered by can shift from semester to semester to reflect what employers are looking for.

    Throughout this course, students will work as individuals and in groups to apply these skills to projects. These are both skills that are extremely important to employers: being able to function independently on assigned tasks, and being able to collaborate with different people on a wide variety of tasks. The idea is to closely resemble milestones and checkpoints in project development, which occur with one to many people.

    What are the prerequisites?

    This course will be covering both client-facing and server-side technologies. CS 1110 or equivalent programming experience is a pre-requisite.

    Please complete the pre-assessment here. It should take less than an hour! This is not meant as a test, but rather a way of ensuring that you are familiar with the foundational material in the course. Upload your submissions as a zip to your application at https://bit.ly/web-dev-sp21. This preassessment is mandatory; those who do not submit it will not be admitted in the course.

    When is it?

    Thursdays at 4:30 - 5:30 PM ET beginning February 25th.

    Where does it meet?

    Zoom

    What is the expected workload?

    Students should expect to work anywhere from 5-6 hours per week in this course.

    What software will be supported in the course?

    You are free to use whichever text editor or programming IDE of your choice. However, we can't guarantee that course staff will be able to directly help you with problems that relate to your editor or IDE.

    How many credits is it?

    Two credits S/U, although students are allowed to audit this course for 0 credits.

    What's the grading policy?

    Attendance - 20% (based on weekly lecture quizzes, can miss 1 of 10 without penalty)

    Filling out Feedback - 10%

    Final project - 20%

    Assignments - 50%

    Keep in mind that you only need a C- (70) or higher to pass. If you ever feel that you are falling behind, please feel free to talk to us and we will try our best to find a solution. You can reach us at plw53@cornell.edu or bh456@cornell.edu.

    Who should I contact with questions?

    Email Peter (plw53@cornell.edu) or Becky (bh456@cornell.edu).

    Policies

    Academic Integrity

    As a programming course, you may find yourself in a position to copy or appropriate code that someone else has written. Please cite any code or media that you do not have direct authorship of on any assignments submitted to the course. Code should receive a citation to the original author as a comment in your source code, while media citations (images, videos) should be visible on the page that they appear.

    Accessibility

    We seek to make this class as inclusive as possible for all students. All lectures will be video recorded for instructor use only. If you have accommodations with Student Disability Services and require access to these recordings, or any other class accommodations, please speak to an instructor before the first lecture and we will work with you to make arrangements as necessary.

    - + \ No newline at end of file diff --git a/docs/2021sp/lecture1/index.html b/docs/2021sp/lecture1/index.html index 7ca23f13b..6384d11e4 100644 --- a/docs/2021sp/lecture1/index.html +++ b/docs/2021sp/lecture1/index.html @@ -5,7 +5,7 @@ Lecture 1 | Trends in Web Dev - + @@ -22,7 +22,7 @@ now; we'll explain what they mean next week.

    yarn init # answer the questions as well
    yarn add typescript
    yarn add ts-node

    We used the following example code. (note that TypeScript files have a .ts extension, as opposed to JavaScript's .js. This will allow VS Code to recognize that you are coding in TS)

    script.ts
    const mySum = (inputArray: number[]): number => {
    let sum: number = 0;
    for (const num of inputArray) {
    sum += num;
    }
    return sum;
    };

    console.log(mySum([1, 2, 3])); // expected 6

    const isLeapYear = (year: number): boolean => {
    return (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;
    };

    console.log(isLeapYear(2000)); // is a leap year
    console.log(isLeapYear(2100)); // is NOT a leap year;

    const perfectSquares = (arr: number[]): number[] => {
    const ans: number[] = [];
    for (const num of arr) {
    if (Math.sqrt(num) % 1 === 0) {
    ans.push(num);
    }
    }
    return ans;
    };

    console.log(perfectSquares([1, 4, 9])); // expected same as input
    console.log(perfectSquares([1, 5, 9])); // expected [1, 9]

    Run it with ts-node script.ts. Voilร ! That's a basic introduction to TypeScript. For more language quirks and useful syntax, visit the TypeScript website and pick the tutorial that best fits you.

    - + \ No newline at end of file diff --git a/docs/2021sp/lecture10/index.html b/docs/2021sp/lecture10/index.html index 5972de2e6..3f05dc8d3 100644 --- a/docs/2021sp/lecture10/index.html +++ b/docs/2021sp/lecture10/index.html @@ -5,13 +5,13 @@ Lecture 10 | Trends in Web Dev - +
    Version: 2021sp

    Lecture 10

    Lecture Slides

    Lecture Video

    Final Project - Milestone 2 due 5/6 3:59 PM

    React Native

    What is React Native?

    React Native allows for cross platform mobile development using a webdev framework we already know--React!

    React Native allows you to build UIs independent of the platform. Usually when developing an app you have to develop an Android version (using Java/Kotlin) and iOS version (using Objective-C/Swift) separately. React Native takes care of this conversion for you.

    Core React Native Components

    Since React Native is really just React, many of the same concepts (useState, props, React Hooks, etc) still apply to React. However, instead of HTML we have Views. A view is the basic building block of UI in mobile development. Views can display images, hold text, handle user input, etc.

    Some core React Native components are:

    • <View>: A container that supports layout with flexbox, style, some touch handling, and accessibility controls. Similar to a non-scrolling <div>.
    • <Text>: Displays, styles, and nests strings of text and even handles touch events. Similar to a <p>
    • <Image>: Displays images like <img>
    • <ScrollView>: A generic scrolling container than can hold nested components and views. Similar to a <div>.
    • <TextInput>: User text input field similar to <input type="text" />.
    • ... and you can also define your own custom components (and use those built by the community)!

    How to start a React Native Project?

    A popular way to use React Native is through the Expo framework, which allows developing, building, and iterating on iOS, Android and webapps. Expo provides a UI for you to view your changes and if you download the Expo app (Android, iOS) you can see those changes on your phone as well! After all, we're doing mobile development.

    To start a React Native project run the following:

    yarn global add expo-cli
    expo init <project name>
    yarn start

    expo init is similar to create-react-app in that it generates boilerplate code for you.

    Demo

    As part of the demo we built the simple TODO list app from assignment 4 in React Native! The code is here:

    App.tsx
    import { StatusBar } from 'expo-status-bar';
    import React, { useState } from 'react';
    import { StyleSheet, Text, View, TextInput, Button } from 'react-native';

    export default function App(): React.ReactElement {
    const [inputItem, setInputItem] = useState<string>('');
    const [items, setItems] = useState<string[]>([]);

    const updateItems = (): void => {
    setItems([...items, inputItem]);
    setInputItem('');
    };

    return (
    <View style={styles.container}>
    <View style={styles.itemsView}>
    {items.map((item, idx) => (
    <Text key={idx}> {item} </Text>
    ))}
    </View>
    <TextInput
    placeholder="Add an item"
    style={styles.input}
    value={inputItem}
    onChangeText={(text) => setInputItem(text)}
    />
    <Button title="Add Item" onPress={() => updateItems()} />
    <StatusBar style="auto">
    </View>
    );
    }

    const styles = StyleSheet.create({
    container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
    },
    input: {
    borderWidth: 1,
    width: 150,
    marginBottom: 10,
    },
    itemsView: {
    borderWidth: 1
    }
    });

    Once you yarn start, you should be taken to Expo where you can view your changes on your browser. On the bottom left, there should also be a QR code. If you download the Expo app and scan the QR code with your phone camera (Android, iOS), it should take you to the Expo app where you can see your application in mobile form!

    Learn more

    This was a very cursory introduction to React Native. You can learn more by referring to React docs.

    React Docs: https://reactnative.dev/docs/getting-started

    Expo Docs: https://docs.expo.io/

    - + \ No newline at end of file diff --git a/docs/2021sp/lecture2/index.html b/docs/2021sp/lecture2/index.html index 83551961f..d84bc3da9 100644 --- a/docs/2021sp/lecture2/index.html +++ b/docs/2021sp/lecture2/index.html @@ -5,7 +5,7 @@ Lecture 2 | Trends in Web Dev - + @@ -20,7 +20,7 @@ Node package manager in its class (this is used in production at many companies like Facebook!), and its corresponding commands for installing packages as well.

    Upon running yarn init, and answering the questions as seen in the previous lecture, we now need to add our dependencies for the project and scripts to start it.

    Installing Dependencies

    Node projects don't come with every possible dependency right out of the box. We will add these with Yarn by using yarn add <pkg_name> (which is the equivalent of npm install <pkg_name> --save, but remember we are using Yarn.)

    Every time you add a dependency with yarn add <pkg_name>, <pkg_name> will be added to your dependencies in package.json if it can be found. It will also be added to node_modules/.

    Take a look inside your node_modules folder. This is where all your packages will be installed. Notice that even though you just installed one package, multiple packages are in package.json. This is because express itself has several of its own dependencies that also got installed.

    You can find more packages to use on npmjs.com.

    Don't Submit node_modules!!

    node_modules can potentially hundreds of megabytes of data on packages you installed. It is important to never submit this with your assignment or push it up to any remote repositories such as GitHub. Before submitting an assignment, remember to remove node_modules from the folder, then zip it and submit the zip file. You will be penalized if node_modules is submitted. Don't worry, we will be able to recover your dependencies simply by running yarn install.

    How to use and interpret package.json

    package.json contains instructions for necessary packages and scripts that you can use to run your code.

    • Dependencies
      • Packages we use in code
      • if not installed on local end, this will cause an error
      • ie. express, ts-node, typescript
    • devDependencies
      • Toolkits used during development to make development easier
      • ie. types, autocompletion

    Live Coding Demo

    We demoed how to set up a yarn project and create some basic getter HTTP routes.

    Set up a yarn project by running yarn init. It will ask you a few questions and you can press return to accept all the defaults:

    yarn init v1.22.5
    question name (lecture2):
    question version (1.0.0):
    question description:
    question entry point (index.ts):
    question repository url:
    question author:
    question license (MIT):
    question private:
    success Saved package.json
    โœจ Done in 37.88s.
    tip

    You can also use yarn init -y to set up a project with all the default values!

    This will generate a package.json file with all the inputs you provided.

    We also want to add several dependencies starting with express. Add express with yarn add express. Since we are also using TypeScript we want to install the typescript and ts-node packages as well using yarn add typescript ts-node. Finally to get some nice autocompletion features, we want to install @types/node and @types/express as devDependencies using yarn add -D @types/node @types/express.

    Your package.json should now look like the following:

    package.json
    {
    "name": "lecture2",
    "version": "1.0.0",
    "main": "index.ts",
    "license": "MIT",
    "dependencies": {
    "express": "^4.17.1",
    "ts-node": "^9.1.1",
    "typescript": "^4.2.2"
    },
    "devDependencies": {
    "@types/express": "^4.17.11",
    "@types/node": "^14.14.31"
    }
    }

    Now we can define some basic express routes in a file index.ts:

    index.ts
    import * as express from 'express';

    const app = express();

    app.get('/home', function (req, res) {
    res.send('Welcome home!');
    });

    // example using path parameters
    app.get('/users/:name/:lname', function (req, res) {
    res.send('Hello ' + req.params.name + ' ' + req.params.lname);
    });

    // example using query parameters
    app.get('/users', function (req, res) {
    res.send('Hello ' + req.query.name + ' ' + req.query.lname);
    });

    // tell express to listen for requests on port 8080
    app.listen(8080, function () {
    console.log('server started');
    });

    Add the following to your package.json:

    // other package.json properties...
    "scripts": {
    "start": "ts-node index.ts"

    Now when you go to localhost:8080/home you should see Welcome home!. At localhost:8080/users/<your_name>/<your_last_name> or localhost:8080/users/?name=<your_name>&lname=<your_last_name> you should see Hello <your_name> <your_last_name>.

    This was Node.js (and Express)!

    - + \ No newline at end of file diff --git a/docs/2021sp/lecture3/index.html b/docs/2021sp/lecture3/index.html index 25ecf49bd..406aec133 100644 --- a/docs/2021sp/lecture3/index.html +++ b/docs/2021sp/lecture3/index.html @@ -5,7 +5,7 @@ Lecture 3 | Trends in Web Dev - + @@ -57,7 +57,7 @@ that the code below does not care about what are the fields of a post, because Firestore doesn't require you to have a predefined set of fields. This gives you flexibility when writing your backend code.

    index.ts
    import admin from 'firebase-admin';
    import express from 'express';

    // require the service account: note the file path
    const serviceAccount = require('../service-account.json');
    // initialize the firebase app
    admin.initializeApp({
    credential: admin.credential.cert(serviceAccount),
    });

    const db = admin.firestore();
    const app = express();
    const port = 8080;
    // allow request body parsing
    app.use(express.json());

    // check connections
    app.get('/', (_, res) => {
    res.send('connected!');
    });

    // create a post type and post with id
    type Post = {
    content: string;
    name: string;
    };

    type PostWithID = Post & {
    id: string;
    };

    // CRUD with firestore
    let posts1: Post[] = [{ content: 'I miss wellness days', name: 'Becky' }];

    // posts collection from db
    const postsCollection = db.collection('posts');

    // GET requests: get the songs
    app.get('/posts', (_, res) => {
    res.send(posts1);
    });

    // use firebase instead
    app.get('/getPosts', async (_, res) => {
    const postsSnapshot = await postsCollection.get();
    const allPostsDoc = postsSnapshot.docs;
    const posts: PostWithID[] = [];
    for (let doc of allPostsDoc) {
    const post: PostWithID = doc.data() as PostWithID;
    post.id = doc.id;
    posts.push(post);
    }
    res.send(posts);
    });

    // read posts by name
    app.get('/posts/:name', async function (req, res) {
    const name = req.params.name;
    const postsSnapshot = await postsCollection.where('name', '==', name).get();
    const allPostsDoc = postsSnapshot.docs;
    const posts: PostWithID[] = [];
    for (let doc of allPostsDoc) {
    const post: PostWithID = doc.data() as PostWithID;
    post.id = doc.id;
    posts.push(post);
    }
    res.send(posts);
    });

    // read posts by id
    app.get('/postById/:id', async function (req, res) {
    const id = req.params.id;
    const postsSnapshot = await postsCollection.doc(id).get();
    const post: PostWithID = postsSnapshot.data() as PostWithID;
    res.send(post);
    });

    // sort posts by name
    app.get('/postsorted', async function (req, res) {
    const postsSnapshot = await postsCollection.orderBy('name', 'desc').get();
    const allPostsDoc = postsSnapshot.docs;
    const posts: PostWithID[] = [];
    for (let doc of allPostsDoc) {
    const post: PostWithID = doc.data() as PostWithID;
    post.id = doc.id;
    posts.push(post);
    }
    res.send(posts);
    });

    // POST method: create a new post
    app.post('/addPost', (req, res) => {
    const post: Post = req.body;
    posts1.push(post);
    res.send(`Post created by ${req.body.name}!`);
    });

    // generate a document with a random name to store the post's data
    app.post('/addPost2', async function (req, res) {
    const post: Post = req.body;
    const postDoc = postsCollection.doc();
    await postDoc.set(post);
    res.send(postDoc.id);
    });

    // POST method: update an existing post
    app.post('/updatePost', (req, res) => {
    for (let post of posts1) {
    if (post.name === req.body.name) {
    post.content = req.body.content;
    }
    }
    console.log(posts1);
    res.send('content updated!');
    });

    // update by id
    app.post('/updatePost2/:id', async function (req, res) {
    const newPost: Post = req.body;
    const id: string = req.params.id;
    await postsCollection.doc(id).update(newPost);
    res.send('updated!');
    });

    // DELETE methdod: delete a post
    app.delete('/removePost', (req, res) => {
    const newPosts = [];
    for (let post of posts1) {
    if (post.name !== req.body.name) {
    newPosts.push(post);
    }
    }
    posts1 = newPosts;
    res.send(`Post by ${req.body.name} deleted!`);
    });

    // delete by id
    app.delete('/removePost/:id', async function (req, res) {
    const id = req.params.id;
    await postsCollection.doc(id).delete();
    res.send('deleted!');
    });

    app.listen(port, () => console.log('App started!'));
    - + \ No newline at end of file diff --git a/docs/2021sp/lecture4/index.html b/docs/2021sp/lecture4/index.html index aefedcbb3..8c23532fe 100644 --- a/docs/2021sp/lecture4/index.html +++ b/docs/2021sp/lecture4/index.html @@ -5,7 +5,7 @@ Lecture 4 | Trends in Web Dev - + @@ -58,7 +58,7 @@ The series is comprehensive and will teach you everything you want to know.

    Demo Code

    Need more examples? This code from the lecture's live coding demo rewrites some functions from the preassessment first using loops then with map, filter, and reduce.

    index.ts
    // getSqrts: takes in an array and returns an array with all the square roots
    // of those numbers
    // example: [1, 4, 9] => [1, 2, 3]
    const getSqrts = (arr: number[]): number[] => {
    const result: number[] = [];
    for (const num of arr) {
    result.push(Math.sqrt(num));
    }
    return result;
    };
    const getSqrtsMap = (arr: number[]): number[] => {
    return arr.map(Math.sqrt);
    };
    // perfectSquares: takes in an array and returns an array with only the
    // elements that are perfect squares
    // example: [1, 2, 3] => [1]
    const perfectSquares = (arr: number[]): number[] => {
    const result: number[] = [];
    for (const num of arr) {
    if (Math.sqrt(num) % 1 === 0) {
    result.push(num);
    }
    }
    return result;
    };
    const isPerfectSquare = (num: number) => Math.sqrt(num) % 1 === 0;
    const perfectSquaresFilter = (arr: number[]): number[] => {
    return arr.filter(isPerfectSquare);
    };
    // mySum: takes in an array and returns the sum of the elements
    // example: [1, 2, 3] => 6
    const mySum = (arr: number[]): number => {
    let sum = 0;
    for (const num of arr) {
    sum += num;
    }
    return sum;
    };
    const mySumReduce = (arr: number[]): number => {
    return arr.reduce((acc: number, curr: number) => acc + curr);
    };
    // testing!
    const input = [1, 2, 3];
    console.log(getSqrts(input));
    console.log(getSqrtsMap(input));
    console.log(perfectSquares(input));
    console.log(perfectSquaresFilter(input));
    console.log(mySum(input));
    console.log(mySumReduce(input));
    - + \ No newline at end of file diff --git a/docs/2021sp/lecture5/index.html b/docs/2021sp/lecture5/index.html index 81309774f..8b74f2277 100644 --- a/docs/2021sp/lecture5/index.html +++ b/docs/2021sp/lecture5/index.html @@ -5,7 +5,7 @@ Lecture 5 | Trends in Web Dev - + @@ -26,7 +26,7 @@ In this particular example, you should only use name as the key if you know that the property name is unique. However, if there are multiple objects with the same name in the list that are used as a key, it would confuse React.

    - + \ No newline at end of file diff --git a/docs/2021sp/lecture6/index.html b/docs/2021sp/lecture6/index.html index 4213aa062..69c229152 100644 --- a/docs/2021sp/lecture6/index.html +++ b/docs/2021sp/lecture6/index.html @@ -5,7 +5,7 @@ Lecture 6 | Trends in Web Dev - + @@ -34,7 +34,7 @@ based on conditions a lot easier. In this small example, we went from five lines of code in the component to just one!

    Composition vs. Inheritance

    Composition and inheritance are two programming techniques for defining how classes relate to objects. (Think of classes as the blueprint for a house and objects the actual houses created from that blueprint)

    Composition

    Composition defines a class as the sum of its individual parts. This is a "has-a" relationship (e.g. a car has a steering wheel, has a window, etc). In Java (and other object oriented languages), these components are represented as instance variables.

    Inheritance

    Inheritance derives one class from another. If class A is the parent of class B and C, B and C inherit the properties/functions of A. This is a "is-a" relationship (e.g. car is a vehicle, circle is a shape.)

    React uses Composition

    If React were to use inheritance, the children components would essentially be very similar to their parent, which doesn't make much sense. Instead, we use composition so that the parent compoens is the sum of their children.

    โ€œReact has a powerful composition model, and we recommend using composition instead of inheritance to reuse code between components.โ€ -- React Docs

    Containment

    Children are the components you put within another component:

    <ComponentA>{/* anything here is a child of Component A */}</ComponentA>

    Components may not know their children ahead of time. If we want to create a component A in component B and we want A to have some children components C, D, E... A doesn't know what components are there beforehand. Instead we can pass a children prop from B to A to pass in the children components for A.

    Container.tsx
    import { ReactNode } from 'react';
    type Props = { readonly children: ReactNode };
    const Container = (props: Props) => (
    <div className="Border">{props.children}</div>
    );
    App.tsx
    const App = () => (
    <div className="App">
    <Container>
    <p>Hello!</p>
    <p>Bye!</p>
    </Container>
    </div>
    );

    There are two paragraph elements <p></p> between the <Container></Container> tag. Those two elements will be passed to Container as props.children.

    We didn't actually get to this live demo, adapted from this tutorial in the React docs, during lecture but it is very simple if you want to try it out yourself. We also show how to import styles.

    Less common but you also may want multiple "holes" in your component (for example, a left and right child):

    SplitPane.tsx
    import { ReactNode } from 'react';
    import './SplitPane.css'; // this is how we import styles

    type Props = { readonly left: ReactNode; readonly right: ReactNode };

    const SplitPane = (props: Props) => (
    <div>
    <div className="LeftPane">{props.left}</div>
    <div className="RightPane">{props.right}</div>
    </div>
    );

    export default SplitPane;
    SplitPane.css
    /* these colors are ugly I know */
    .LeftPane {
    float: left;
    width: 50%;
    background-color: red;
    }

    .RightPane {
    float: right;
    width: 50%;
    background-color: aquamarine;
    }
    App.tsx
    import SplitPane from './SplitPane';
    import Container from './Container';

    const App = () => {
    return (
    <div className="App">
    <Container>
    <p>Hello, world!</p>
    </Container>
    <SplitPane
    left={<div>I'm on the left!</div>}
    right={<div>I'm on the right!</div>}
    />
    </div>
    );
    };

    export default App;

    Lifting State Up

    Recall containment, which says that the parents don't know their children components beforehand. This brings up some issues when we do need the parents to access some states in the children components. The way we solve this problem is by lifting the states up.

    This section was a live demo, adapted from this tutorial in the React docs. In this demo we will create a convertor between Celsius and Fahrenheit. We will create two components, FahrenheitInput and CelsiusInput. They will each keep a state that stores the current temperature in its corresponding unit. However, this is not enough. Once we change the temperature input in a certain component, we need to update the other so that they will be on the same page. The way we do that is through lifting the states up to store the temperature in App. We pass the temperature setter as callbacks to each of the children components.

    App.tsx
    import { useState } from 'react';
    import './App.css';
    import FahrenheitInput from './FahrenheitInput';
    import CelsiusInput from './CelsiusInput';

    function App() {
    const [temperature, setTemperature] = useState(-40);

    return (
    <div className="App">
    <label>Fahrenheit:</label>
    <FahrenheitInput
    temperature={temperature}
    callback={(temp) => setTemperature(temp)}
    />
    <br />
    <label>Celsius:</label>
    <CelsiusInput
    temperature={temperature}
    callback={(temp) => setTemperature(temp)}
    />
    <br />
    {temperature >= 100 ? (
    <span>Water would boil here!</span>
    ) : (
    <span>Water would not boil here!</span>
    )}
    <br />
    <span>Water would {temperature >= 0 && 'not'} freeze here!</span>
    </div>
    );
    }

    export default App;
    CelsiusInput.tsx
    import { ChangeEvent } from 'react';

    type Props = {
    readonly temperature: number;
    readonly callback: (temperature: number) => void;
    };

    const CelsiusInput = ({ temperature, callback }: Props) => {
    const handlechange = (e: ChangeEvent<HTMLInputElement>) =>
    callback(parseInt(e.target.value) || 0);

    return <input value={temperature} onChange={handlechange} />;
    };

    export default CelsiusInput;
    FahrenheitInput.tsx
    type Props = {
    readonly temperature: number;
    readonly callback: (temperature: number) => void;
    };

    const FahrenheitInput = ({ temperature, callback }: Props) => {
    const handlechange = (e: ChangeEvent<HTMLInputElement>) =>
    callback((((parseInt(e.target.value) || 0) - 32) * 5) / 9);

    return <input value={(temperature * 9) / 5 + 32} onChange={handlechange} />;
    };

    export default FahrenheitInput;

    Check lecture video for a more detailed explanation.

    - + \ No newline at end of file diff --git a/docs/2021sp/lecture7/index.html b/docs/2021sp/lecture7/index.html index cc98c3702..36efeebb8 100644 --- a/docs/2021sp/lecture7/index.html +++ b/docs/2021sp/lecture7/index.html @@ -5,7 +5,7 @@ Lecture 7 | Trends in Web Dev - + @@ -69,7 +69,7 @@ parent component
  • Connect component to the backend (more on this in Lecture 8!)
  • Filterable Product Table Example

    This section contains the code from the live demo presented during class. Watch the lecture video linked at the top for an explanation of the code, intended to teach how to think in the React development paradigm.

    App.tsx (root component)
    import React from 'react';
    import FilterableProductTable from './FilterableProductTable';

    const PRODUCTS = [
    {
    category: 'Sporting Goods',
    price: '$49.99',
    stocked: true,
    name: 'Football',
    },
    {
    category: 'Sporting Goods',
    price: '$9.99',
    stocked: true,
    name: 'Baseball',
    },
    {
    category: 'Sporting Goods',
    price: '$29.99',
    stocked: false,
    name: 'Basketball',
    },
    {
    category: 'Electronics',
    price: '$99.99',
    stocked: true,
    name: 'iPod Touch',
    },
    {
    category: 'Electronics',
    price: '$399.99',
    stocked: false,
    name: 'iPhone 5',
    },
    {
    category: 'Electronics',
    price: '$199.99',
    stocked: true,
    name: 'Nexus 7',
    },
    ];

    const App = () => (
    <div className="App">
    <FilterableProductTable products={PRODUCTS} />
    </div>
    );

    export default App;
    FilterableProductTable.tsx
    import { ChangeEvent, useEffect, useState } from 'react';
    import ProductTable, { Product } from './ProductTable';

    type SearchProps = {
    readonly filterText: string;
    readonly inStockOnly: boolean;
    readonly handleFilterTextChange: (e: ChangeEvent<HTMLInputElement>) => void;
    readonly handleCheckBoxChange: (e: ChangeEvent<HTMLInputElement>) => void;
    };

    const SearchBar = ({
    filterText,
    inStockOnly,
    handleFilterTextChange,
    handleCheckBoxChange,
    }: SearchProps) => (
    <form>
    <input
    type="text"
    placeholder="search here"
    value={filterText}
    onChange={handleFilterTextChange}
    />
    <p>
    <input
    type="checkbox"
    checked={inStockOnly}
    onChange={handleCheckBoxChange}
    />
    Only show products in stock
    </p>
    </form>
    );

    type TableProps = {
    readonly products: Product[];
    };

    const FilterableProductTable = ({ products }: TableProps) => {
    const [filterText, setFilterText] = useState('');
    const [inStockOnly, setInStockOnly] = useState(false);
    const [loading, setLoading] = useState(false);

    const handleFilterTextChange = (e: ChangeEvent<HTMLInputElement>) =>
    setFilterText(e.target.value);
    const handleCheckBoxChange = (e: ChangeEvent<HTMLInputElement>) =>
    setInStockOnly(e.target.checked);

    useEffect(() => {
    setLoading(true);
    setTimeout(() => setLoading(false), 3000);
    }, [inStockOnly]);

    return loading ? (
    <div>loading</div>
    ) : (
    <div>
    <SearchBar
    filterText={filterText} // states passed as prop to SearchBar
    inStockOnly={inStockOnly} // states passed as prop to SearchBar
    handleFilterTextChange={handleFilterTextChange} // pass down callbacks to update search state
    handleCheckBoxChange={handleCheckBoxChange}
    />
    <ProductTable
    products={products} // JSON API model
    filterText={filterText} // states passed as prop to SearchBar
    inStockOnly={inStockOnly} // states passed as prop to SearchBar
    />
    </div>
    );
    };

    export default FilterableProductTable;
    Starter.tsx
    // Contains all the base components (we can put multiple components in a jsx file
    // for convenience, though this is not usually good practice).
    import React, { ReactElement } from 'react';
    // These components will be starter code because they are most self-explanatory
    // and purely presentational. We will go over this code briefly in lecture.
    // Students encouraged to read this on their own time.

    // Export since this is used in FilterableProductTable as well
    export type Product = {
    readonly category: string;
    readonly price: string;
    readonly stocked: boolean;
    readonly name: string;
    };

    const ProductRow = (product: Product) => {
    const name = product.stocked ? (
    product.name
    ) : (
    <span style={{ color: 'red' }}>{product.name}</span>
    );
    return (
    <tr>
    <td>{name}</td>
    <td>{product.price}</td>
    </tr>
    );
    };

    type RowProps = {
    readonly category: string;
    };

    const ProductCategoryRow = ({ category }: RowProps) => (
    <tr>
    <th colSpan={2}>{category}</th>
    </tr>
    );

    type Props = {
    readonly products: Product[];
    readonly filterText: string;
    readonly inStockOnly: boolean;
    };

    const ProductTable = ({ products, filterText, inStockOnly }: Props) => {
    const rows: ReactElement[] = [];
    let lastCategory: string | null = null;

    products.forEach((product) => {
    if (product.name.indexOf(filterText) === -1) {
    return;
    }
    if (inStockOnly && !product.stocked) {
    return;
    }
    if (product.category !== lastCategory) {
    rows.push(
    <ProductCategoryRow
    category={product.category}
    key={product.category}
    />,
    );
    }
    rows.push(<ProductRow key={product.name} {...product} />);
    lastCategory = product.category;
    });

    return (
    <table>
    <thead>
    <tr>
    <th>Name</th>
    <th>Price</th>
    </tr>
    </thead>
    <tbody>{rows}</tbody>
    </table>
    );
    };

    // Here we can export all these components at once!
    // Notice also the name of the file does not match any single component name.
    // export { ProductRow, ProductCategoryRow, ProductTable };
    export default ProductTable; // but we only need ProductTable here (in FilterableProductTable.jsx)
    - + \ No newline at end of file diff --git a/docs/2021sp/lecture8/index.html b/docs/2021sp/lecture8/index.html index 40e6b05a8..370c82a4a 100644 --- a/docs/2021sp/lecture8/index.html +++ b/docs/2021sp/lecture8/index.html @@ -5,7 +5,7 @@ Lecture 8 | Trends in Web Dev - + @@ -60,7 +60,7 @@ you have.

    You update your data by calling an endpoint within useEffect and setting your data to the response that you get back.

    You can call endpoints using fetch() or axios and handle the responses asynchronously.

    Demo Code

    Backend

    index.ts (backend)
    import admin from 'firebase-admin';
    import express from 'express';

    const serviceAccount = require('../service-account.json');

    admin.initializeApp({
    credential: admin.credential.cert(serviceAccount),
    });

    const db = admin.firestore();

    const app = express();
    const port = 8080;
    app.use(express.json());

    type Product = {
    category: string;
    price: string;
    stocked: boolean;
    name: string;
    };

    type ProductWithID = Product & {
    id: string;
    };

    const productsCollection = db.collection('products');

    app.get('/getProducts', async (_, res) => {
    const products = await productsCollection.orderBy('category').get();
    res.json(
    products.docs.map((doc): ProductWithID => {
    const product = doc.data() as Product;
    return { ...product, id: doc.id };
    }),
    );
    });

    app.post('/createProduct', async (req, res) => {
    const newProduct: Product = req.body;
    const addedProduct = await productsCollection.add(newProduct);
    res.send(addedProduct.id);
    });

    app.post('/updatePrice', async (req, res) => {
    const { id, rating } = req.query;
    await productsCollection.doc(id as string).update({ rating });
    res.send('Product updated!');
    });

    app.delete('/deleteProduct', async (req, res) => {
    const { id } = req.query;
    await productsCollection.doc(id as string).delete();
    res.send('Product deleted!');
    });

    app.listen(port, () => console.log(`Example app listening on port ${port}!`));
    ProductTable.tsx (frontend)
    import { ReactElement } from 'react';

    export type Product = {
    readonly category: string;
    readonly price: string;
    readonly stocked: boolean;
    readonly name: string;
    readonly id: string;
    };

    type ProductRowProps = {
    product: Product;
    products: Product[];
    };

    // new code 3
    const ProductRow = ({ product, products }: ProductRowProps) => {
    const name = product.stocked ? (
    product.name
    ) : (
    <span style={{ color: 'red' }}>{product.name}</span>
    );

    // DELETE request using fetch
    const deleteProduct = () => {
    fetch(`/deleteProduct?id=${product.id}`, {
    method: 'DELETE',
    headers: {
    'content-type': 'application/json',
    },
    });
    };

    // DELETE request using axios and async/await
    // const deleteProduct = async () => {
    // await axios.delete('/deleteProduct?id=${product.id}');
    // }

    return (
    <tr>
    <td>{name}</td>
    <td>{product.price}</td>
    <td>
    <button onClick={() => deleteProduct()}>delete</button>
    </td>
    </tr>
    );
    };

    type ProductCategoryRowProps = {
    readonly category: string;
    };

    const ProductCategoryRow = ({ category }: ProductCategoryRowProps) => (
    <tr>
    <th colSpan={3}>{category}</th>
    </tr>
    );

    type Props = {
    readonly products: Product[];
    readonly filterText: string;
    readonly inStockOnly: boolean;
    };

    const ProductTable = ({ products, filterText, inStockOnly }: Props) => {
    const rows: ReactElement[] = [];
    let lastCategory: string | null = null;

    products.forEach((product) => {
    if (product.name.indexOf(filterText) === -1) return;
    if (inStockOnly && !product.stocked) return;
    if (product.category !== lastCategory) {
    rows.push(
    <ProductCategoryRow
    category={product.category}
    key={product.category}
    />,
    );
    }
    rows.push(
    <ProductRow key={product.name} product={product} products={products} />,
    );
    lastCategory = product.category;
    });

    return (
    <table>
    <thead>
    <th>Name</th>
    <th>Price</th>
    </thead>
    <tbody>{rows}</tbody>
    </table>
    );
    };

    export default ProductTable;
    FilterableProductTable.tsx (frontend)
    import { ChangeEvent, useEffect, useState } from 'react';
    import ProductTable, { Product } from './ProductTable';

    type SearchProps = {
    readonly filterText: string;
    readonly inStockOnly: boolean;
    readonly handleFilterTextChange: (e: ChangeEvent<HTMLInputElement>) => void;
    readonly handleCheckBoxChange: (e: ChangeEvent<HTMLInputElement>) => void;
    };

    const SearchBar = ({
    filterText,
    inStockOnly,
    handleFilterTextChange,
    handleCheckBoxChange,
    }: SearchProps) => (
    <form>
    <input
    type="text"
    placeholder="search here"
    value={filterText}
    onChange={handleFilterTextChange}
    />
    <p>
    <input
    type="checkbox"
    checked={inStockOnly}
    onChange={handleCheckBoxChange}
    />
    Only show the products in stock
    </p>
    </form>
    );

    type NewProductProps = {
    products: Product[];
    setProducts: React.Dispatch<React.SetStateAction<Product[]>>;
    };

    const NewProduct = ({ products, setProducts }: NewProductProps) => {
    const [category, setCategory] = useState('');
    const [price, setPrice] = useState('');
    const [stocked, setStocked] = useState(true);
    const [name, setName] = useState('');

    // POST request using fetch
    const createProduct = () => {
    const newProduct = {
    category: category,
    price: price,
    stocked: stocked,
    name: name,
    };
    fetch('/createProduct', {
    method: 'POST',
    headers: {
    'content-type': 'application/json',
    },
    body: JSON.stringify(newProduct),
    })
    .then((res) => res.text())
    .then((data) => {
    const newProductWithID = { ...newProduct, id: data };
    setProducts([...products, newProductWithID]);
    });
    };

    // POST request using axios and async/await
    // const createProduct = async () => {
    // const newProduct = { category, price, stocked, name };
    // const { data } = await axios.post<string>('/createProduct', newProduct);
    // const newProductWithID = { ...newProduct, id: data };
    // setProducts([...products, newProductWithID]);
    // }

    return (
    <div>
    <p>new product</p>
    <input
    type="text"
    name="category"
    placeholder="category..."
    value={category}
    onChange={(e) => setCategory(e.target.value)}
    />
    <input
    type="text"
    name="price"
    placeholder="price..."
    value={price}
    onChange={(e) => setPrice(e.target.value)}
    />
    <select
    name="stock"
    onChange={(e) => setStocked(e.target.selectedIndex === 0)}
    >
    <option value="true">in stock</option>
    <option value="false">out of stock</option>
    </select>
    <input
    type="text"
    name="name"
    placeholder="name..."
    value={name}
    onChange={(e) => setName(e.target.value)}
    />
    <button onClick={() => createProduct()}>add a product</button>
    </div>
    );
    };

    const FilterableProductTable = () => {
    const [filterText, setFilterText] = useState('');
    const [inStockOnly, setInStockOnly] = useState(false);
    const [products, setProducts] = useState<Product[]>([]);

    // GET request using fetch
    useEffect(() => {
    fetch('/getProducts')
    .then((res) => res.json())
    .then((data) => {
    setProducts(data);
    });
    }, [products]);

    // GET request using axios and async/await
    // useEffect(() => {
    // const { data } = await axios.get<readonly Product[]>('/getProducts');
    // setProducts(data)
    // }, [products]);

    const handleFilterTextChange = (e: ChangeEvent<HTMLInputElement>) => {
    setFilterText(e.target.value);
    };

    const handleCheckBoxChange = (e: ChangeEvent<HTMLInputElement>) => {
    setInStockOnly(e.target.checked);
    };

    return (
    <div>
    <SearchBar
    filterText={filterText}
    inStockOnly={inStockOnly}
    handleFilterTextChange={handleFilterTextChange}
    handleCheckBoxChange={handleCheckBoxChange}
    />
    <ProductTable
    products={products}
    filterText={filterText}
    inStockOnly={inStockOnly}
    />
    <NewProduct products={products} setProducts={setProducts} />
    </div>
    );
    };

    export default FilterableProductTable;
    - + \ No newline at end of file diff --git a/docs/2021sp/lecture9/index.html b/docs/2021sp/lecture9/index.html index b8db68d9d..1bbf90937 100644 --- a/docs/2021sp/lecture9/index.html +++ b/docs/2021sp/lecture9/index.html @@ -5,7 +5,7 @@ Lecture 9 | Trends in Web Dev - + @@ -20,7 +20,7 @@ if user_input were something like '; DROP TABLE comments;.

    Luckily for us, React DOM escapes values embedded in JSX before rendering them by default, preventing injection attacks.

    3. Don't Rely on Client Side Verification

    In general you cannot assume your endpoint will only be used by your frontend.

    Recall that we used Postman at the beginning of the semester to test Express endpoints. As such, it is always important to verify your inputs and authenticate your requests!

    4. Separation of Privileges

    Going hand-in-hand with the last point, when a request is made to your backend, make sure that the source of your request has the sufficient permissions to perform whatever action is being asked!

    For example, if the OfficeHours app recieved a request to edit the times to someone's office hours, the backend would check that this person is indeed the TA who owns that OH and not a student.

    Let's hack our app!

    In the lecture we didn't have time to show you guys how we could hack our application. However, if you are interested you can do so by sending in bad unauthenticated input data.

    We fixed the backend by using Firebase Admin's verifyIdToken function to check whether the user had logged in or not.

    Backend

    On the backend, we had the post endpoints check the idtoken in the request headers.

    backend/index.ts
    app.post('/createProduct', async (req, res) => {
    admin
    .auth()
    .verifyIdToken(req.headers.idtoken as string)
    .then(async () => {
    const newProduct: Product = req.body;
    const addedProduct = await productsCollection.add(newProduct);
    res.send(addedProduct.id);
    })
    .catch(() => res.send('auth error'));
    });

    We call admin.auth().verifyIdToken(req.headers.idtoken as string) and only then do we allow the input to be saved. Otherwise, we log the error.

    Frontend

    Now when we send the request back, we need to send the idtoken with the request header. So in the createProduct method that called the POST endpoints above, we send call firebase.auth().currentUser?.getIdToken(true) to get the user's idtoken. Then we pass it in as a field to headers in the fetch. However, if the currentUser is undefined for some reason (what the ? catches), then we console that the user isn't authenticated. Everything else should be familiar from last lecture.

    frontend/src/FilterableProductTable.tsx
    const createProduct = () => {
    const newProduct = {
    category: category,
    price: price,
    stocked: stocked,
    name: name,
    };

    firebase
    .auth()
    .currentUser?.getIdToken(true)
    .then((idtoken) => {
    fetch('/createProduct', {
    method: 'POST',
    headers: {
    'content-type': 'application/json',
    idtoken,
    },
    body: JSON.stringify(newProduct),
    })
    .then((res) => res.text())
    .then((data) => {
    const newProductWithID = { ...newProduct, id: data };
    setProducts([...products, newProductWithID]);
    });
    })
    .catch(() => console.log('not authenticated'));
    };

    Security Best Practices

    There is a lot more we could've done in terms of security here.

    1. We should send back error messages to the user about why requests are failing instead of just doing console.log() since the user won't see that. This would be bad user experience since they don't have feedback about why things aren't working.
    2. We should also do input validation on the backend to ensure we are getting our expected inputs. What if we aren't passing an object that looks like:
    {
    "category": string
    "price": string
    "stocked": boolean
    "name": string
    }

    Deployment

    To deploy your web application means to put it on a Web server so others can access it via the internet. We will deploy both our frontend and backend on Heroku. There are a variety of services you can use for deployment including Firebase, Amazon AWS, Microsoft Azure, etc, but we decided to show you Heroku deployment since it is easier and requires less setup.

    We will be taking our filterable product table app we have been building throughout the course and deploying it to a remote server. To deploy your own app (for final project for example), you can follow the same steps usually.

    Deployment Setup

    Backend

    index.ts

    To deploy to Heroku we need some additional setup. We will be serving our frontend via our backend so express should grab files from the frontend/build folder. In the frontend folder, when you run yarn build, you will get a build directory containing all the optimized, bundled frontend React code ready to be pushed to a remote server. We will include the line app.use(express.static(path.join(__dirname, '../frontend/build'))); to do this. (Note: this requires us to import path from 'path';)

    We will also be calling upon our express app to use CORS to allow cross origin requests. If your backend requests are being blocked be of some CORS cross origin policy issue you probably forgot to include the app.use(cors()); line. (Note: this requires us to import cors from 'cors';)

    The beginning of your backend/index.ts should look like the below. Note the CORS line (line 12) and express static serving line (line 13) we described above and the relevant import statements.

    backend/index.ts
    import express from 'express';
    import admin from 'firebase-admin';
    import cors from 'cors';
    import path from 'path';

    const serviceAccount = require('./serviceAccount.json');

    admin.initializeApp({
    credential: admin.credential.cert(serviceAccount),
    });

    const app = express();
    app.use(cors());
    app.use(express.static(path.join(__dirname, '../frontend/build')));
    app.use(express.json());
    const db = admin.firestore();

    const productsCollection = db.collection('products');

    app.get('/getProducts', async (_, res) => {
    const products = await productsCollection.orderBy('category').get();
    res.json(
    products.docs.map((doc): ProductWithID => {
    const product = doc.data() as Product;
    return { ...product, id: doc.id };
    }),
    );
    });

    // other routes...
    app.listen(process.env.PORT || 8080, () => console.log('backend started'));
    note

    Notice we are having app.listen listen for either port 8080 or process.env.PORT. Previously, we hardcoded 8080 for this parameter. process.env.PORT will be defined by Heroku and we want the app to listen for requests on that port in the deployed site.

    backend/package.json

    Our backend/package.json will look like this since we need to compile the TypeScript to JavaScript first. The build step calls tsc the TypeScript compiler to compile the index.ts to index.js. You should see a new index.js file after you run yarn build.

    note
    1. We are using the same tsconfig.json as found in lecture2.
    2. Remember to have all of the packages you installed inside dependencies, putting them into devDependencies will cause your deployment to crash. :::
    backend/package.json
    {
    "name": "backend",
    "version": "1.0.0",
    "main": "index.js",
    "license": "MIT",
    "scripts": {
    "build": "tsc -p tsconfig.json",
    "start": "yarn build && node index.js"
    },
    "dependencies": {
    "cors": "^2.8.5",
    "express": "^4.17.1",
    "firebase-admin": "^9.2.0",
    "heroku": "^7.52.0",
    "@types/cors": "^2.8.10",
    "@types/express": "^4.17.8",
    "@types/node": "^14.11.5",
    "typescript": "^4.0.3"
    }
    }

    Frontend

    No further setup required in our frontend folder.

    Root

    package.json

    Our root package.json will look like the following. We are using yarn workspaces as described above and the heroku-postbuild script will build both workspaces to prep them for push to remote server. (We discussed yarn workspaces run build above) heroku-postbuild is a special command recognized by Heroku to allow you to customize the build process.

    /package.json
    {
    "name": "products-app",
    "private": true,
    "scripts": {
    "heroku-postbuild": "yarn workspaces run build"
    },
    "workspaces": ["backend", "frontend"],
    "version": "1.0.0",
    "main": "index.js",
    "license": "MIT",
    "devDependencies": {
    "heroku": "^7.52.0"
    }
    }

    The root package.json can have dependencies and yarn will install them with yarn install just like everything else. You will just need to run yarn add -W <pkg_name> since just using yarn add will show a warning message asking if you are sure about adding a dependency to the root. We add Heroku in this case to help with deployment! :::

    Read more about heroku-postbuild here.

    Procfile

    We will also have Procfile that tells Heroku what script to run to start the application. In this case we will be using the backend node index.js to start the backend listening for requests and serving the frontend assets from the frontend/build folder.

    Procfile
    web: yarn workspace backend start

    Read more about the Heroku Procfile here.

    Deploy time

    We will now show the series of commands to deploy to Heroku. We will be using Heroku Git to deploy.

    yarn add heroku
    git init
    git add .
    git commit -m "COMMIT MESSAGE"
    yarn heroku login
    yarn heroku create <optional project name>
    git push heroku master
    (optional) yarn heroku open
    note

    If you run into any bugs when running the code above (probably yarn heroku create <optional project name>), you can create a new project through Heroku's website www.heroku.com

    Visiting the URL should take you to the same application you had locally. Now you can share that link with your friends so they can visit your website too! Take a look at our deployed site here: https://lec9-demo.herokuapp.com/ (If you have issues deploying feel free to submit this link for the attendance question, but please do come to office hours first!)

    note

    If you run into issues that the app isn't authorized to use authentication, go to your Firebase project on firebase.google.com > Go to Console > [your project] > Authentication [on sidebar] > Sign-in Method > Authorized domains > Add Domain and enter the URL of your deployed site.

    info

    In your final submission of your final project, we want you to follow these steps to deploy your app to Heroku and place the link in the README.md. If you prefer to deploy on another platform feel free but we do not guarantee we know how to debug any issues that come up.

    - + \ No newline at end of file diff --git a/docs/2021sp/setup-editor/index.html b/docs/2021sp/setup-editor/index.html index 0850fbaa8..ae815f41e 100644 --- a/docs/2021sp/setup-editor/index.html +++ b/docs/2021sp/setup-editor/index.html @@ -5,7 +5,7 @@ Setup your editor | Trends in Web Dev - + @@ -13,7 +13,7 @@
    Version: 2021sp

    Setup your editor

    For convenience, we assume you will use VSCode. If you are using WebStorm and Atom, you are likely to find some extensions that provide similar functionalities.

    To install extensions in VS Code, navigate to the left-hand sidebar, and click the building blocks icon at the bottom. This should take you to the Extensions marketplace.

    ESLint

    Installing ESLint in VSCode will give you real-time linter feedback in any JavaScript code you write, allowing you to quickly pinpoint many problems and have readable, proper formatting.

    Once installed, add these lines to your VSCode Settings (refer to this link if you need help getting there or alternatively open the command palette in VSCode with CMD/CTRL + SHIFT + P and search settings.json):

      // Other settings ...
    "eslint.alwaysShowStatus": true,
    "editor.codeActionsOnSave": {
    "source.fixAll.eslint": true
    },
    "eslint.packageManager": "yarn"

    Bracket Pair Colorizer

    Highlights matching brackets to make code easier to read.

    npm

    This will be useful later when inspecting package.json files.

    - + \ No newline at end of file diff --git a/docs/2021sp/setup-environment/index.html b/docs/2021sp/setup-environment/index.html index de557a4bd..aaaea1f12 100644 --- a/docs/2021sp/setup-environment/index.html +++ b/docs/2021sp/setup-environment/index.html @@ -5,14 +5,14 @@ Setup your development environment | Trends in Web Dev - +
    Version: 2021sp

    Setup your development environment

    Install Node.js

    Go to this website and follow the instructions.

    For consistency, we will use Node LTS (currently, as of Spring 2021, this is Node 14).

    Install Yarn

    For convenience, we assume you will use Yarn instead of npm. Yarn gives us the power to create workspaces, which we will discuss in detail later.

    Go to this website and follow the instructions.

    - + \ No newline at end of file diff --git a/docs/2022fa/assignment1/index.html b/docs/2022fa/assignment1/index.html index 05ac1d8ec..51f53df01 100644 --- a/docs/2022fa/assignment1/index.html +++ b/docs/2022fa/assignment1/index.html @@ -5,7 +5,7 @@ Assignment 1 | Trends in Web Dev - + @@ -23,7 +23,7 @@ documentation, methods are often denoted like: Type.prototype.method()

    Remember, anything in JavaScript/TypeScript can be an object!

    So, we can do: (5).toExponential(10) or let x = 5; x.toExponential()

    Your goal is to round the age to the nearest tenth.

    For example, you want to display "5.6 years" for the value 5.64 and "5.7 years" for the value 5.65.

    Take a look at this documentation if you are stuck!

    Example for makeSentences

    const doggos = [
    { name: 'Sparky', age: 3.35, breed: 'Pomeranian Husky' },
    { name: 'Oreo', age: 5.42, breed: 'Dalmatian' },
    { name: 'Stella', age: 4, breed: 'Alaskan Klee Kai' },
    ];

    makeSentences(doggos);

    should output

    [
    'Sparky is 3.4 years old and is a Pomeranian Husky',
    'Oreo is 5.4 years old and is a Dalmatian',
    'Stella is 4.0 years old and is a Alaskan Klee Kai',
    ];

    Starter code:

    // TODO: You should replace this any with an accurate object type in your submission!
    type Doggo = any;

    export const makeSentences = (array: Doggo[]): string[] => {
    /* TODO: add your code */
    };

    Don't worry about printing "year" vs "years" or "a" vs "an", unless you want the extra challenge!

    Submission

    Please submit to CMS your index.ts file containing your implementations of each of the functions described above.

    - + \ No newline at end of file diff --git a/docs/2022fa/assignment2/index.html b/docs/2022fa/assignment2/index.html index 64139ffc0..fdbaf3fa7 100644 --- a/docs/2022fa/assignment2/index.html +++ b/docs/2022fa/assignment2/index.html @@ -5,7 +5,7 @@ Assignment 2 | Trends in Web Dev - + @@ -39,7 +39,7 @@ If in doubt, wrap it in an anonymous function: eg. () => console.log("hi") vs console.log("hi")

    Step 4 - Submission

    Once you are done, please zip up everything in your project folder EXCEPT the node_modules and .next folders.

    Then submit to CMS!

    - + \ No newline at end of file diff --git a/docs/2022fa/assignment3/index.html b/docs/2022fa/assignment3/index.html index 449681331..f760c0b01 100644 --- a/docs/2022fa/assignment3/index.html +++ b/docs/2022fa/assignment3/index.html @@ -5,7 +5,7 @@ Assignment 3 | Trends in Web Dev - + @@ -53,7 +53,7 @@ node_modules and .next folders.

    Be extra careful with .next because it is a hidden folder, which will not show up in Finder/File Explorer by default. Please find out how to show hidden files/folders for your file browser of your choice.

    Then submit to CMS!

    - + \ No newline at end of file diff --git a/docs/2022fa/assignment4/index.html b/docs/2022fa/assignment4/index.html index a67d393f3..030b659ff 100644 --- a/docs/2022fa/assignment4/index.html +++ b/docs/2022fa/assignment4/index.html @@ -5,7 +5,7 @@ Assignment 4 | Trends in Web Dev - + @@ -41,7 +41,7 @@ firebase.ts before submission. We will be grading by connecting your app to our own database.

    Once you are done, please zip up everything in your project folder EXCEPT the node_modules and .next folders.

    Then submit to CMS!

    - + \ No newline at end of file diff --git a/docs/2022fa/assignments/index.html b/docs/2022fa/assignments/index.html index 0ff8f74cb..953ad8918 100644 --- a/docs/2022fa/assignments/index.html +++ b/docs/2022fa/assignments/index.html @@ -5,14 +5,14 @@ Assignments | Trends in Web Dev - +
    Version: 2022fa

    Assignments

    Assignments will be released here after lecture! There will be 5 assignments total, as well as a final project spanning the last few weeks of the class.

    You are allowed max 3 slip days (out of 6 total for all assignments and the final project) per an assignment.

    The class is scheduled to finish well before finals week.

    Assignment 1: Due on CMS by 10/13 by 7:30pm

    Assignment 2: Due on CMS by 1/2 by 7:30pm

    Assignment 3: Due on CMS by 1/7 by 7:30pm

    Assignment 4: Due on CMS by 1/14 by 7:30pm

    - + \ No newline at end of file diff --git a/docs/2022fa/finalproject/index.html b/docs/2022fa/finalproject/index.html index d112d1477..a49bd9d05 100644 --- a/docs/2022fa/finalproject/index.html +++ b/docs/2022fa/finalproject/index.html @@ -5,7 +5,7 @@ Final Project | Trends in Web Dev - + @@ -24,7 +24,7 @@ group members and netIDs, a link to the deployed site, a link to the GitHub repo if you used GitHub, and anything else that you think is important for us to know.

    As always, do not include your node_modules.

    Tips for Success!

    • Get in contact with your partner early!
      • Milestone 0 is intended for you to get some discussion on what you want to build before you start implementation. Make sure you are both aligned on what needs to be built to avoid issues later on. Better initial planning means less frustrations later on.
    • Be realistic.
      • We know you are ambitious but also understand your own capabilities. Building something too complex may be too overwhelming. You are allowed to change ideas, but that would be time wasted on the old project.
    • Use git
      • git is one of the best (and prevalent) version control systems out there. It is great for sharing code between you and your partner/team members. Please use a service like GitHub instead of emailing code back and forth to each other.
    • Use branches!
      • When developing a feature, you should open up a new GitHub branch rather than committing and pushing directly to the main branch. This will allow you to develop your feature independently of the current state of main (and what your partners are doing) and only merge in when you are sure your feature is done and works.
      • Branches can also protect you from weird frustrating merge conflicts (so you can focus on developing awesome features!)
    • Pair programming is fun!
      • Ideally, you should both be actively involved in the whole development process. A good way to achieve this is to step up a time to pair program and code together!
    • Also refer to tips in How to Lose in CS 2112
    - + \ No newline at end of file diff --git a/docs/2022fa/introduction/index.html b/docs/2022fa/introduction/index.html index 88495810b..d7cb55f88 100644 --- a/docs/2022fa/introduction/index.html +++ b/docs/2022fa/introduction/index.html @@ -5,7 +5,7 @@ Syllabus | Trends in Web Dev - + @@ -71,7 +71,7 @@ have accommodations with Student Disability Services and require access to any class accommodations, please speak to an instructor before the first lecture and we will work with you to make arrangements as necessary.

    - + \ No newline at end of file diff --git a/docs/2022fa/lecture1/index.html b/docs/2022fa/lecture1/index.html index 595741d50..9db4c6da6 100644 --- a/docs/2022fa/lecture1/index.html +++ b/docs/2022fa/lecture1/index.html @@ -5,7 +5,7 @@ Lecture 1 | Trends in Web Dev - + @@ -28,7 +28,7 @@ annotations and the use of arrow function syntax! See if you can spot any explicit type annotations that can be inferred instead.

    demo2.ts
    const mySum = (inputArray: number[]): number => {
    let sum: number = 0;
    for (const num of inputArray) {
    sum += num;
    }
    return sum;
    };

    console.log(mySum([1, 2, 3])); // expected 6

    const isLeapYear = (year: number): boolean => {
    return (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;
    };

    console.log(isLeapYear(2000)); // is a leap year
    console.log(isLeapYear(2100)); // is NOT a leap year;

    const perfectSquares = (arr: number[]): number[] => {
    const ans: number[] = [];
    for (const num of arr) {
    if (Math.sqrt(num) % 1 === 0) {
    ans.push(num);
    }
    }
    return ans;
    };

    console.log(perfectSquares([1, 4, 9])); // expected same as input
    console.log(perfectSquares([1, 5, 9])); // expected [1, 9]

    Run it with ts-node script.ts. Voilร ! That's a basic introduction to TypeScript. For more language quirks and useful syntax, visit the TypeScript website and pick the tutorial that best fits you.

    - + \ No newline at end of file diff --git a/docs/2022fa/lecture2/index.html b/docs/2022fa/lecture2/index.html index d606a295f..09dab7fe2 100644 --- a/docs/2022fa/lecture2/index.html +++ b/docs/2022fa/lecture2/index.html @@ -5,7 +5,7 @@ Lecture 2 | Trends in Web Dev - + @@ -68,7 +68,7 @@ resource for quickly looking up the documentation for various features in Javascript, complete with examples.

    JavaScript

    We mentioned Mozilla Developer Network as a site for documentation about the JavaScript language, but it's also a great way to get familiar with the language.

    Here is their JavaScript guide: JavaScript Guide - JavaScript | MDN (mozilla.org)

    It's a bit long, so I recommend skimming through the first few parts, up to and including the Objects section (Working with objects - JavaScript | MDN (mozilla.org)).

    JavaScript objects will show up a good amount in this course, so make sure you understand the basics!

    TypeScript

    The official TypeScript website is a great resource to get familiar with the language. There are different guides that assume different programming backgrounds. Choose the article that best suits your background.

    Take a look at the Get Started section here: TypeScript: The starting point for learning TypeScript (typescriptlang.org)

    If you've gone through the MDN JavaScript guide, fill in your TypeScript knowledge with this: TypeScript: Documentation - TypeScript for JavaScript Programmers (typescriptlang.org)

    There is also a Handbook that you can chug through if interested (not necessary at all): TypeScript: Handbook - The TypeScript Handbook (typescriptlang.org)

    Hope this is helpful for you all! This will be the language you'll be working with all-semester, so being comfortable with the language will pay off.

    Demo Code

    Need more examples? This code from last year's lecture's live coding demo rewrites some functions from the preassessment first using loops then with map, filter, and reduce.

    index.ts
    // getSqrts: takes in an array and returns an array with all the square roots
    // of those numbers
    // example: [1, 4, 9] => [1, 2, 3]
    const getSqrts = (arr: number[]): number[] => {
    const result: number[] = [];
    for (const num of arr) {
    result.push(Math.sqrt(num));
    }
    return result;
    };
    const getSqrtsMap = (arr: number[]): number[] => {
    return arr.map(Math.sqrt);
    };

    // perfectSquares: takes in an array and returns an array with only the
    // elements that are perfect squares
    // example: [1, 2, 3] => [1]
    const perfectSquares = (arr: number[]): number[] => {
    const result: number[] = [];
    for (const num of arr) {
    if (Math.sqrt(num) % 1 === 0) {
    result.push(num);
    }
    }
    return result;
    };
    const isPerfectSquare = (num: number) => Math.sqrt(num) % 1 === 0;
    const perfectSquaresFilter = (arr: number[]): number[] => {
    return arr.filter(isPerfectSquare);
    };

    // mySum: takes in an array and returns the sum of the elements
    // example: [1, 2, 3] => 6
    const mySum = (arr: number[]): number => {
    let sum = 0;
    for (const num of arr) {
    sum += num;
    }
    return sum;
    };
    const mySumReduce = (arr: number[]): number => {
    return arr.reduce((acc: number, curr: number) => acc + curr);
    };

    // testing!
    const input = [1, 2, 3];
    console.log(getSqrts(input));
    console.log(getSqrtsMap(input));
    console.log(perfectSquares(input));
    console.log(perfectSquaresFilter(input));
    console.log(mySum(input));
    console.log(mySumReduce(input));
    - + \ No newline at end of file diff --git a/docs/2022fa/lecture3/index.html b/docs/2022fa/lecture3/index.html index 5aabbf7e2..f30940ee8 100644 --- a/docs/2022fa/lecture3/index.html +++ b/docs/2022fa/lecture3/index.html @@ -5,7 +5,7 @@ Lecture 3 | Trends in Web Dev - + @@ -92,7 +92,7 @@ with your VSCode correctly, you should be getting linter warnings directly in your IDE. However, you can also run this command to run the linter.

    You can run these scripts with yarn [script], for example yarn dev.

    And that's it! Feel free to play around with this project, and we'll be diving into React in the next lecture.

    - + \ No newline at end of file diff --git a/docs/2022fa/lecture4/index.html b/docs/2022fa/lecture4/index.html index 1962fb3cd..3b5731384 100644 --- a/docs/2022fa/lecture4/index.html +++ b/docs/2022fa/lecture4/index.html @@ -5,7 +5,7 @@ Lecture 4 | Trends in Web Dev - + @@ -28,7 +28,7 @@ Whenever the component updates/re-renders, useEffect runs the argument (a function).

    useEffect(() => {setCount(count + 1)}) The function can have any arbitrary logic/function callsโ€ฆ such as the setCount state variable update function! But setCount also triggers another component update soโ€ฆ

    Optimizing useEffect

    useEffect(function, filters) useEffect triggers the function at every component update, but you can restrict this to occur only when the variables in the filters array update. This makes your React component more optimized. You could say that these variables are a dependency of the useEffect statement. Example below:

    useEffect(function, [prop1, observable])

    As a fun fact, it is possible to enter an infinite loop if the dependencies are state variables that are also set inside the effect. Don't do this!

    Cleanup

    A good use of useEffect is to hook into file streams, WebSockets, Firebase hooks, or some other Observable-like API in order to make your component reactive to changes in data. (when the observed data/value/file stream updates, the React component should update.) In order to use an API for this purpose, it is often necessary to open up an initial connection or subscription. It is good manners to cleanup by closing or unsubscribing. In a useEffect statement, the cleanup code is stored in a function that is returned by the effect (function).

    useEffect(() => {
    return () => {
    cleanup();
    };
    });

    Example usage below:

    useEffect(() => {
    return () => {
    ObservableAPI.unsubscribe()
    });
    }, [valueFromObservableApi]);

    useEffect(() => {
    return () => {
    dataStream.close()
    });
    }, [dataStreamContents]);
    - + \ No newline at end of file diff --git a/docs/2022fa/lecture5/index.html b/docs/2022fa/lecture5/index.html index e14b1ce9e..a32282b93 100644 --- a/docs/2022fa/lecture5/index.html +++ b/docs/2022fa/lecture5/index.html @@ -5,7 +5,7 @@ Lecture 5 | Trends in Web Dev - + @@ -90,7 +90,7 @@ type of the error we will get.

    Using .then() with fetch()

    fetch() returns a Promise that resolves to a Response object.

    Consider this snippet, similar to one shown above:

    fetch(`https://jsonplaceholder.typicode.com/posts`)
    .then((res) => res.json())
    .then((d) => setData(d))
    .catch((err) => console.log(err));

    Here we are getting the response from an endpoint and then calling .json() on the response and then calling setData on the result of json().

    If a promise gets rejected anywhere along this chain, we will log the error in our console.

    - + \ No newline at end of file diff --git a/docs/2022fa/lecture6/index.html b/docs/2022fa/lecture6/index.html index 3e6a5ffcd..d642dcb5e 100644 --- a/docs/2022fa/lecture6/index.html +++ b/docs/2022fa/lecture6/index.html @@ -5,7 +5,7 @@ Lecture 6 | Trends in Web Dev - + @@ -74,7 +74,7 @@ by listing them out as multiple parameters within query.

    Update

    Updating a document will only replace the specified fields within a doc and maintain unmodified fields. So the following code keep the notes_writer field but change year and cat_or_dog.

    await updateDoc(jasonDocRef, { year: '2022', cat_or_dog: 'cat' });

    Delete

    Deleting a document removes it from the collection.

    await deleteDoc(jasonDocRef);

    Sample code

    This week's sample code can be found in the files under this directory.

    - + \ No newline at end of file diff --git a/docs/2022fa/lecture7/index.html b/docs/2022fa/lecture7/index.html index 7d7a9f4ca..e4a3b7d76 100644 --- a/docs/2022fa/lecture7/index.html +++ b/docs/2022fa/lecture7/index.html @@ -5,7 +5,7 @@ Lecture 7 | Trends in Web Dev - + @@ -17,7 +17,7 @@ Creating a Collection

    (Now repeat this for the other two collections)

    Set Up Authentication

    As mentioned above, Firebase has nice integration with Google sign-on. Let's take advantage of this!

    Open the Authentication Tab... Opening the Authentication Tab

    ...and select the 'Google' authentication strategy (this uses the Google sign-on) Choosing the Google Auth strategy

    And on the client-side:

    // TODO: Replace with your own Firebase config
    const firebaseConfig = {
    apiKey: 'asfasdfasdf',
    authDomain: 'trends-sp22-lecture-8.firebaseapp.com',
    projectId: 'trends-sp22-lecture-8',
    storageBucket: 'trends-sp22-lecture-8.appspot.com',
    messagingSenderId: 'sijiofdsjdi',
    appId: '1:3209483200:web:u897j8ydq973342',
    };

    const app = getApps().length ? getApp() : initializeApp(firebaseConfig);

    const db = getFirestore(app);

    const provider = new GoogleAuthProvider();

    provider.setCustomParameters({
    login_hint: 'user@example.com',
    hd: 'cornell.edu',
    });
    provider.addScope('email');

    const auth = getAuth();
    signInWithPopup(auth, provider)
    .then((result) => {
    // This gives you a Google Access Token. You can use it to access the Google API.
    const credential = GoogleAuthProvider.credentialFromResult(result);
    const token = credential?.accessToken;
    // The signed-in user info.
    const user = result.user;
    userUpload(user, db);
    // ...
    })
    .catch((error) => {
    // Handle Errors here.
    const errorCode = error.code;
    const errorMessage = error.message;
    // The email of the user's account used.
    const email = error.email;
    // The AuthCredential type that was used.
    const credential = GoogleAuthProvider.credentialFromError(error);
    // ...
    });

    export { db };

    Architecting the App

    Avoid Hard-coding Routes!

    It's a good practice to avoid hard-coding constants such as the path to each collection. Better to include these into a fireRoutes.ts file:

    export const BOOKS_PATH = 'books';
    export const REVIEWERS_PATH = 'reviewers';
    export const REVIEWS_PATH = 'reviews';

    Writing our collection query hooks

    With the database set up, we need to build queries on the database as well as actions that can write to the database. To avoid prop drilling, we need to build custom React hooks that allow any component to use and "hook into" our data. Our custom hooks need to always have the most up-to-date data available (it is a real-time database after all), so we need to store the information in state variables (so that any components using these variables will be updated when the variable updates).

    We can start this a file fireHooks.ts:

    const useCollectionWithCallback = (
    collectionId: string,
    callback: () => void,
    ) => {
    const [coll, setColl] = useState<DocumentData[] | undefined>();
    const collectionRef = collection(db, collectionId);
    // Trigger an effect whenever the query returns a new snapshot
    useEffect(() => {
    const unsubscribe = onSnapshot(query(collectionRef), (querySnapshot) => {
    const docsInCollection: DocumentData[] = [];

    querySnapshot.forEach((doc) => docsInCollection.push(doc.data()));
    // in the effect, set the collection data. This triggers an update in any component using 'coll' (using this collection hook).
    setColl(docsInCollection);
    callback();
    });
    return () => {
    // run any any cleanup code
    unsubscribe();
    };
    }, [collectionId]);
    return coll;
    };

    Alternatively, in a slightly nicer (more functional, more Observable-y way), we can use the rxFire package to simplify some of the code for us:

    const useCollectionWithCallback2 = (
    collectionId: string,
    callback: () => void,
    ) => {
    const [coll, setColl] = useState<DocumentData[] | undefined>();
    const collectionRef = collection(db, collectionId);
    // trigger an effect whenever the collectionData observable publishes a new version of the data
    useEffect(() => {
    const subscription = collectionData(collectionRef).subscribe(
    (c: DocumentData[]) => {
    // in the effect, set the collection data. This triggers an update in any component using 'coll' (using this collection hook).
    setColl(c);
    callback();
    },
    );
    return () => {
    // run any any cleanup code
    subscription.unsubscribe();
    };
    }, [collectionId]);
    return coll;
    };

    Build Actions to Write to our Database

    Recall the 'anatomy of a Firestore real-time app' image. Now that we have hooked into our data, we need calls that will write to the data. In our case, we need calls to add, edit, and delete reviews. We also need calls to add books and get books/reviews by ID. NOTE: in this tutorial, we use the shortcut of concatenating titles and authors/reviewers to generate document IDs. DO NOT ACTUALLY DO THIS! Do the extra work of generating a Firestore document id with doc().

    Editing reviews:

    export const editReview = async (id: string, update: Partial<FireReview>) => {
    await setDoc(doc(db, REVIEWS_PATH, id), update, { merge: true });
    };

    Adding reviews:

    export const addReview = async (id: string, book: FireReview) => {
    // shh
    editReview(id, book);
    };

    Deleting reviews.

    export const deleteReview = async (id: string) => {
    await deleteDoc(doc(db, REVIEWS_PATH, id));
    };

    Adding a book (when there is a new revew on a book that does not quite exist). Note that we use a transaction to create the book, because multiple users can attempt to create a book at the same time, so there may be data races (and we want to avoid duplicate entries).

    export const addBook = async (id: string, book: FireBook) => {
    try {
    await runTransaction(db, async (transaction) => {
    const bookDocRef = doc(db, BOOKS_PATH, id);
    const bookDoc = await transaction.get(bookDocRef);

    if (bookDoc.exists()) {
    throw `Book ${book.title} by ${book.author} already exists!`;
    }

    transaction.update(bookDocRef, book);
    });
    } catch (e) {
    console.log('Transaction failed: ', e);
    }
    };

    Getting books and reviews by id:

    export const getBookId = (book: FireBook) => {
    return `${book.title}::${book.author}`;
    };
    export const getReviewId = (review: FireReview) => {
    return `${review.title}::${review.author}::${review.reviewer}`;
    };

    Uploading a user when auth:

    export const userUpload = (user: User | null, db: Firestore) => {
    if (user != null) {
    const uid = user.uid;
    const email = user.email || 'Dummy Email';

    runTransaction(db, async (transaction) => {
    const userDocumentReference = doc(collection(db, REVIEWERS_PATH), uid);

    const userDocument = await transaction.get(userDocumentReference);
    if (!userDocument.exists()) {
    const fullUserDocument: FireReviewer = {
    email,
    };
    transaction.set(userDocumentReference, fullUserDocument);
    }
    // eslint-disable-next-line no-console
    }).catch(() => console.error('Unable to upload user.'));
    }
    };

    Finally, the filters to search & sort reviews

    import { FireReview } from '../types';

    export const sortByRating = (reviews: FireReview[]) =>
    [...reviews].sort((reviewA, reviewB) => reviewA.rating - reviewB.rating);

    export const filterByTitle = (reviews: FireReview[], title: string) =>
    reviews.filter((review) => review.title === title);

    export const filterByAuthor = (reviews: FireReview[], author: string) =>
    reviews.filter((review) => review.author === author);

    export const filterByReviewer = (reviews: FireReview[], reviewer: string) =>
    reviews.filter((review) => review.reviewer === reviewer);

    export const filterByBook = (
    reviews: FireReview[],
    title: string,
    author: string,
    ) =>
    reviews.filter(
    (review) => review.title === title && review.author === author,
    );

    Now how can we use the above functions to implement the main feature of our books review platform?

    export const getAvgRatingForBook = (
    reviews: FireReview[],
    title: string,
    author: string,
    ) => {
    const filteredList = filterByBook(reviews, title, author);
    return (
    filteredList.reduce((prevSum, review) => prevSum + review.rating, 0) /
    filteredList.length
    );
    };

    export const paginateReviews = (
    reviews: FireReview[],
    resultsPerPage: number,
    page: number,
    ) => {
    const lastPage = Math.ceil((reviews.length + 1) / page);
    const pageSanitized = Math.min(Math.max(0, page), lastPage);

    return reviews.filter(
    (value, i) =>
    i > pageSanitized * resultsPerPage &&
    i < Math.min(pageSanitized + 1, lastPage),
    );
    };
    - + \ No newline at end of file diff --git a/docs/2022fa/lecture8/index.html b/docs/2022fa/lecture8/index.html index 19561ba8d..1014a9809 100644 --- a/docs/2022fa/lecture8/index.html +++ b/docs/2022fa/lecture8/index.html @@ -5,7 +5,7 @@ Lecture 8 | Trends in Web Dev - + @@ -57,7 +57,7 @@ specifies who created the task.

    Then in our query to retrieve from the database, we can filter for only tasks that belong to the current user.

    Frodo.tsx
    const { user } = useAuth();

    //...

    const taskQuery = query(
    collection(db, 'tasks'),
    where('owner', '==', user!.uid),
    );

    Similarly, we can populate the owner field whenever we create a task.

    TaskAddControl.tsx
    const { user } = useAuth();

    //...

    const task: Task = {
    text: input,
    checked: false,
    owner: user!.uid,
    };
    addDoc(collection(db, 'tasks'), task);

    Demo code

    Feel free to reference our demo code to implement authentication in your final project!

    In addition, here is a supplemental video explaining the demo code: Video Link

    - + \ No newline at end of file diff --git a/docs/2022fa/lecture9/index.html b/docs/2022fa/lecture9/index.html index 5d53b321d..3128be562 100644 --- a/docs/2022fa/lecture9/index.html +++ b/docs/2022fa/lecture9/index.html @@ -5,7 +5,7 @@ Lecture 9 | Trends in Web Dev - + @@ -71,7 +71,7 @@ Deno! Deno supports TypeScript out of the box (pretty cool) and was created by Node.js creator Ryan Dahl to fix some things he didn't like about Node.js.

    - + \ No newline at end of file diff --git a/docs/2022fa/setup-editor/index.html b/docs/2022fa/setup-editor/index.html index 01eff3612..e0644f91a 100644 --- a/docs/2022fa/setup-editor/index.html +++ b/docs/2022fa/setup-editor/index.html @@ -5,7 +5,7 @@ Setup your editor | Trends in Web Dev - + @@ -13,7 +13,7 @@
    Version: 2022fa

    Setup your editor

    For convenience, we assume you will use VSCode.You can download VS Code here.

    These are some recommended extensions for your editor. If you are using WebStorm and Atom, you are likely to find some extensions that provide similar functionalities.

    To install extensions in VS Code, navigate to the left-hand sidebar, and click the building blocks icon at the bottom. This should take you to the Extensions marketplace.

    ESLint

    Installing ESLint in VSCode will give you real-time linter feedback in any JavaScript code you write, allowing you to quickly pinpoint many problems and have readable, proper formatting.

    Once installed, add these lines to your VSCode Settings (refer to this link if you need help getting there or alternatively open the command palette in VSCode with CMD/CTRL + SHIFT + P and search settings.json):

      // Other settings ...
    "eslint.alwaysShowStatus": true,
    "editor.codeActionsOnSave": {
    "source.fixAll.eslint": true
    },
    "eslint.packageManager": "yarn"

    Bracket Pair Colorizer

    Highlights matching brackets to make code easier to read.

    npm

    This will be useful later when inspecting package.json files.

    - + \ No newline at end of file diff --git a/docs/2022fa/setup-environment/index.html b/docs/2022fa/setup-environment/index.html index f41c86ec6..f6c81a595 100644 --- a/docs/2022fa/setup-environment/index.html +++ b/docs/2022fa/setup-environment/index.html @@ -5,13 +5,13 @@ Setup your development environment | Trends in Web Dev - +
    Version: 2022fa

    Setup your development environment

    Install Node.js

    Go to this website and follow the instructions.

    For consistency, we will use Node LTS (currently, as of Spring 2022, this is Node 16).

    Install Yarn

    For convenience, we assume you will use Yarn instead of npm. However, you can use either for the purposes of this project.

    Go to this website and follow the instructions.

    Install ts-node to run ts files

     npm install -g ts-node

    Then to run a file file.ts

    ts-node file.ts
    - + \ No newline at end of file diff --git a/docs/2022sp/assignment1/index.html b/docs/2022sp/assignment1/index.html index 68fbcaf75..b624f83d3 100644 --- a/docs/2022sp/assignment1/index.html +++ b/docs/2022sp/assignment1/index.html @@ -5,7 +5,7 @@ Assignment 1 | Trends in Web Dev - + @@ -23,7 +23,7 @@ documentation, methods are often denoted like: Type.prototype.method()

    Remember, anything in JavaScript/TypeScript can be an object!

    So, we can do: (5).toExponential(10) or let x = 5; x.toExponential()

    Your goal is to round the age to the nearest tenth.

    For example, you want to display "5.6 years" for the value 5.64 and "5.7 years" for the value 5.65.

    Take a look at this documentation if you are stuck!

    Example for makeSentences

    const doggos = [
    { name: 'Sparky', age: 3.35, breed: 'Pomeranian Husky' },
    { name: 'Oreo', age: 5.42, breed: 'Dalmatian' },
    { name: 'Stella', age: 4, breed: 'Alaskan Klee Kai' },
    ];

    makeSentences(doggos);

    should output

    [
    'Sparky is 3.4 years old and is a Pomeranian Husky',
    'Oreo is 5.4 years old and is a Dalmatian',
    'Stella is 4.0 years old and is a Alaskan Klee Kai',
    ];

    Starter code:

    // TODO: You should replace this any with an accurate object type in your submission!
    type Doggo = any;

    export const makeSentences = (array: Doggo[]): string[] => {
    /* TODO: add your code */
    };

    Don't worry about printing "year" vs "years" or "a" vs "an", unless you want the extra challenge!

    Submission

    Please submit to CMS your index.ts file containing your implementations of each of the functions described above.

    - + \ No newline at end of file diff --git a/docs/2022sp/assignment2/index.html b/docs/2022sp/assignment2/index.html index 1198bf720..8811cb5c6 100644 --- a/docs/2022sp/assignment2/index.html +++ b/docs/2022sp/assignment2/index.html @@ -5,7 +5,7 @@ Assignment 2 | Trends in Web Dev - + @@ -39,7 +39,7 @@ If in doubt, wrap it in an anonymous function: eg. () => console.log("hi") vs console.log("hi")

    Step 4 - Submission

    Once you are done, please zip up everything in your project folder EXCEPT the node_modules and .next folders.

    Then submit to CMS!

    - + \ No newline at end of file diff --git a/docs/2022sp/assignment3/index.html b/docs/2022sp/assignment3/index.html index e7e9b0761..546fc9b0f 100644 --- a/docs/2022sp/assignment3/index.html +++ b/docs/2022sp/assignment3/index.html @@ -5,7 +5,7 @@ Assignment 3 | Trends in Web Dev - + @@ -53,7 +53,7 @@ node_modules and .next folders.

    Be extra careful with .next because it is a hidden folder, which will not show up in Finder/File Explorer by default. Please find out how to show hidden files/folders for your file browser of your choice.

    Then submit to CMS!

    - + \ No newline at end of file diff --git a/docs/2022sp/assignment4/index.html b/docs/2022sp/assignment4/index.html index e6e0d75cf..335106a83 100644 --- a/docs/2022sp/assignment4/index.html +++ b/docs/2022sp/assignment4/index.html @@ -5,7 +5,7 @@ Assignment 4 | Trends in Web Dev - + @@ -41,7 +41,7 @@ firebase.ts before submission. We will be grading by connecting your app to our own database.

    Once you are done, please zip up everything in your project folder EXCEPT the node_modules and .next folders.

    Then submit to CMS!

    - + \ No newline at end of file diff --git a/docs/2022sp/assignments/index.html b/docs/2022sp/assignments/index.html index ecb42041b..25b3490d3 100644 --- a/docs/2022sp/assignments/index.html +++ b/docs/2022sp/assignments/index.html @@ -5,14 +5,14 @@ Assignments | Trends in Web Dev - +
    Version: 2022sp

    Assignments

    Assignments will be released here after lecture! There will be 5 assignments total, as well as a final project spanning the last few weeks of the class.

    You are allowed max 3 slip days (out of 6 total for all assignments and the final project) per an assignment.

    The class is scheduled to finish well before finals week.

    Assignment 1: Due on CMS by 3/9 at 4:59pm Extended to 3/11 11:59 PM!

    Assignment 2: Due on CMS by 3/23 by 4:59pm

    Assignment 3: Due on CMS by 4/1 by 11:59pm

    Assignment 4: Due on CMS by 4/22 by 11:59pm

    - + \ No newline at end of file diff --git a/docs/2022sp/finalproject/index.html b/docs/2022sp/finalproject/index.html index e70c79f67..020c905ce 100644 --- a/docs/2022sp/finalproject/index.html +++ b/docs/2022sp/finalproject/index.html @@ -5,7 +5,7 @@ Final Project | Trends in Web Dev - + @@ -25,7 +25,7 @@ group members and netIDs, a link to the deployed site, a link to the GitHub repo if you used GitHub, and anything else that you think is important for us to know.

    As always, do not include your node_modules.

    Tips for Success!

    • Get in contact with your partner early!
      • Milestone 0 is intended for you to get some discussion on what you want to build before you start implementation. Make sure you are both aligned on what needs to be built to avoid issues later on. Better initial planning means less frustrations later on.
    • Be realistic.
      • We know you are ambitious but also understand your own capabilities. Building something too complex may be too overwhelming. You are allowed to change ideas, but that would be time wasted on the old project.
    • Use git
      • git is one of the best (and prevalent) version control systems out there. It is great for sharing code between you and your partner/team members. Please use a service like GitHub instead of emailing code back and forth to each other.
    • Use branches!
      • When developing a feature, you should open up a new GitHub branch rather than committing and pushing directly to the main branch. This will allow you to develop your feature independently of the current state of main (and what your partners are doing) and only merge in when you are sure your feature is done and works.
      • Branches can also protect you from weird frustrating merge conflicts (so you can focus on developing awesome features!)
    • Pair programming is fun!
      • Ideally, you should both be actively involved in the whole development process. A good way to achieve this is to step up a time to pair program and code together!
    • Also refer to tips in How to Lose in CS 2112
    - + \ No newline at end of file diff --git a/docs/2022sp/introduction/index.html b/docs/2022sp/introduction/index.html index 5c46cab02..4ed24367f 100644 --- a/docs/2022sp/introduction/index.html +++ b/docs/2022sp/introduction/index.html @@ -5,7 +5,7 @@ Syllabus | Trends in Web Dev - + @@ -62,7 +62,7 @@ have accommodations with Student Disability Services and require access to any class accommodations, please speak to an instructor before the first lecture and we will work with you to make arrangements as necessary.

    - + \ No newline at end of file diff --git a/docs/2022sp/lecture1/index.html b/docs/2022sp/lecture1/index.html index 02efad6a1..30803542f 100644 --- a/docs/2022sp/lecture1/index.html +++ b/docs/2022sp/lecture1/index.html @@ -5,7 +5,7 @@ Lecture 1 | Trends in Web Dev - + @@ -28,7 +28,7 @@ annotations and the use of arrow function syntax! See if you can spot any explicit type annotations that can be inferred instead.

    demo2.ts
    const mySum = (inputArray: number[]): number => {
    let sum: number = 0;
    for (const num of inputArray) {
    sum += num;
    }
    return sum;
    };

    console.log(mySum([1, 2, 3])); // expected 6

    const isLeapYear = (year: number): boolean => {
    return (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;
    };

    console.log(isLeapYear(2000)); // is a leap year
    console.log(isLeapYear(2100)); // is NOT a leap year;

    const perfectSquares = (arr: number[]): number[] => {
    const ans: number[] = [];
    for (const num of arr) {
    if (Math.sqrt(num) % 1 === 0) {
    ans.push(num);
    }
    }
    return ans;
    };

    console.log(perfectSquares([1, 4, 9])); // expected same as input
    console.log(perfectSquares([1, 5, 9])); // expected [1, 9]

    Run it with ts-node script.ts. Voilร ! That's a basic introduction to TypeScript. For more language quirks and useful syntax, visit the TypeScript website and pick the tutorial that best fits you.

    - + \ No newline at end of file diff --git a/docs/2022sp/lecture10/index.html b/docs/2022sp/lecture10/index.html index efceeaa1b..7d033de76 100644 --- a/docs/2022sp/lecture10/index.html +++ b/docs/2022sp/lecture10/index.html @@ -5,7 +5,7 @@ Lecture 10 | Trends in Web Dev - + @@ -71,7 +71,7 @@ Deno! Deno supports TypeScript out of the box (pretty cool) and was created by Node.js creator Ryan Dahl to fix some things he didn't like about Node.js.

    - + \ No newline at end of file diff --git a/docs/2022sp/lecture2/index.html b/docs/2022sp/lecture2/index.html index c88b4f284..1e64764b9 100644 --- a/docs/2022sp/lecture2/index.html +++ b/docs/2022sp/lecture2/index.html @@ -5,7 +5,7 @@ Lecture 2 | Trends in Web Dev - + @@ -68,7 +68,7 @@ resource for quickly looking up the documentation for various features in Javascript, complete with examples.

    JavaScript

    We mentioned Mozilla Developer Network as a site for documentation about the JavaScript language, but it's also a great way to get familiar with the language.

    Here is their JavaScript guide: JavaScript Guide - JavaScript | MDN (mozilla.org)

    It's a bit long, so I recommend skimming through the first few parts, up to and including the Objects section (Working with objects - JavaScript | MDN (mozilla.org)).

    JavaScript objects will show up a good amount in this course, so make sure you understand the basics!

    TypeScript

    The official TypeScript website is a great resource to get familiar with the language. There are different guides that assume different programming backgrounds. Choose the article that best suits your background.

    Take a look at the Get Started section here: TypeScript: The starting point for learning TypeScript (typescriptlang.org)

    If you've gone through the MDN JavaScript guide, fill in your TypeScript knowledge with this: TypeScript: Documentation - TypeScript for JavaScript Programmers (typescriptlang.org)

    There is also a Handbook that you can chug through if interested (not necessary at all): TypeScript: Handbook - The TypeScript Handbook (typescriptlang.org)

    Hope this is helpful for you all! This will be the language you'll be working with all-semester, so being comfortable with the language will pay off.

    Demo Code

    Need more examples? This code from last year's lecture's live coding demo rewrites some functions from the preassessment first using loops then with map, filter, and reduce.

    index.ts
    // getSqrts: takes in an array and returns an array with all the square roots
    // of those numbers
    // example: [1, 4, 9] => [1, 2, 3]
    const getSqrts = (arr: number[]): number[] => {
    const result: number[] = [];
    for (const num of arr) {
    result.push(Math.sqrt(num));
    }
    return result;
    };
    const getSqrtsMap = (arr: number[]): number[] => {
    return arr.map(Math.sqrt);
    };

    // perfectSquares: takes in an array and returns an array with only the
    // elements that are perfect squares
    // example: [1, 2, 3] => [1]
    const perfectSquares = (arr: number[]): number[] => {
    const result: number[] = [];
    for (const num of arr) {
    if (Math.sqrt(num) % 1 === 0) {
    result.push(num);
    }
    }
    return result;
    };
    const isPerfectSquare = (num: number) => Math.sqrt(num) % 1 === 0;
    const perfectSquaresFilter = (arr: number[]): number[] => {
    return arr.filter(isPerfectSquare);
    };

    // mySum: takes in an array and returns the sum of the elements
    // example: [1, 2, 3] => 6
    const mySum = (arr: number[]): number => {
    let sum = 0;
    for (const num of arr) {
    sum += num;
    }
    return sum;
    };
    const mySumReduce = (arr: number[]): number => {
    return arr.reduce((acc: number, curr: number) => acc + curr);
    };

    // testing!
    const input = [1, 2, 3];
    console.log(getSqrts(input));
    console.log(getSqrtsMap(input));
    console.log(perfectSquares(input));
    console.log(perfectSquaresFilter(input));
    console.log(mySum(input));
    console.log(mySumReduce(input));
    - + \ No newline at end of file diff --git a/docs/2022sp/lecture3/index.html b/docs/2022sp/lecture3/index.html index 4137d711a..82ab001c5 100644 --- a/docs/2022sp/lecture3/index.html +++ b/docs/2022sp/lecture3/index.html @@ -5,7 +5,7 @@ Lecture 3 | Trends in Web Dev - + @@ -92,7 +92,7 @@ with your VSCode correctly, you should be getting linter warnings directly in your IDE. However, you can also run this command to run the linter.

    You can run these scripts with yarn [script], for example yarn dev.

    And that's it! Feel free to play around with this project, and we'll be diving into React in the next lecture.

    - + \ No newline at end of file diff --git a/docs/2022sp/lecture4/index.html b/docs/2022sp/lecture4/index.html index f832cee7d..7a38f8cb2 100644 --- a/docs/2022sp/lecture4/index.html +++ b/docs/2022sp/lecture4/index.html @@ -5,7 +5,7 @@ Lecture 4 | Trends in Web Dev - + @@ -28,7 +28,7 @@ Whenever the component updates/re-renders, useEffect runs the argument (a function).

    useEffect(() => {setCount(count + 1)}) The function can have any arbitrary logic/function callsโ€ฆ such as the setCount state variable update function! But setCount also triggers another component update soโ€ฆ

    Optimizing useEffect

    useEffect(function, filters) useEffect triggers the function at every component update, but you can restrict this to occur only when the variables in the filters array update. This makes your React component more optimized. You could say that these variables are a dependency of the useEffect statement. Example below:

    useEffect(function, [prop1, observable])

    As a fun fact, it is possible to enter an infinite loop if the dependencies are state variables that are also set inside the effect. Don't do this!

    Cleanup

    A good use of useEffect is to hook into file streams, WebSockets, Firebase hooks, or some other Observable-like API in order to make your component reactive to changes in data. (when the observed data/value/file stream updates, the React component should update.) In order to use an API for this purpose, it is often necessary to open up an initial connection or subscription. It is good manners to cleanup by closing or unsubscribing. In a useEffect statement, the cleanup code is stored in a function that is returned by the effect (function).

    useEffect(() => {
    return () => {
    cleanup();
    };
    });

    Example usage below:

    useEffect(() => {
    return () => {
    ObservableAPI.unsubscribe()
    });
    }, [valueFromObservableApi]);

    useEffect(() => {
    return () => {
    dataStream.close()
    });
    }, [dataStreamContents]);
    - + \ No newline at end of file diff --git a/docs/2022sp/lecture5/index.html b/docs/2022sp/lecture5/index.html index fdca5b81b..317955f2a 100644 --- a/docs/2022sp/lecture5/index.html +++ b/docs/2022sp/lecture5/index.html @@ -5,7 +5,7 @@ Lecture 5 | Trends in Web Dev - + @@ -35,7 +35,7 @@ of code in the component to just one!

    Composition vs. Inheritance

    Composition and inheritance are two programming techniques for defining how classes relate to objects. (Think of classes as the blueprint for a house and objects the actual houses created from that blueprint)

    Composition

    Composition defines a class as the sum of its individual parts. This is a "has-a" relationship (e.g. a car has a steering wheel, has a window, etc). In Java (and other object oriented languages), these components are represented as instance variables.

    Inheritance

    Inheritance derives one class from another. If class A is the parent of class B and C, B and C inherit the properties/functions of A. This is a "is-a" relationship (e.g. car is a vehicle, circle is a shape.)

    React uses Composition

    โ€œReact has a powerful composition model, and we recommend using composition instead of inheritance to reuse code between components.โ€ -- React Docs

    Containment

    Components may not know their children ahead of time.

    Children are the components you put within another component:

    <ComponentA>{/* anything here is a child of Component A */}</ComponentA>

    Use the children prop to pass in children components.

    Container.tsx
    import React, { ReactNode } from 'react';
    type Props = { readonly children: ReactNode };
    const Container = (props: Props) => (
    <div className="Border">{props.children}</div>
    );
    const App = () => (
    <div className="App">
    <Container>
    <p>Hello!</p>
    <p>Bye!</p>
    </Container>
    </div>
    );

    props.children will have the paragraph elements.

    We didn't actually get to this live demo, adapted from this tutorial in the React docs, during lecture but it is very simple if you want to try it out yourself. We also show how to import styles.

    Container.tsx
    import React, { ReactNode } from 'react';
    import './Container.css'; // this is how we import styles

    type Props = { readonly children: ReactNode };

    export default (props: Props) => <div className="Border">{props.children}</div>;
    Container.css
    .Border {
    border: 4px solid black;
    background-color: azure;
    }

    Less common but you also may want multiple "holes" in your component (for example, a left and right child):

    SplintPane.tsx
    import React, { ReactNode } from 'react';
    import './SplitPane.css';

    type Props = { readonly left: ReactNode; readonly right: ReactNode };

    export default (props: Props) => (
    <div>
    <div className="LeftPane">{props.left}</div>
    <div className="RightPane">{props.right}</div>
    </div>
    );
    SplitPane.css
    /* these colors are ugly I know */
    .LeftPane {
    float: left;
    width: 50%;
    background-color: red;
    }

    .RightPane {
    float: right;
    width: 50%;
    background-color: aquamarine;
    }
    import React from 'react';
    import SplitPane from './SplitPane';
    import Container from './Container';

    export default () => {
    return (
    <div className="App">
    <Container>
    <p>Hello, world!</p>
    </Container>
    <SplitPane
    left={<div>I'm on the left!</div>}
    right={<div>I'm on the right!</div>}
    />
    </div>
    );
    };

    Lifting State Up

    This section was a (old) live demo, adapted from this tutorial in the React docs.

    Calculator.tsx
    import { useState } from 'react';
    import TemperatureInput from './TemperatureInput';

    type Scale = 'celsius' | 'fahrenheit';

    const Calculator = () => {
    const [temperature, setTemperature] = useState('');
    const [scale, setScale] = useState<Scale>('celsius');

    const onCelsiusChange = (t: string) => {
    setTemperature(t);
    setScale('celsius');
    };

    const onFahrenheitChange = (t: string) => {
    setTemperature(t);
    setScale('fahrenheit');
    };

    const fahrenheitToCelsius = (t: number) => {
    return ((t - 32) * 5) / 9;
    };

    const celsiusToFahrenheit = (t: number) => {
    return (t * 9) / 5 + 32;
    };

    const tryConvert = (targetScale: Scale) => {
    const temp = parseFloat(temperature);
    if (Number.isNaN(temp)) {
    return '';
    }
    const res = getAppropriateTemperature(temp, targetScale);
    const trimmed = Math.round(res * 1000) / 1000;
    return trimmed.toString();
    };

    const getAppropriateTemperature = (tempNum: number, targetScale: Scale) => {
    if (targetScale === scale) {
    return tempNum;
    } else {
    if (targetScale === 'celsius') {
    return fahrenheitToCelsius(tempNum);
    } else {
    return celsiusToFahrenheit(tempNum);
    }
    }
    };

    return (
    <div>
    <TemperatureInput
    scale="celsius"
    temperature={tryConvert('celsius')}
    onTemperatureChange={onCelsiusChange}
    />
    <TemperatureInput
    scale="fahrenheit"
    temperature={tryConvert('fahrenheit')}
    onTemperatureChange={onFahrenheitChange}
    />
    </div>
    );
    };

    export type { Scale };
    export default Calculator;
    TemperatureInput.tsx
    import { Scale } from './Calculator';

    type Props = {
    readonly scale: Scale;
    readonly temperature: string;
    readonly onTemperatureChange: (t: string) => void;
    };

    const TemperatureInput = ({
    scale,
    temperature,
    onTemperatureChange,
    }: Props) => {
    return (
    <div>
    <legend>Enter temperature in {scale}</legend>
    <input
    value={temperature}
    onChange={(event) => onTemperatureChange(event.target.value)}
    />
    </div>
    );
    };

    export default TemperatureInput;

    Live Demo Material

    You can get the starter code for the live demo by running: yarn create next-app --typescript --example "https://github.com/cornell-dti/trends-sp22-starters/tree/main/lec5-demo" YOUR_DIR_NAME

    (Replace YOUR_DIR_NAME with whatever you want to name your directory!)

    Here's my completed version from class.

    - + \ No newline at end of file diff --git a/docs/2022sp/lecture6/index.html b/docs/2022sp/lecture6/index.html index 1e4f77c71..d381d0d81 100644 --- a/docs/2022sp/lecture6/index.html +++ b/docs/2022sp/lecture6/index.html @@ -5,7 +5,7 @@ Lecture 6 | Trends in Web Dev - + @@ -74,7 +74,7 @@ statements in a try...catch block!

    Live Demo Material

    The demo only covers the latter half of the lecture (data fetching). Feel free to play around with hooks on your own time!

    You can get the starter code for the live demo by running: yarn create next-app --typescript --example "https://github.com/cornell-dti/trends-sp22-starters/tree/main/lec6-demo" YOUR_DIR_NAME

    (Replace YOUR_DIR_NAME with whatever you want to name your directory!)

    - + \ No newline at end of file diff --git a/docs/2022sp/lecture7/index.html b/docs/2022sp/lecture7/index.html index 768e4a3b1..3a55cb521 100644 --- a/docs/2022sp/lecture7/index.html +++ b/docs/2022sp/lecture7/index.html @@ -5,7 +5,7 @@ Lecture 7 | Trends in Web Dev - + @@ -67,7 +67,7 @@ by listing them out as multiple parameters within query.

    Update

    Updating a document will only replace the specified fields within a doc and maintain unmodified fields. So the following code keep the notes_writer field but change year and cat_or_dog.

    await updateDoc(jasonDocRef, { year: '2022', cat_or_dog: 'cat' });

    Delete

    Deleting a document removes it from the collection.

    await deleteDoc(jasonDocRef);

    Sample code

    This week's sample code can be found in the files under this directory.

    - + \ No newline at end of file diff --git a/docs/2022sp/lecture8/index.html b/docs/2022sp/lecture8/index.html index b91198844..915722054 100644 --- a/docs/2022sp/lecture8/index.html +++ b/docs/2022sp/lecture8/index.html @@ -5,7 +5,7 @@ Lecture 8 | Trends in Web Dev - + @@ -17,7 +17,7 @@ Creating a Collection

    (Now repeat this for the other two collections)

    Set Up Authentication

    As mentioned above, Firebase has nice integration with Google sign-on. Let's take advantage of this!

    Open the Authentication Tab... Opening the Authentication Tab

    ...and select the 'Google' authentication strategy (this uses the Google sign-on) Choosing the Google Auth strategy

    And on the client-side:

    // TODO: Replace with your own Firebase config
    const firebaseConfig = {
    apiKey: 'asfasdfasdf',
    authDomain: 'trends-sp22-lecture-8.firebaseapp.com',
    projectId: 'trends-sp22-lecture-8',
    storageBucket: 'trends-sp22-lecture-8.appspot.com',
    messagingSenderId: 'sijiofdsjdi',
    appId: '1:3209483200:web:u897j8ydq973342',
    };

    const app = getApps().length ? getApp() : initializeApp(firebaseConfig);

    const db = getFirestore(app);

    const provider = new GoogleAuthProvider();

    provider.setCustomParameters({
    login_hint: 'user@example.com',
    hd: 'cornell.edu',
    });
    provider.addScope('email');

    const auth = getAuth();
    signInWithPopup(auth, provider)
    .then((result) => {
    // This gives you a Google Access Token. You can use it to access the Google API.
    const credential = GoogleAuthProvider.credentialFromResult(result);
    const token = credential?.accessToken;
    // The signed-in user info.
    const user = result.user;
    userUpload(user, db);
    // ...
    })
    .catch((error) => {
    // Handle Errors here.
    const errorCode = error.code;
    const errorMessage = error.message;
    // The email of the user's account used.
    const email = error.email;
    // The AuthCredential type that was used.
    const credential = GoogleAuthProvider.credentialFromError(error);
    // ...
    });

    export { db };

    Architecting the App

    Avoid Hard-coding Routes!

    It's a good practice to avoid hard-coding constants such as the path to each collection. Better to include these into a fireRoutes.ts file:

    export const BOOKS_PATH = 'books';
    export const REVIEWERS_PATH = 'reviewers';
    export const REVIEWS_PATH = 'reviews';

    Writing our collection query hooks

    With the database set up, we need to build queries on the database as well as actions that can write to the database. To avoid prop drilling, we need to build custom React hooks that allow any component to use and "hook into" our data. Our custom hooks need to always have the most up-to-date data available (it is a real-time database after all), so we need to store the information in state variables (so that any components using these variables will be updated when the variable updates).

    We can start this a file fireHooks.ts:

    const useCollectionWithCallback = (
    collectionId: string,
    callback: () => void,
    ) => {
    const [coll, setColl] = useState<DocumentData[] | undefined>();
    const collectionRef = collection(db, collectionId);
    // Trigger an effect whenever the query returns a new snapshot
    useEffect(() => {
    const unsubscribe = onSnapshot(query(collectionRef), (querySnapshot) => {
    const docsInCollection: DocumentData[] = [];

    querySnapshot.forEach((doc) => docsInCollection.push(doc.data()));
    // in the effect, set the collection data. This triggers an update in any component using 'coll' (using this collection hook).
    setColl(docsInCollection);
    callback();
    });
    return () => {
    // run any any cleanup code
    unsubscribe();
    };
    }, [collectionId]);
    return coll;
    };

    Alternatively, in a slightly nicer (more functional, more Observable-y way), we can use the rxFire package to simplify some of the code for us:

    const useCollectionWithCallback2 = (
    collectionId: string,
    callback: () => void,
    ) => {
    const [coll, setColl] = useState<DocumentData[] | undefined>();
    const collectionRef = collection(db, collectionId);
    // trigger an effect whenever the collectionData observable publishes a new version of the data
    useEffect(() => {
    const subscription = collectionData(collectionRef).subscribe(
    (c: DocumentData[]) => {
    // in the effect, set the collection data. This triggers an update in any component using 'coll' (using this collection hook).
    setColl(c);
    callback();
    },
    );
    return () => {
    // run any any cleanup code
    subscription.unsubscribe();
    };
    }, [collectionId]);
    return coll;
    };

    Build Actions to Write to our Database

    Recall the 'anatomy of a Firestore real-time app' image. Now that we have hooked into our data, we need calls that will write to the data. In our case, we need calls to add, edit, and delete reviews. We also need calls to add books and get books/reviews by ID. NOTE: in this tutorial, we use the shortcut of concatenating titles and authors/reviewers to generate document IDs. DO NOT ACTUALLY DO THIS! Do the extra work of generating a Firestore document id with doc().

    Editing reviews:

    export const editReview = async (id: string, update: Partial<FireReview>) => {
    await setDoc(doc(db, REVIEWS_PATH, id), update, { merge: true });
    };

    Adding reviews:

    export const addReview = async (id: string, book: FireReview) => {
    // shh
    editReview(id, book);
    };

    Deleting reviews.

    export const deleteReview = async (id: string) => {
    await deleteDoc(doc(db, REVIEWS_PATH, id));
    };

    Adding a book (when there is a new revew on a book that does not quite exist). Note that we use a transaction to create the book, because multiple users can attempt to create a book at the same time, so there may be data races (and we want to avoid duplicate entries).

    export const addBook = async (id: string, book: FireBook) => {
    try {
    await runTransaction(db, async (transaction) => {
    const bookDocRef = doc(db, BOOKS_PATH, id);
    const bookDoc = await transaction.get(bookDocRef);

    if (bookDoc.exists()) {
    throw `Book ${book.title} by ${book.author} already exists!`;
    }

    transaction.update(bookDocRef, book);
    });
    } catch (e) {
    console.log('Transaction failed: ', e);
    }
    };

    Getting books and reviews by id:

    export const getBookId = (book: FireBook) => {
    return `${book.title}::${book.author}`;
    };
    export const getReviewId = (review: FireReview) => {
    return `${review.title}::${review.author}::${review.reviewer}`;
    };

    Uploading a user when auth:

    export const userUpload = (user: User | null, db: Firestore) => {
    if (user != null) {
    const uid = user.uid;
    const email = user.email || 'Dummy Email';

    runTransaction(db, async (transaction) => {
    const userDocumentReference = doc(collection(db, REVIEWERS_PATH), uid);

    const userDocument = await transaction.get(userDocumentReference);
    if (!userDocument.exists()) {
    const fullUserDocument: FireReviewer = {
    email,
    };
    transaction.set(userDocumentReference, fullUserDocument);
    }
    // eslint-disable-next-line no-console
    }).catch(() => console.error('Unable to upload user.'));
    }
    };

    Finally, the filters to search & sort reviews

    import { FireReview } from '../types';

    export const sortByRating = (reviews: FireReview[]) =>
    [...reviews].sort((reviewA, reviewB) => reviewA.rating - reviewB.rating);

    export const filterByTitle = (reviews: FireReview[], title: string) =>
    reviews.filter((review) => review.title === title);

    export const filterByAuthor = (reviews: FireReview[], author: string) =>
    reviews.filter((review) => review.author === author);

    export const filterByReviewer = (reviews: FireReview[], reviewer: string) =>
    reviews.filter((review) => review.reviewer === reviewer);

    export const filterByBook = (
    reviews: FireReview[],
    title: string,
    author: string,
    ) =>
    reviews.filter(
    (review) => review.title === title && review.author === author,
    );

    Now how can we use the above functions to implement the main feature of our books review platform?

    export const getAvgRatingForBook = (
    reviews: FireReview[],
    title: string,
    author: string,
    ) => {
    const filteredList = filterByBook(reviews, title, author);
    return (
    filteredList.reduce((prevSum, review) => prevSum + review.rating, 0) /
    filteredList.length
    );
    };

    export const paginateReviews = (
    reviews: FireReview[],
    resultsPerPage: number,
    page: number,
    ) => {
    const lastPage = Math.ceil((reviews.length + 1) / page);
    const pageSanitized = Math.min(Math.max(0, page), lastPage);

    return reviews.filter(
    (value, i) =>
    i > pageSanitized * resultsPerPage &&
    i < Math.min(pageSanitized + 1, lastPage),
    );
    };
    - + \ No newline at end of file diff --git a/docs/2022sp/lecture9/index.html b/docs/2022sp/lecture9/index.html index bde33d50e..8d2e967f7 100644 --- a/docs/2022sp/lecture9/index.html +++ b/docs/2022sp/lecture9/index.html @@ -5,7 +5,7 @@ Lecture 9 | Trends in Web Dev - + @@ -57,7 +57,7 @@ specifies who created the task.

    Then in our query to retrieve from the database, we can filter for only tasks that belong to the current user.

    Frodo.tsx
    const { user } = useAuth();

    //...

    const taskQuery = query(
    collection(db, 'tasks'),
    where('owner', '==', user!.uid),
    );

    Similarly, we can populate the owner field whenever we create a task.

    TaskAddControl.tsx
    const { user } = useAuth();

    //...

    const task: Task = {
    text: input,
    checked: false,
    owner: user!.uid,
    };
    addDoc(collection(db, 'tasks'), task);

    Demo code

    Feel free to reference our demo code to implement authentication in your final project!

    In addition, here is a supplemental video explaining the demo code: Video Link

    - + \ No newline at end of file diff --git a/docs/2022sp/setup-editor/index.html b/docs/2022sp/setup-editor/index.html index d43383f18..d4935ac21 100644 --- a/docs/2022sp/setup-editor/index.html +++ b/docs/2022sp/setup-editor/index.html @@ -5,7 +5,7 @@ Setup your editor | Trends in Web Dev - + @@ -13,7 +13,7 @@
    Version: 2022sp

    Setup your editor

    For convenience, we assume you will use VSCode. If you are using WebStorm and Atom, you are likely to find some extensions that provide similar functionalities.

    To install extensions in VS Code, navigate to the left-hand sidebar, and click the building blocks icon at the bottom. This should take you to the Extensions marketplace.

    ESLint

    Installing ESLint in VSCode will give you real-time linter feedback in any JavaScript code you write, allowing you to quickly pinpoint many problems and have readable, proper formatting.

    Once installed, add these lines to your VSCode Settings (refer to this link if you need help getting there or alternatively open the command palette in VSCode with CMD/CTRL + SHIFT + P and search settings.json):

      // Other settings ...
    "eslint.alwaysShowStatus": true,
    "editor.codeActionsOnSave": {
    "source.fixAll.eslint": true
    },
    "eslint.packageManager": "yarn"

    Bracket Pair Colorizer

    Highlights matching brackets to make code easier to read.

    npm

    This will be useful later when inspecting package.json files.

    - + \ No newline at end of file diff --git a/docs/2022sp/setup-environment/index.html b/docs/2022sp/setup-environment/index.html index 9876e5259..5b7857b53 100644 --- a/docs/2022sp/setup-environment/index.html +++ b/docs/2022sp/setup-environment/index.html @@ -5,13 +5,13 @@ Setup your development environment | Trends in Web Dev - +
    Version: 2022sp

    Setup your development environment

    Install Node.js

    Go to this website and follow the instructions.

    For consistency, we will use Node LTS (currently, as of Spring 2022, this is Node 16).

    Install Yarn

    For convenience, we assume you will use Yarn instead of npm. However, you can use either for the purposes of this project.

    Go to this website and follow the instructions.

    - + \ No newline at end of file diff --git a/docs/2023fa/assignment1/index.html b/docs/2023fa/assignment1/index.html index 6442739fc..08f8e25a2 100644 --- a/docs/2023fa/assignment1/index.html +++ b/docs/2023fa/assignment1/index.html @@ -5,14 +5,14 @@ Assignment 1 | Trends in Web Dev - +
    Version: 2023fa

    Assignment

    Due Oct 3 by 11:59pm

    Run the following command in an appropriate folder on your system:

    pnpm dlx degit cornell-dti/trends-mono/frontend-hw-1-starter my-project-name
    cd my-project-name
    pnpm install

    Then, see main.js for instructions.

    Submission

    Please submit to CMS your main.js file containing your implementations of each of the functions described above.

    - + \ No newline at end of file diff --git a/docs/2023fa/assignment2/index.html b/docs/2023fa/assignment2/index.html index 714ac1639..c0940d295 100644 --- a/docs/2023fa/assignment2/index.html +++ b/docs/2023fa/assignment2/index.html @@ -5,14 +5,14 @@ Assignment 2 | Trends in Web Dev - +
    Version: 2023fa

    Assignment

    Due Oct 10 by 11:59pm

    Run the following command in an appropriate folder on your system:

    pnpm dlx degit cornell-dti/trends-mono/frontend-hw-2-starter my-project-name
    cd my-project-name
    pnpm install

    Then, see src/main.ts for instructions.

    Submission

    Please submit to CMS your main.ts file containing your implementations of each of the functions described above.

    - + \ No newline at end of file diff --git a/docs/2023fa/assignment3/index.html b/docs/2023fa/assignment3/index.html index 6188547a1..9094c416d 100644 --- a/docs/2023fa/assignment3/index.html +++ b/docs/2023fa/assignment3/index.html @@ -5,13 +5,13 @@ Assignment 3 | Trends in Web Dev - +
    Version: 2023fa

    Assignment

    Due Oct 17 by 11:59pm

    Run the following command in an appropriate folder on your system:

    pnpm dlx degit cornell-dti/trends-mono/frontend-hw-3-starter my-project-name
    cd my-project-name
    pnpm install

    Then, see src/Paginator.tsx for instructions.

    Submission

    Please submit to CMS your Paginator.tsx file.

    - + \ No newline at end of file diff --git a/docs/2023fa/assignment4/index.html b/docs/2023fa/assignment4/index.html index 844d8ef9e..195b146dc 100644 --- a/docs/2023fa/assignment4/index.html +++ b/docs/2023fa/assignment4/index.html @@ -5,13 +5,13 @@ Assignment 4 | Trends in Web Dev - +
    Version: 2023fa

    Assignment

    Due Oct 24 by 11:59pm

    Run the following command in an appropriate folder on your system:

    pnpm dlx degit cornell-dti/trends-mono/frontend-hw-4-starter my-project-name
    cd my-project-name
    pnpm install

    Then, see src/components/Gallery.tsx for instructions.

    Submission

    Please submit to CMS your Gallery.tsx file.

    - + \ No newline at end of file diff --git a/docs/2023fa/assignments/index.html b/docs/2023fa/assignments/index.html index 8f9c3d8db..3e2d3a663 100644 --- a/docs/2023fa/assignments/index.html +++ b/docs/2023fa/assignments/index.html @@ -5,14 +5,14 @@ Assignments | Trends in Web Dev - +
    Version: 2023fa

    Assignments

    Assignments will be released here after lecture! There will be 5 assignments total, including a final project spanning the last few weeks of the class.

    You are allowed max 3 slip days (out of 6 total for all assignments and the final project) per an assignment.

    The class is scheduled to finish well before finals week.

    Assignment 1: Due on CMSX by Oct 3 at 11:59pm

    Assignment 2: Due on CMSX by Oct 10 at 11:59pm

    Assignment 3: Due on CMSX by Oct 17 at 11:59pm

    Assignment 4: Due on CMSX by Oct 24 at 11:59pm


    Independent Project Starter Templates

    Frontend Starters

    If you're trying to create a new frontend React project on your own, cd into a directory of your choice, and then use one of our starter templates (highly recommended):

    The following command creates an incredibly simple React project:

    pnpm dlx degit cornell-dti/trends-mono/frontend-starter your-project-name

    The following command creates a more advanced React project with a component library, icons, and routing built in for you:

    pnpm dlx degit cornell-dti/trends-mono/frontend-starter-advanced your-project-name

    Note that neither of these projects include a backend!


    Why use our custom frontend templates?

    In the past, people recommended using Create React App. However, in recent years, popular opinion has turned against this specific starter: in fact, it was removed from the official React documentation recently. Since we're literally "Trends in Web Development", we want to use the latest and greatest tools, and that means not using Create React App.

    Well, where are the Trends pointing towards? Past semesters of Trends used Next.js, a popular React metaframework (that is, a framework built atop another framework) that adds a lot of useful features to React. However, the future of Next.js is increasingly unclear: it's heavily opinionated, with a business focus on locking its users into its ecosystem, increasingly slow and unnecessarily complex, and just increasingly controversial.

    So, we've created our own lightweight starter template built atop Vite, an incredibly popular, fast, and un-opinionated build tool by Evan You, the creator of Vue.js. Using industry standard technologies like React Router and easy-to-get-started-with libraries like Mantine and Lucide, we've created a starter template that's easier to use, easier to understand, and easier to build upon.

    Disagree? That's fine! You're free to use whatever React-based metaframework you want for your project, whether that's Next.js, Remix, Create React App, or something else.

    - + \ No newline at end of file diff --git a/docs/2023fa/finalproject/index.html b/docs/2023fa/finalproject/index.html index 182d41110..88a546312 100644 --- a/docs/2023fa/finalproject/index.html +++ b/docs/2023fa/finalproject/index.html @@ -5,7 +5,7 @@ Final Project | Trends in Web Dev - + @@ -23,7 +23,7 @@ get most of the points here; the only way to lose points is if it is clear that not a lot of effort has been put in or you are consistently missing milestones.
  • Milestones (15%)

    • Each milestone is worth 5% of your grade.
  • Peer Review (5%)

  • Tips for Success!

    • Get in contact with your partner early! Better initial planning means less frustrations later on.
    • Be realistic. We know you are ambitious, but also understand your own capabilities, and avoid changing ideas halfway through.
    • Use git.
      • It is great for sharing code between you and your partner/team members. Please don't just email code back and forth.
      • Use git branches!
        • When developing a feature, you should open up a new GitHub branch rather than committing and pushing directly to the main branch. This will allow you to develop your feature independently of the current state of main (and what your partners are doing) and only merge in when you are sure your feature is done and works.
        • Branches can also protect you from weird frustrating merge conflicts (so you can focus on developing awesome features!)
    • Pair programming is fun!
      • Ideally, you should both be actively involved in the whole development process. A good way to achieve this is to step up a time to pair program and code together!
    • Also refer to tips in How to Lose in CS 2112
    - + \ No newline at end of file diff --git a/docs/2023fa/introduction/index.html b/docs/2023fa/introduction/index.html index 37e237ab7..fddd2e9a6 100644 --- a/docs/2023fa/introduction/index.html +++ b/docs/2023fa/introduction/index.html @@ -5,7 +5,7 @@ Syllabus | Trends in Web Dev - + @@ -19,7 +19,7 @@ that you are familiar with the foundational material in the course.

    Upload your submissions as a zip to your application here.

    This preassessment is mandatory; those who do not submit it will not be admitted in the course.

    Course Objectives

    By the end of the course, a student will be able to:

    1. Develop dynamic, interactive web applications using frontend frameworks and server side rendering
    2. Utilize standard API calls and traditional HTTP requests to transmit and retrieve data through the web
    3. Design and implement various data models and utilize data manipulation techniques to represent data in web applications
    4. Implement each aspect of a web application from frontend technologies to backend technologies and how to utilize middleware to communicate between the two
    5. Develop and implement proper authentication methods to encrypt user data

    Course Materials

    We highly recommend that you have a computer capable of running a modern web browser and text editor, as well as access to a stable internet connection.

    Assignments

    Assignments are released after each lecture! There are 5 assignments total, which includes a final project spanning the last few weeks of the class.

    You are allowed max 3 slip days (out of 6 total for all assignments and the final project) per an assignment.

    The class is scheduled to finish well before finals week.

    AssignmentTopicDue Date
    Assignment 1JavaScript FundamentalsOct 3 by 11:59pm
    Assignment 2TypeScript FundamentalsOct 10 by 11:59pm
    Assignment 3React FundamentalsOct 17 by 11:59pm
    Assignment 4Frontend CapstoneOct 24 by 11:59pm
    Assignment 5Final ProjectTBA - Tentatively Nov 26

    Method of Assessing Student Achievement

    Basis of Grade Determination

    First, we will determine your numerical grade. This will be done by the following:

    AssignmentPercentage of Grade
    Attendance20% (based on weekly lecture quizzes, can miss 1 of 9 without penalty)
    Filling out Feedback10%
    Final Project30%
    Assignments40%

    Your final grade will be determined by your numerical grade calculated above:

    Satisfactory (S) - 70% or higher

    Unsatisfactory (U) - 69% or lower

    Keep in mind that the class is S/U, and that a numerical score of a C- (70%) or higher would allow you to pass with an S grade.

    Grading Scale

    Web Development is a creative process, one that depends heavily on current technologies and how students decide to utilize them towards an end. Because of this, it is unnecessary to assign letter grades to students in this course. Rather, we will assign Satisfactory/Unsatisfactory grades. For a student to receive an Unsatisfactory grade, their numerical grade must be at or below 69% according to the numerical determination outlined above. Otherwise, they will receive a Satisfactory grade.

    Course Management

    For the following sections, the word "us" refers to the course staff.

    When in contact with us, contact with either of the two primary instructors (Daniel Wei and Michelle Li) is sufficient to qualify as contact with us.

    The contact information can be found above in the "Instructors" section.

    Academic Integrity

    Each student in this course is expected to abide by the Cornell University Code of Academic Integrity: http://cuinfo.cornell.edu/aic.cfm

    Under the provisions of the Code, anyone who gives or receives unauthorized assistance in the preparation of work at home or during tests in class will be subject to disciplinary action. A student's name on any piece of work is our assurance that they have neither given nor received any unauthorized help in its preparation. Students may assist each other on assignments by answering questions and explaining various concepts. However, one student should not allow another student to copy their work directly. All University policies with respect to cheating will be enforced. A student who is found to have cheated on an exam, or any other graded assignment, will receive a "Uโ€ in the course.

    SDS Accommodations

    Students with Disabilities: Your access in this course is important to us! Please request your accommodation letter early in the semester, or as soon as you become registered with Student Disability Services (SDS), so that we have adequate time to arrange your approved academic accommodations.

    Once SDS approves your accommodation letter, it will be emailed to both you and us. Please follow up with us to discuss the necessary logistics of your accommodations using the contact information provided in this syllabus. If you are approved for exam accommodations, please consult with us at least two weeks before the scheduled exam date to confirm the testing arrangements.

    If you experience any access barriers in this course, such as with printed content, graphics, online materials, or any communication barriers, reach out to us or SDS right away.

    If you need immediate accommodation, please speak with us after class or send an email message to us and SDS at sds_cu@cornell.edu.

    If you have, or think you may have, a disability, please contact Student Disability Services for a confidential discussion: sds_cu@cornell.edu or visit sds.cornell.edu to learn more.

    Mental Health and Well-being

    Your health and wellbeing are important to us.

    There are services and resources at Cornell designed specifically to bolster undergraduate, graduate, and professional student mental health and well-being. Remember, your mental health and emotional well-being are just as important as your physical health. If you or a friend are struggling emotionally or feeling stressed, fatigued, or burned out, there is a continuum of campus resources available to you: https://mentalhealth.cornell.edu/get-support/support-students.

    Help is also available any time day or night through Cornellโ€™s 24/7 phone consultation (607-255-5155). You can also reach out to me, your college student services office, your resident advisor, or Cornell Health for support.

    - + \ No newline at end of file diff --git a/docs/2023fa/lecture5/index.html b/docs/2023fa/lecture5/index.html index 99e4e28a6..709805823 100644 --- a/docs/2023fa/lecture5/index.html +++ b/docs/2023fa/lecture5/index.html @@ -5,7 +5,7 @@ Lecture 5 | Trends in Web Dev - + @@ -42,7 +42,7 @@ function.

    Here is an example of doing equivalent things with either syntax:

    const thenCatchExample = () => {
    fetch(`https://jsonplaceholder.typicode.com/posts`)
    .then((res) => res.json())
    .then((d) => setData(d));
    };

    const asyncAwaitExample = async () => {
    const res = await fetch(`https://jsonplaceholder.typicode.com/posts`);
    const d = await res.json();
    setData(d);
    };

    In order to handle rejected Promises using async/await, just wrap all your await statements in a try...catch block!

    Like this:

    try {
    asyncAwaitExample();
    } catch (error) {
    // display any errors that may occur from async/await function
    console.error(error);
    }

    Quick Postman Demo

    Check out Postman to send API requests! Here

    Live Demo Material

    You can get the starter code for the live demo by running: yarn create next-app --typescript --example "https://github.com/cornell-dti/trends-sp23-lec6-demo" YOUR_DIR_NAME

    (Replace YOUR_DIR_NAME with whatever you want to name your directory!)

    - + \ No newline at end of file diff --git a/docs/2023fa/lecture6/index.html b/docs/2023fa/lecture6/index.html index 2e2b1d976..ed186ce2f 100644 --- a/docs/2023fa/lecture6/index.html +++ b/docs/2023fa/lecture6/index.html @@ -5,14 +5,14 @@ Lecture 6 | Trends in Web Dev - +
    Version: 2023fa

    Lecture 6

    Lecture Slides

    Intro to Express.js

    Now that we understand the difference between what we mean by client side programming and server side programming. Our primary focus for the first 4 lectures was client side programming. Now weโ€™re going to dive deeper into server side programming. One tool we can use to create our own server is a framework called Express.js. Express.js is a minimal and flexible web application framework that provides us a set of features to create our own APIs.

    To install Express, run npm install express or yarn add express in your preferred directory. If you're using yarn, make sure yarn is properly installed on your local development environment.

    Quick Example

    import express from 'express';

    const app = express();
    const port = 8080;
    app.use(express.json());
    app.use(cors());

    app.get('/', (req, res) => {
    res.send('Hello World!');
    });

    app.listen(port, () => {
    console.log(`Server listening on port ${port}`);
    });

    Here is a quick example on how to set up the boiler plate code for creating an express server.

    We start by importing the express function from the express framework that we just installed using npm.

    Then weโ€™re going to create a variable called app. This is just an instance of express, which is used to create an application. In our case, weโ€™re using this instance of express to create our server application.

    This next line is app.use(express.json()). This just automatically parses any incoming requests into JSON objects. This makes it easier to handle data sent from the client.

    Thereโ€™s an interesting line app.use(cors()); This line enables the express app to respond to something called preflight requests. A preflight request is something like an OPTION request sent to the server before the actual request is sent. An OPTION request is an HTTP method that is sent by browsers to find out what methods are allowed by the server. This allows clients to obtain any parameters and requirements for specific resources and server capabilities that might be necessary. This is just an aside and isnโ€™t crucial. If youโ€™re interested in learning more about OPTION requests, come to office hours! Basically, this line allows our server to be accessible to any domain that requests a resource from our server via a browser. This basically relaxes the security applied to an API. These CORS errors can get really painful, so remember to add this line if you run into them.

    The next section is where weโ€™re actually creating our API endpoints. Using the app instance, weโ€™re going ot create a GET endpoint with the location at the root, which is indicated by the string in the function. Then here is some interesting syntax, which I will go into in more detail later. Essentially, weโ€™re creating a GET endpoint at the root path and then sending a response to the client saying โ€œHello Worldโ€

    Finally, this last section is where our server application will actually listen for any connections from clients on the specified port, which we indicated before as 8080. When our server is up and running, we will console log โ€œServer listening on port [insert port number] in this case itโ€™s 8080. This function will constantly listen for any connections being made.

    When running the application with npm start, the server will be listening on http://localhost:[insert port number here].

    Routing

    Routing determines how our server responses to a client request to particular endpoint. An endpoint simply consists of a URI (or path) and a specific HTTP request method (GET, POST, PUT, DELETE)

    app.[METHOD]([PATH], [HANDLER])

    GET Endpoint

    app.get('/', (req, res) => {
    res.send('Hello World!');
    });

    Here we go back to the get request that we saw previously. Essentially here again we have a GET request with the path at the root. We could change this string to anything we want. Then the next parameter is the handler, which is a call back function that is executed when the route is matched. So, when the client makes a GET request to the root path of our server running on port 8080, the server will match that request to this function right here and call the handler. The req and res objects are created automatically by the framework and is passed as an argument to route handler functions.

    The handler takes in the request object and a response object as parameters and executes the body of this function. The request object represents an HTTP request and contains any data that the client may have sent over to the server (query strings, parameters, body, HTTP headers, etc). This isnโ€™t applicable to GET request since GET requests are only used to request data. But, we will look at other method types later. The second parameter is the response object which represents the HTTP response object the server will send back to the client. So, this in example, when the client request is routed to this handler, the server will send an object containing the string Hello World.

    POST Endpoint

    app.post('/', (req, res) => {
    const body = req.body
    res.send(โ€˜This is a POST requestโ€™)
    })

    Here we have a POST request with the path at the root. We are allowed to have multiple endpoints with the same path they just have to be different HTTP methods. So, we can have a GET endpoint and a POST endpoint at the same path. But, we cannot have two GET endpoints with the same path.

    PUT Endpoint

    app.put('/user', (req, res) => {
    const body = req.body;
    const username = req.body.username;
    res.send('This is a PUT request');
    });

    Here we have a PUT request with the path at /user. Here the handler will get the variable called body to the req.body, which represents any data that the client may have sent over to the server at this endpoint. The handler also sets a constant called username to the username parameter sent in through the body of the clientโ€™s HTTP request. Then the handler will send a response back saying This is a PUT request. A PUT request is typically used for updating data, we will take more of a deep dive into all this in the next lecture when we talk about databases.

    DELETE Endpoint

    app.delete('/user/:id', (req, res) => {
    res.send('This is a delete request for id ${req.params.id}');
    });

    Here we have a DELETE request with the path at โ€œ/user/:idโ€. This is an interesting path because we have :id. Here we have defined a route that takes in a parameter called id. When we visit something like /user/1, the server will respond with This is a DELETE request for id 1. You can include parameters into any endpoint path for your server and read them from the request object sent from the clientโ€™s HTTP request. This is an example of a dynamic route.

    Postman

    In order to test our APIs and make sure that they are responding appropriately for certain routes, Postman is a great tool for sending and receiving API requests directly to and from the server.

    Postman is an API platform used for building and using APIs.

    Download here

    Sample code

    This week's sample code starter can be found in the files under this directory.

    demo solution

    - + \ No newline at end of file diff --git a/docs/2023fa/lecture7/index.html b/docs/2023fa/lecture7/index.html index 03d5a87cd..b863a40f5 100644 --- a/docs/2023fa/lecture7/index.html +++ b/docs/2023fa/lecture7/index.html @@ -5,7 +5,7 @@ Lecture 7 | Trends in Web Dev - + @@ -52,7 +52,7 @@ first.

    await peopleCollectionRef
    .doc(โ€˜myl39โ€™)
    .update({ age: '20', first: 'michelle' });

    Note if the document we're trying to access is not available, an error will occur.

    Delete

    Deleting a document removes it from the collection.

    await peopleCollectionRef
    .doc(โ€˜myl39โ€™)
    .delete();

    Callback/Promise-based vs Real-Time Queries

    Promise-BasedReal-Time
    If you need the data now, you can query for itYou already have the data
    Data queries can be decentralized (done in any component)Data queries are fetched and memoized through centralized (React) hooks
    Querying data is imperative, but can quickly become hard to maintain and track (and you lose some of the advantages of a declarative web UI framework)Up-front cost to query data pays off (because you don't hopefully have to query it again)
    There is no cleanup codeYou first have to "subscribe" to changes in the data, then unsubscribe after you are done (kind of like opening and closing a file stream when reading a file)

    What do Callback/Promise-based vs. Real-Time Queries Look Like?

    Promise-based queries are single queries that return a single async result. So, they are run once and then passed along downstream to children and other descendants of your component. Typically, they are used to react to some update (i.e user clicks a button, a component loads).

    Real-time queries are single queries that return a stream of async results such as weather data. These types of queries are used once the data is listenable and needs to be "subscribed to". These take a stream of results and are built on top of wbe sockets, which are abstractions over a byte stream. So, they're good for ... real-time applications.

    How Do Callback/Promised Based vs. Real-Time Queries Work?

    Promise based queries typically calls some backend API route, which fetches and returns data to you. They're built on top of traditional HTTP requests.

    Real-time queries might call a backend route to pass data over to a web socket or it'll simply use an API library to makes calls directly to a database (ex. Firebase Firestore call). These queries are usually wrapped in a library like RxJS's observable data type or function calls that allow you to subsribe to changes.

    Choosing a Querying Method

    As described in the first section, the type of queries your application will use will affect the app's architecture. In particular, real-time queries play nicely with having a centralized query that runs over a listenable data access object that is "owned" either by

    1. a top-level component (OK in small apps, but prone to prop drilling in more complex apps), or
    2. a custom React hook that wraps an effect (triggering an update when the data access object publishes a new version of the data)

    That is not to say that your app cannot use both types of queries. It is just that a real-time application requires a specific architecture in which all data is queried first and passed along to components as props or referenced by components via (potentially custom) React/Redux hooks. This does not play nicely with callback/Promise-based queries because the data from the callback/Promise-based queries may be in an inconsistent state by the time the data from a real-time query has updated.

    Firebase Firestore Application: Callback/Promise-based or Real-Time Queries

    Firestore offers you a database that nicely organizes your data into documents and collections (groups of documents). It allows you to build queries that can either

    1. return once with a single snapshot of data (a Promise-based query), or
    2. allow you to hook into the data's live values (a real-time query).

    Firestore Real-time Queries

    Provides collection + document data as an listenable (subscribable) data object

    • As soon as a collection updates, the collection access object publishes a new version of the collection
    • As soon as a doc updates, the doc access object publishes a new version of the doc This can be passed as a React prop or an effect dependency, which triggers a component update!

    Anatomy of a Firebase Firestore Real-Time Application (The "Full" Stack)

    Anatomy of a Firebase Firestore Real-Time Update

    Unlike callback/promise-based queries, the connection between updating and fetching data is completely gone. Updating data occurs along an entirely separate channel from subscribing to the data. This means that implementing calls to update data will look very different

    Miscellaneous Advice

    When designing a system:

    • avoid two-way dependencies (or as many dependencies as possible)
      • as with React & declarative web frameworks, one-way data binding is the way to go
      • avoids: more things to update
      • avoids: more surface area for synchronization errors

    This philosophy helps us prefer real-time queries over Promise-based queries, because there is only a single dependency for the queried data, rather than the set of all the decentralized Promise-based queries.

    Sample code

    This week's sample code starter can be found in the files under this directory.

    demo solution

    - + \ No newline at end of file diff --git a/docs/2023fa/lecture8/index.html b/docs/2023fa/lecture8/index.html index 9130a32c8..fc2344097 100644 --- a/docs/2023fa/lecture8/index.html +++ b/docs/2023fa/lecture8/index.html @@ -5,7 +5,7 @@ Lecture 8 | Trends in Web Dev - + @@ -57,7 +57,7 @@ specifies who created the task.

    Then in our query to retrieve from the database, we can filter for only tasks that belong to the current user.

    Frodo.tsx
    const { user } = useAuth();

    //...

    const taskQuery = query(
    collection(db, 'tasks'),
    where('owner', '==', user!.uid),
    );

    Similarly, we can populate the owner field whenever we create a task.

    TaskAddControl.tsx
    const { user } = useAuth();

    //...

    const task: Task = {
    text: input,
    checked: false,
    owner: user!.uid,
    };
    addDoc(collection(db, 'tasks'), task);

    Demo code

    Feel free to reference our demo code to implement authentication in your final project!

    Here is the final solution to the in class demo

    - + \ No newline at end of file diff --git a/docs/2023fa/lecture9/index.html b/docs/2023fa/lecture9/index.html index cf11aeb36..b96c06459 100644 --- a/docs/2023fa/lecture9/index.html +++ b/docs/2023fa/lecture9/index.html @@ -5,13 +5,13 @@ Lecture 9 | Trends in Web Dev - +
    Version: 2023fa

    Lecture 9

    Lecture Slides

    Final Project Instructions

    Quick Announcements

    • Final Project Demonstration
      • Tuesday 12/5 12:30 โ€“ 2:00pm
      • Location: Upson 222
      • Demo required (5 minutes max)
      • Presentation optional

    Containerization Concepts

    Containerization allows applications to packaged with their dependencies into standardized units called containers.

    Benefits

    • Portability between environments
    • Ensure consistency
    • Streamline deployment

    Docker is a popular containerization platform. Key concepts:

    • Images: Blueprint describing the environment
    • Containers: Running instances of images
    • Dockerfile: Defines how to build an image

    With Docker we can package applications into images that can be run reliably as containers anywhere.

    Docker Setup

    To build a Docker image for a Node.js app:

    1. Create a Dockerfile
    2. Define base image, copy source code, specify commands
    3. Build image: docker build
    4. Run container from image: docker run

    Deploying Containers

    Platforms like Fly.io make it easy to deploy Docker containers.

    To deploy on Fly.io:

    1. Install flyctl CLI
    2. Sign up and login
    3. Launch app with flyctl launch
    4. Deploy updates with flyctl deploy

    Fly.io handles running containers on their infrastructure.

    Even Further Beyond

    Some further technologies:

    • Redux, SWR, Axios
    • GraphQL
    • Nest.js
    • Deno

    Final Course Feedback

    Let us know your thoughts!

    Thank you!

    - + \ No newline at end of file diff --git a/docs/2023fa/setup-editor/index.html b/docs/2023fa/setup-editor/index.html index e5cbc215d..b27f7e29f 100644 --- a/docs/2023fa/setup-editor/index.html +++ b/docs/2023fa/setup-editor/index.html @@ -5,13 +5,13 @@ Setup your editor | Trends in Web Dev - +
    Version: 2023fa

    Install an Editor

    We recommend using VSCode, which is free for students. Install it from the website.

    Note: Course staff, instructors, and TAs will be using VSCode, so if you are using any other editors (such as WebStorm), you may need to do some extra work to get help from us.

    To install extensions in VS Code, navigate to the left-hand sidebar, and click the building blocks icon near the bottom. This should take you to the Extensions marketplace.

    ESLint

    Installing ESLint in VSCode will give you real-time linter feedback in any JavaScript code you write, allowing you to quickly pinpoint many problems and have readable, proper formatting.

    Once installed, add these lines to your VSCode Settings (refer to this link if you need help getting there or alternatively open the command palette in VSCode with CMD/CTRL + SHIFT + P and search settings.json):

      // Other settings ...
    "eslint.alwaysShowStatus": true,
    "editor.codeActionsOnSave": {
    "source.fixAll.eslint": true
    },
    "eslint.packageManager": "yarn"

    Prettier

    Prettier is a code formatter that will automatically format your code to be consistent with the rest of the class.

    Install it and it should automatically work!

    - + \ No newline at end of file diff --git a/docs/2023fa/setup-environment/index.html b/docs/2023fa/setup-environment/index.html index 190a9286c..47df557d6 100644 --- a/docs/2023fa/setup-environment/index.html +++ b/docs/2023fa/setup-environment/index.html @@ -5,13 +5,13 @@ Setup your development environment | Trends in Web Dev - +
    Version: 2023fa

    Setup your development environment

    Install Node.js

    Node is a JavaScript runtime that allows you to run JavaScript code outside of a browser! It powers all of web development today, and is critically necessary for this course.

    Go to this website and follow the instructions to install it on your workstation.

    For consistency, please use the latest Node LTS (currently, as of Fall 2023, this is Node 18).

    Install Additional Packages

    We will be using a few additional packages to help us with development.

    Deno

    Deno is a drop-in replacement for Node that's faster, more secure, and critically -- supports TypeScript out of the box. We use it for its "REPL" (Read-Eval-Print-Loop) functionality, which allows us to run TypeScript code interactively during demos in class. Install it if you want to follow along with the demos.

    Instructions

    Pnpm

    Pnpm is a package manager that is faster and more efficient than the default npm package manager.

    npm install -g pnpm

    Making a React Project

    To make sure you've got everything set up correctly, cd into a directory of your choice and run:

    pnpm dlx degit cornell-dti/trends-mono/frontend-starter your-project-name

    This will create a new directory called your-project-name with a React project inside. cd into it and run pnpm install to install all the dependencies.

    Take a look around and edit the code if you'd like.

    When you're ready, run pnpm dev to start the development server. If everything works as it should, you should be able to navigate to localhost:5173 and see a React app running!

    You can also use that command to create a new 'blank' React project at any time. Bookmark this page and come back to it!

    - + \ No newline at end of file diff --git a/docs/2023fa/unit1/index.html b/docs/2023fa/unit1/index.html index 20674107b..922aa9470 100644 --- a/docs/2023fa/unit1/index.html +++ b/docs/2023fa/unit1/index.html @@ -5,7 +5,7 @@ Frontend / Unit 1 | Trends in Web Dev - + @@ -49,7 +49,7 @@ gets evaluated to 0, {} gets evaluated to [object Object], and they both get coerced to strings. Then, adding a list to a string simply adds the contents of the list to the string, so 1 gets appended to the end.

    Example 3

    const zero = +[]; // + coerce [] into 0
    const one = +!![]; // ! coerce [] into false, got inverted, then coerce to 1
    const two = +!![] + +!![]; // 2 = 1 + 1

    const fib2 = (__) =>
    __ === zero || __ === one ? __ : fib2(__ - one) + fib2(__ - two);

    This is the Fibonacci sequence implemented using type coercion.

    - + \ No newline at end of file diff --git a/docs/2023fa/unit2/index.html b/docs/2023fa/unit2/index.html index ef85e8578..68e65814e 100644 --- a/docs/2023fa/unit2/index.html +++ b/docs/2023fa/unit2/index.html @@ -5,7 +5,7 @@ Frontend / Unit 2 | Trends in Web Dev - + @@ -81,7 +81,7 @@ your own hook.

    Syntax for Custom Hook

    Just write a function using hooks! Make sure your function is named according to the useXXXX scheme.

    There is no function signature that you must follow in order for it to be hook - it can have whatever arguments and return type that you choose.

    Learn more about custom hooks here

    - + \ No newline at end of file diff --git a/docs/2023sp/assignment1/index.html b/docs/2023sp/assignment1/index.html index 0762264bf..58c479e16 100644 --- a/docs/2023sp/assignment1/index.html +++ b/docs/2023sp/assignment1/index.html @@ -5,7 +5,7 @@ Assignment 1 | Trends in Web Dev - + @@ -24,7 +24,7 @@ documentation, methods are often denoted like: Type.prototype.method()

    Remember, anything in JavaScript/TypeScript can be an object!

    So, we can do: (5).toExponential(10) or let x = 5; x.toExponential()

    Your goal is to round the age to the nearest tenth.

    For example, you want to display "5.6 years" for the value 5.64 and "5.7 years" for the value 5.65.

    Take a look at this documentation if you are stuck!

    Example for makeSentences

    const doggos = [
    { name: 'Sparky', age: 3.35, breed: 'Pomeranian Husky' },
    { name: 'Oreo', age: 5.42, breed: 'Dalmatian' },
    { name: 'Stella', age: 4, breed: 'Alaskan Klee Kai' },
    ];

    makeSentences(doggos);

    should output

    [
    'Sparky is 3.4 years old and is a Pomeranian Husky',
    'Oreo is 5.4 years old and is a Dalmatian',
    'Stella is 4.0 years old and is a Alaskan Klee Kai',
    ];

    Starter code:

    // TODO: You should replace this any with an accurate object type in your submission!
    type Doggo = any;

    export const makeSentences = (array: Doggo[]): string[] => {
    /* TODO: add your code */
    };

    Don't worry about printing "year" vs "years" or "a" vs "an", unless you want the extra challenge!

    Submission

    Please submit to CMS your index.ts file containing your implementations of each of the functions described above.

    - + \ No newline at end of file diff --git a/docs/2023sp/assignment2/index.html b/docs/2023sp/assignment2/index.html index e2579d233..cff2b477d 100644 --- a/docs/2023sp/assignment2/index.html +++ b/docs/2023sp/assignment2/index.html @@ -5,7 +5,7 @@ Assignment 2 | Trends in Web Dev - + @@ -39,7 +39,7 @@ If in doubt, wrap it in an anonymous function: eg. () => console.log("hi") vs console.log("hi")

    Step 4 - Submission

    Once you are done, please zip up everything in your project folder EXCEPT the node_modules and .next folders.

    Then submit to CMS!

    - + \ No newline at end of file diff --git a/docs/2023sp/assignment3/index.html b/docs/2023sp/assignment3/index.html index fecfaf654..6f877419d 100644 --- a/docs/2023sp/assignment3/index.html +++ b/docs/2023sp/assignment3/index.html @@ -5,7 +5,7 @@ Assignment 3 | Trends in Web Dev - + @@ -53,7 +53,7 @@ node_modules and .next folders.

    Be extra careful with .next because it is a hidden folder, which will not show up in Finder/File Explorer by default. Please find out how to show hidden files/folders for your file browser of your choice.

    Then submit to CMS!

    - + \ No newline at end of file diff --git a/docs/2023sp/assignment4/index.html b/docs/2023sp/assignment4/index.html index cf712919b..678f15417 100644 --- a/docs/2023sp/assignment4/index.html +++ b/docs/2023sp/assignment4/index.html @@ -5,7 +5,7 @@ Assignment 4 | Trends in Web Dev - + @@ -39,7 +39,7 @@ remove that particular task from the database.

    You may find updateDoc and deleteDoc helpful for this part.

    Step 6 - Submission

    Make sure you've completed all of the TODOs, including your name/netid in components/layout/Footer.tsx and the hours worked in pages/index.tsx.

    Once you are done, please zip up everything in your project folder EXCEPT the node_modules and .next folders.

    Then submit to CMS!

    - + \ No newline at end of file diff --git a/docs/2023sp/assignments/index.html b/docs/2023sp/assignments/index.html index 59f8c5426..914dd6e01 100644 --- a/docs/2023sp/assignments/index.html +++ b/docs/2023sp/assignments/index.html @@ -5,14 +5,14 @@ Assignments | Trends in Web Dev - +
    Version: 2023sp

    Assignments

    Assignments will be released here after lecture! There will be 5 assignments total, including a final project spanning the last few weeks of the class.

    You are allowed max 3 slip days (out of 6 total for all assignments and the final project) per an assignment.

    The class is scheduled to finish well before finals week.

    Assignment 1: Due on CMSX by 3/15 at 11:59pm

    Assignment 2: Due on CMSX by 3/31 at 11:59pm

    Assignment 3: Due on CMSX by 4/19 at 11:59pm

    Assignment 4: Due on CMSX by 4/30 at 11:59pm

    - + \ No newline at end of file diff --git a/docs/2023sp/finalproject/index.html b/docs/2023sp/finalproject/index.html index 40eec21b8..241dd4fa8 100644 --- a/docs/2023sp/finalproject/index.html +++ b/docs/2023sp/finalproject/index.html @@ -5,7 +5,7 @@ Final Project | Trends in Web Dev - + @@ -19,7 +19,7 @@ get most of the points here; the only way to lose points is if it is clear that not a lot of effort has been put in or you are consistently missing milestones.

    Tips for Success!

    • Get in contact with your partner early!
      • Milestone 0 is intended for you to get some discussion on what you want to build before you start implementation. Make sure you are both aligned on what needs to be built to avoid issues later on. Better initial planning means less frustrations later on.
    • Be realistic.
      • We know you are ambitious but also understand your own capabilities. Building something too complex may be too overwhelming. You are allowed to change ideas, but that would be time wasted on the old project.
    • Use git
      • git is one of the best (and prevalent) version control systems out there. It is great for sharing code between you and your partner/team members. Please use a service like GitHub instead of emailing code back and forth to each other.
    • Use branches!
      • When developing a feature, you should open up a new GitHub branch rather than committing and pushing directly to the main branch. This will allow you to develop your feature independently of the current state of main (and what your partners are doing) and only merge in when you are sure your feature is done and works.
      • Branches can also protect you from weird frustrating merge conflicts (so you can focus on developing awesome features!)
    • Pair programming is fun!
      • Ideally, you should both be actively involved in the whole development process. A good way to achieve this is to step up a time to pair program and code together!
    • Also refer to tips in How to Lose in CS 2112
    - + \ No newline at end of file diff --git a/docs/2023sp/introduction/index.html b/docs/2023sp/introduction/index.html index 3826c8d5c..c8e390a17 100644 --- a/docs/2023sp/introduction/index.html +++ b/docs/2023sp/introduction/index.html @@ -5,7 +5,7 @@ Syllabus | Trends in Web Dev - + @@ -19,7 +19,7 @@ that you are familiar with the foundational material in the course.

    Upload your submissions as a zip to your application here.

    This preassessment is mandatory; those who do not submit it will not be admitted in the course.

    Course Objectives

    By the end of the course, a student will be able to:

    1. Develop dynamic, interactive web applications using frontend frameworks and server side rendering
    2. Utilize standard API calls and traditional HTTP requests to transmit and retrieve data through the web
    3. Design and implement various data models and utilize data manipulation techniques to represent data in web applications
    4. Implement each aspect of a web application from frontend technologies to backend technologies and how to utilize middleware to communicate between the two
    5. Develop and implement proper authentication methods to encrypt user data

    Course Materials

    We highly recommend that you have a computer capable of running a modern web browser and text editor, as well as access to a stable internet connection.

    Assignments

    Assignments are released after each lecture! There are 5 assignments total, which includes a final project spanning the last few weeks of the class.

    You are allowed max 3 slip days (out of 6 total for all assignments and the final project) per an assignment.

    The class is scheduled to finish well before finals week.

    AssignmentTopicDue Date
    Assignment 1JS, TS, and Basic React3/15 by 11:59pm
    Assignment 2CSS and Complex React3/25 by 11:59pm
    Assignment 3State Management and Conditional Rendering4/8 by 11:59pm
    Assignment 4Connecting to and using Firebase4/22 by 11:59pm
    Assignment 5Final ProjectTBD

    Method of Assessing Student Achievement

    Basis of Grade Determination

    First, we will determine your numerical grade. This will be done by the following:

    AssignmentPercentage of Grade
    Attendance20% (based on weekly lecture quizzes, can miss 1 of 9 without penalty)
    Filling out Feedback10%
    Final Project20%
    Assignments50%

    Your final grade will be determined by your numerical grade calculated above:

    Satisfactory (S) - 70% or higher

    Unsatisfactory (U) - 69% or lower

    Keep in mind that the class is S/U, and that a numerical score of a C- (70%) or higher would allow you to pass with an S grade.

    Grading Scale

    Web Development is a creative process, one that depends heavily on current technologies and how students decide to utilize them towards an end. Because of this, it is unnecessary to assign letter grades to students in this course. Rather, we will assign Satisfactory/Unsatisfactory grades. For a student to receive an Unsatisfactory grade, their numerical grade must be at or below 69% according to the numerical determination outlined above. Otherwise, they will receive a Satisfactory grade.

    Course Management

    For the following sections, the word "us" refers to the course staff.

    When in contact with us, contact with either of the two primary instructors (Daniel Wei and Michelle Li) is sufficient to qualify as contact with us.

    The contact information can be found above in the "Instructors" section.

    Academic Integrity

    Each student in this course is expected to abide by the Cornell University Code of Academic Integrity: http://cuinfo.cornell.edu/aic.cfm

    Under the provisions of the Code, anyone who gives or receives unauthorized assistance in the preparation of work at home or during tests in class will be subject to disciplinary action. A student's name on any piece of work is our assurance that they have neither given nor received any unauthorized help in its preparation. Students may assist each other on assignments by answering questions and explaining various concepts. However, one student should not allow another student to copy their work directly. All University policies with respect to cheating will be enforced. A student who is found to have cheated on an exam, or any other graded assignment, will receive a "Uโ€ in the course.

    SDS Accommodations

    Students with Disabilities: Your access in this course is important to us! Please request your accommodation letter early in the semester, or as soon as you become registered with Student Disability Services (SDS), so that we have adequate time to arrange your approved academic accommodations.

    Once SDS approves your accommodation letter, it will be emailed to both you and us. Please follow up with us to discuss the necessary logistics of your accommodations using the contact information provided in this syllabus. If you are approved for exam accommodations, please consult with us at least two weeks before the scheduled exam date to confirm the testing arrangements.

    If you experience any access barriers in this course, such as with printed content, graphics, online materials, or any communication barriers, reach out to us or SDS right away.

    If you need immediate accommodation, please speak with us after class or send an email message to us and SDS at sds_cu@cornell.edu.

    If you have, or think you may have, a disability, please contact Student Disability Services for a confidential discussion: sds_cu@cornell.edu or visit sds.cornell.edu to learn more.

    Mental Health and Well-being

    Your health and wellbeing are important to us.

    There are services and resources at Cornell designed specifically to bolster undergraduate, graduate, and professional student mental health and well-being. Remember, your mental health and emotional well-being are just as important as your physical health. If you or a friend are struggling emotionally or feeling stressed, fatigued, or burned out, there is a continuum of campus resources available to you: https://mentalhealth.cornell.edu/get-support/support-students.

    Help is also available any time day or night through Cornellโ€™s 24/7 phone consultation (607-255-5155). You can also reach out to me, your college student services office, your resident advisor, or Cornell Health for support.

    - + \ No newline at end of file diff --git a/docs/2023sp/lecture1/index.html b/docs/2023sp/lecture1/index.html index 1dd715a47..963ba926a 100644 --- a/docs/2023sp/lecture1/index.html +++ b/docs/2023sp/lecture1/index.html @@ -5,7 +5,7 @@ Lecture 1 | Trends in Web Dev - + @@ -47,7 +47,7 @@ gets evaluated to 0, {} gets evaluated to [object Object], and they both get coerced to strings. Then, adding a list to a string simply adds the contents of the list to the string, so 1 gets appended to the end.

    Example 3

    const zero = +[]; // + coerce [] into 0
    const one = +!![]; // ! coerce [] into false, got inverted, then coerce to 1
    const two = +!![] + +!![]; // 2 = 1 + 1

    const fib2 = (__) =>
    __ === zero || __ === one ? __ : fib2(__ - one) + fib2(__ - two);

    This is the Fibonacci sequence implemented using type coercion.

    In-class Demo 1: Types!

    TODO: @daniel

    • Pre-assessment solutions, but walk them through it and use types
    - + \ No newline at end of file diff --git a/docs/2023sp/lecture10/index.html b/docs/2023sp/lecture10/index.html index 54463cd99..739d00c1a 100644 --- a/docs/2023sp/lecture10/index.html +++ b/docs/2023sp/lecture10/index.html @@ -5,7 +5,7 @@ Lecture 10 | Trends in Web Dev - + @@ -71,7 +71,7 @@ Deno! Deno supports TypeScript out of the box (pretty cool) and was created by Node.js creator Ryan Dahl to fix some things he didn't like about Node.js.

    - + \ No newline at end of file diff --git a/docs/2023sp/lecture2/index.html b/docs/2023sp/lecture2/index.html index 7a3597ca6..5ced1d9fc 100644 --- a/docs/2023sp/lecture2/index.html +++ b/docs/2023sp/lecture2/index.html @@ -5,7 +5,7 @@ Lecture 2 | Trends in Web Dev - + @@ -28,7 +28,7 @@ available for free on GitHub. The series is comprehensive and will teach you everything you want to know.

    Additionally, the MDN Web Docs are a great resource for quickly looking up the documentation for various features in Javascript, complete with examples.

    JavaScript

    We mentioned Mozilla Developer Network as a site for documentation about the JavaScript language, but it's also a great way to get familiar with the language.

    Here is their JavaScript guide: JavaScript Guide - JavaScript | MDN (mozilla.org)

    It's a bit long, so I recommend skimming through the first few parts, up to and including the Objects section (Working with objects - JavaScript | MDN (mozilla.org)).

    JavaScript objects will show up a good amount in this course, so make sure you understand the basics!

    TypeScript

    The official TypeScript website is a great resource to get familiar with the language. There are different guides that assume different programming backgrounds. Choose the article that best suits your background.

    Take a look at the Get Started section here: TypeScript: The starting point for learning TypeScript (typescriptlang.org)

    If you've gone through the MDN JavaScript guide, fill in your TypeScript knowledge with this: TypeScript: Documentation - TypeScript for JavaScript Programmers (typescriptlang.org)

    There is also a Handbook that you can chug through if interested (not necessary at all): TypeScript: Handbook - The TypeScript Handbook (typescriptlang.org)

    Hope this is helpful for you all! This will be the language you'll be working with all-semester, so being comfortable with the language will pay off.

    In-Class Demo 1: Executing Javascript in node and in the browser

    TODO: @daniel

    • Walk them through using node to execute javascript line by line, as well as using the console to execute javascript line-by-line.
    • Specifically,
    - + \ No newline at end of file diff --git a/docs/2023sp/lecture3/index.html b/docs/2023sp/lecture3/index.html index bcdd3cef9..a3f018cba 100644 --- a/docs/2023sp/lecture3/index.html +++ b/docs/2023sp/lecture3/index.html @@ -5,7 +5,7 @@ Lecture 3 | Trends in Web Dev - + @@ -92,7 +92,7 @@ with your VSCode correctly, you should be getting linter warnings directly in your IDE. However, you can also run this command to run the linter.

    You can run these scripts with yarn [script], for example yarn dev.

    And that's it! Feel free to play around with this project, and we'll be diving into React in the next lecture.

    How React?

    Allows developers to create reusable UI components and manage the state of those components efficiently. React uses a virtual DOM (Document Object Model) to improve performance by minimizing the amount of DOM manipulation required when a user interacts with a React application. This allows for efficient updates and rendering of components, making it a popular choice for building complex and high-performing web and mobile applications.

    In essence, it's built around a few core features:

    • Reusable components
    • Reactivity (state management)

    Conceptually, everything in React is a function. Your entire UI is therefore a function of your state. This is a very powerful concept.

    Components

    Components are the building blocks of visual React. They are reusable pieces of code that can be used to build more complex components. These functional components form the visual basis of all React applications. They are defined as functions that return a React element. They are the simplest way to define a component.

    Here, an example of a functional component (implemented as an arrow function):

    const MyComponent = () => {
    return <div>Hello World!</div>;
    };

    Then, elsewhere, you can reuse MyComponent, such as in a higher-order App.tsx component:

    const App = () => {
    return (
    <div>
    <MyComponent />
    </div>
    );
    };

    The returned code is JSX, which is a JavaScript extension that allows you to write HTML-like code in JavaScript. It is compiled to JavaScript by Babel, and then inserted as HTML into the DOM.

    Thus, we can compose components together to build more complex components. This is the basis of React's component-based architecture, which is compositional. This is a very powerful concept, and we'll cover it in more detail later in the next lecture, once you have a more firm grasp of basic React.

    Hooks

    Hooks are the building blocks of logical React. They are reusable pieces of code that can be used to manage the state of your application, and are the most powerful feature of React. Changes to a hook variable will cause components and other hooks that depend on it to re-render or re-calculate their values, which is the basis of React's reactivity.

    Like for components, you can create custom hooks to encapsulate logic and reuse it across your application. However, extremely frequently, you'll also use the built-in hooks provided by React.

    The most important hook is useState, which simply provides a getter and setter for a variable. This is the basis of React's state management.

    Here, an example of a component that uses useState:

    const MyComponent = () => {
    const [count, setCount] = useState(0);

    return (
    <div>
    <p>You clicked {count} times</p>
    <button onClick={() => setCount(count + 1)}>Click me</button>
    </div>
    );
    };

    The other most important built-in hook is useEffect, which allows you to run code when a component is mounted or unmounted, or when a variable changes. This is the basis of React's lifecycle management.

    Lifecycle management is when you want to run code when a component is mounted or unmounted, or when a variable changes, or on every render. It encapsulates logic that must in essence run at certain points during a component's lifecycle.

    Here, an example of a component that uses useEffect:

    const MyComponent = () => {
    const [count, setCount] = useState(0);

    useEffect(() => {
    console.log('The count changed!'); // this will fire every time count variable changes
    }, [count]);

    useEffect(() => {
    console.log('The component mounted or the count changed!'); // this will fire every render (all the time)
    });

    useEffect(() => {
    console.log('The component mounted!'); // this will fire once, when the component is mounted
    return () => {
    console.log('The component unmounted!'); // this will fire once, when the component is unmounted
    };
    }, []);

    return (
    <div>
    <p>You clicked {count} times</p>
    <button onClick={() => setCount(count + 1)}>Click me</button>
    </div>
    );
    };

    Combining components and hooks, you can build complex applications with React that respond to events, update their state accordingly, and then re-render the UI to reflect the new state!

    Today, let's build an extremely simple React app to get a feel for how React works. Next time, we'll use useState, useEffect, and components more extensively, along with other React features.

    In-class Demo 1: An Extremely Simple React App

    TODO: @daniel

    • Let's implement a simple minutes-since-event counter. It'll increase indefinitely, but can be reset to 0.
    - + \ No newline at end of file diff --git a/docs/2023sp/lecture4/index.html b/docs/2023sp/lecture4/index.html index efbf9b915..3e19693b6 100644 --- a/docs/2023sp/lecture4/index.html +++ b/docs/2023sp/lecture4/index.html @@ -5,7 +5,7 @@ Lecture 4 | Trends in Web Dev - + @@ -83,7 +83,7 @@ it can have whatever arguments and return type that you choose.

    Learn more about custom hooks here

    In-class Demo 1: Complete Contact info interface

    Using useRef, useMemo, useCallback, useState, and useEffect, as well a custom hook or two, and multiple levels of composition, we'll create a complete and searchable contact book interface!

    TODO: @daniel

    - + \ No newline at end of file diff --git a/docs/2023sp/lecture5/index.html b/docs/2023sp/lecture5/index.html index 9423c44fa..0c51c9cfc 100644 --- a/docs/2023sp/lecture5/index.html +++ b/docs/2023sp/lecture5/index.html @@ -5,14 +5,14 @@ Lecture 5 | Trends in Web Dev - +
    Version: 2023sp

    Lecture 5

    Homework: Assignment 2: CSS and Complex React, is due 3/31 by 11:59pm

    Slides: Here

    Explore more:

    • Prefabricated React components in Google's Material UI Style: React MUI

    Now that we have a complete understanding of React for building the raw HTML frontend of our application and manipulating that interface, let's take a look at that most-loved of frontend tasks: styling.

    Today's Lecture 3/20

    By the end of today, you should be able to:

    • Understand the basics of how to use CSS

    An Intro to CSS

    CSS, or Cascading Style Sheets, allows us to control the appearance of HTML elements. Itโ€™s the standard โ€œlanguageโ€ of styling. Along with HTML and JS, itโ€™s considered one of the three fundamental web languages. Itโ€™s also an extremely simple, non-Turing-complete language.

    CSS Selectors

    CSS selectors are the things that we use to select HTML elements. For example, we can use the h1 selector to select all h1 elements, or the .my-class selector to select all elements with the my-class class.

    CSS Properties

    CSS properties are the building blocks of CSS. They are the things that we use to style our HTML elements. For example, we can use the color property to change the color of text, or the background-color property to change the background color of an element.

    CSS Concepts

    The Box Model

    The box model is a concept that describes how HTML elements are laid out on the page. Itโ€™s a very simple concept, but itโ€™s important to understand it in order to style elements properly.

    The box model is a box that wraps around every HTML element. It consists of margins, borders, padding, and the actual content.

    Refer to the slides, or this article for more information.

    Positioning

    Positioning is a concept that describes how HTML elements are positioned on the page. Itโ€™s a very simple concept, but itโ€™s important to understand it in order to style elements properly.

    There are four types of positioning:

    • Static
    • Relative
    • Absolute
    • Fixed

    Static Positioning

    Static positioning is the default positioning of HTML elements. It means that the element is positioned according to the normal flow of the page. This means that the element will be positioned where it would be if no positioning was applied.

    Relative Positioning

    Relative positioning is a type of positioning that is relative to the normal flow of the page. This means that the element will be positioned where it would be if no positioning was applied, but then offset by the specified amount.

    Absolute Positioning

    Absolute positioning is a type of positioning that is relative to the nearest positioned ancestor. This means that the element will be positioned where it would be if no positioning was applied, but then offset by the specified amount.

    This removes the element from the normal flow of the page, and it will not affect the position of other elements on the page.

    Fixed Positioning

    Fixed positioning is a type of positioning that is relative to the viewport. This means that the element will be positioned where it would be if no positioning was applied, but then offset by the specified amount.

    This removes the element from the normal flow of the page, and it will not affect the position of other elements on the page.

    CSS Selectors

    CSS selectors are the things that we use to select HTML elements. For example, we can use the h1 selector to select all h1 elements, or the .my-class selector to select all elements with the my-class class, or the #my-id selector to select the element with the my-id id.

    We can also combine selectors to select multiple elements. For example, we can use the h1.my-class selector to select all h1 elements with the my-class class, or the h1, h2, h3 selector to select all h1, h2, and h3 elements.

    You can also apply multiple classes to an element. For example, <h1 class="my-class my-other-class">Hello, world!</h1>.

    You can also apply the same properties to multiple selectors. For example, h1, h2, h3 { color: red; } will make all h1, h2, and h3 elements red.

    You can also select elements based on their ancestors. For example, ul li { color: red; } will make all li elements that are descendants of ul elements red.

    A more advanced concept is to select elements based on their immediate and direct parent elements: ul > li { color: red; } will make all li elements that are direct descendants of ul elements red.

    Units

    There are many different units that we can use to specify the size of things in CSS. The most common ones are:

    • px: Pixels. This is the default unit of measurement for CSS. Itโ€™s a fixed unit of measurement, meaning that it will always be the same size on the screen.
    • rem: Relative to the font size of the root element. This is a relative unit of measurement, meaning that it will be a different size depending on the font size of the root element.
    • %: Relative to the parent element. This is a relative unit of measurement, meaning that it will be a different size depending on the size of the parent element.
    • vh: Relative to the height of the viewport. This is a relative unit of measurement, meaning that it will be a different size depending on the height of the viewport.
    • vw: Relative to the width of the viewport. This is a relative unit of measurement, meaning that it will be a different size depending on the width of the viewport.

    Calculations

    We can also perform calculations in CSS. For example, width: calc(100% - 20px); will make the width of an element 100% of the width of its parent element, minus 20 pixels.

    Pseudo-classes

    Pseudo-classes are special selectors that allow us to select elements based on their state. For example, we can use the :hover pseudo-class to select elements when the user is hovering over them with their mouse.

    Cascading

    CSS is a cascading language. This means that the order in which we apply CSS rules matters. The last rule that is applied to an element will be the one that is used. Later properties can effectively override earlier properties.

    Media Queries and Clamping

    Media queries allow us to apply different CSS rules based on the size of the viewport. For example, we can use a media query to apply different CSS rules when the viewport is less than 600 pixels wide. For example:

    @media (max-width: 600px) {
    .my-class {
    width: 100%;
    }
    }

    Alternatively, clamping is a way to clamp a value between a minimum and maximum value. For example, clamp(0px, 100%, 600px) will return the value 100% if the value is between 0px and 600px, and will return the value 0px if the value is less than 0px, and will return the value 600px if the value is greater than 600px.

    CSS Variables

    CSS variables are a way to define variables in CSS. For example, --my-variable: red; will define a variable called --my-variable with the value red. We can then use this variable in our CSS rules. For example, color: var(--my-variable); will set the color of an element to the value of the --my-variable variable.

    Normally, CSS variables are defined in the :root selector. For example:

    :root {
    --my-variable: red;
    }

    We can then use this variable in our CSS rules. For example:

    .my-class {
    color: var(--my-variable);
    }

    Flexbox and CSS Grid

    Flexbox and CSS Grid are two different ways to layout elements on the page. Flexbox is a one-dimensional layout system, meaning that it only works on one axis at a time. CSS Grid is a two-dimensional layout system, meaning that it works on both the horizontal and vertical axes at the same time.

    Refer to the documentation for Flexbox here, and for CSS Grid here.

    - + \ No newline at end of file diff --git a/docs/2023sp/lecture6/index.html b/docs/2023sp/lecture6/index.html index 04c5ef90c..2f91d2e75 100644 --- a/docs/2023sp/lecture6/index.html +++ b/docs/2023sp/lecture6/index.html @@ -5,7 +5,7 @@ Lecture 6 | Trends in Web Dev - + @@ -42,7 +42,7 @@ function.

    Here is an example of doing equivalent things with either syntax:

    const thenCatchExample = () => {
    fetch(`https://jsonplaceholder.typicode.com/posts`)
    .then((res) => res.json())
    .then((d) => setData(d));
    };

    const asyncAwaitExample = async () => {
    const res = await fetch(`https://jsonplaceholder.typicode.com/posts`);
    const d = await res.json();
    setData(d);
    };

    In order to handle rejected Promises using async/await, just wrap all your await statements in a try...catch block!

    Like this:

    try {
    asyncAwaitExample();
    } catch (error) {
    // display any errors that may occur from async/await function
    console.error(error);
    }

    Quick Postman Demo

    Check out Postman to send API requests! Here

    Live Demo Material

    You can get the starter code for the live demo by running: yarn create next-app --typescript --example "https://github.com/cornell-dti/trends-sp23-lec6-demo" YOUR_DIR_NAME

    (Replace YOUR_DIR_NAME with whatever you want to name your directory!)

    - + \ No newline at end of file diff --git a/docs/2023sp/lecture7/index.html b/docs/2023sp/lecture7/index.html index ae22ec571..7bedc8c68 100644 --- a/docs/2023sp/lecture7/index.html +++ b/docs/2023sp/lecture7/index.html @@ -5,7 +5,7 @@ Lecture 7 | Trends in Web Dev - + @@ -52,7 +52,7 @@ first.

    await peopleCollectionRef
    .doc(โ€˜myl39โ€™)
    .update({ age: '20', first: 'michelle' });

    Note if the document we're trying to access is not available, an error will occur.

    Delete

    Deleting a document removes it from the collection.

    await peopleCollectionRef
    .doc(โ€˜myl39โ€™)
    .delete();

    Callback/Promise-based vs Real-Time Queries

    Promise-BasedReal-Time
    If you need the data now, you can query for itYou already have the data
    Data queries can be decentralized (done in any component)Data queries are fetched and memoized through centralized (React) hooks
    Querying data is imperative, but can quickly become hard to maintain and track (and you lose some of the advantages of a declarative web UI framework)Up-front cost to query data pays off (because you don't hopefully have to query it again)
    There is no cleanup codeYou first have to "subscribe" to changes in the data, then unsubscribe after you are done (kind of like opening and closing a file stream when reading a file)

    What do Callback/Promise-based vs. Real-Time Queries Look Like?

    Promise-based queries are single queries that return a single async result. So, they are run once and then passed along downstream to children and other descendants of your component. Typically, they are used to react to some update (i.e user clicks a button, a component loads).

    Real-time queries are single queries that return a stream of async results such as weather data. These types of queries are used once the data is listenable and needs to be "subscribed to". These take a stream of results and are built on top of wbe sockets, which are abstractions over a byte stream. So, they're good for ... real-time applications.

    How Do Callback/Promised Based vs. Real-Time Queries Work?

    Promise based queries typically calls some backend API route, which fetches and returns data to you. They're built on top of traditional HTTP requests.

    Real-time queries might call a backend route to pass data over to a web socket or it'll simply use an API library to makes calls directly to a database (ex. Firebase Firestore call). These queries are usually wrapped in a library like RxJS's observable data type or function calls that allow you to subsribe to changes.

    Choosing a Querying Method

    As described in the first section, the type of queries your application will use will affect the app's architecture. In particular, real-time queries play nicely with having a centralized query that runs over a listenable data access object that is "owned" either by

    1. a top-level component (OK in small apps, but prone to prop drilling in more complex apps), or
    2. a custom React hook that wraps an effect (triggering an update when the data access object publishes a new version of the data)

    That is not to say that your app cannot use both types of queries. It is just that a real-time application requires a specific architecture in which all data is queried first and passed along to components as props or referenced by components via (potentially custom) React/Redux hooks. This does not play nicely with callback/Promise-based queries because the data from the callback/Promise-based queries may be in an inconsistent state by the time the data from a real-time query has updated.

    Firebase Firestore Application: Callback/Promise-based or Real-Time Queries

    Firestore offers you a database that nicely organizes your data into documents and collections (groups of documents). It allows you to build queries that can either

    1. return once with a single snapshot of data (a Promise-based query), or
    2. allow you to hook into the data's live values (a real-time query).

    Firestore Real-time Queries

    Provides collection + document data as an listenable (subscribable) data object

    • As soon as a collection updates, the collection access object publishes a new version of the collection
    • As soon as a doc updates, the doc access object publishes a new version of the doc This can be passed as a React prop or an effect dependency, which triggers a component update!

    Anatomy of a Firebase Firestore Real-Time Application (The "Full" Stack)

    Anatomy of a Firebase Firestore Real-Time Update

    Unlike callback/promise-based queries, the connection between updating and fetching data is completely gone. Updating data occurs along an entirely separate channel from subscribing to the data. This means that implementing calls to update data will look very different

    Miscellaneous Advice

    When designing a system:

    • avoid two-way dependencies (or as many dependencies as possible)
      • as with React & declarative web frameworks, one-way data binding is the way to go
      • avoids: more things to update
      • avoids: more surface area for synchronization errors

    This philosophy helps us prefer real-time queries over Promise-based queries, because there is only a single dependency for the queried data, rather than the set of all the decentralized Promise-based queries.

    Sample code

    This week's sample code starter can be found in the files under this directory.

    - + \ No newline at end of file diff --git a/docs/2023sp/lecture8/index.html b/docs/2023sp/lecture8/index.html index 9e37db819..5be7ef73e 100644 --- a/docs/2023sp/lecture8/index.html +++ b/docs/2023sp/lecture8/index.html @@ -5,7 +5,7 @@ Lecture 8 | Trends in Web Dev - + @@ -15,7 +15,7 @@ Creating a Collection

    (Now repeat this for the other two collections)

    Set Up Authentication

    As mentioned above, Firebase has nice integration with Google sign-on. Let's take advantage of this!

    Open the Authentication Tab... Opening the Authentication Tab

    ...and select the 'Google' authentication strategy (this uses the Google sign-on) Choosing the Google Auth strategy

    And on the client-side:

    // TODO: Replace with your own Firebase config
    const firebaseConfig = {
    apiKey: 'asfasdfasdf',
    authDomain: 'trends-sp22-lecture-8.firebaseapp.com',
    projectId: 'trends-sp22-lecture-8',
    storageBucket: 'trends-sp22-lecture-8.appspot.com',
    messagingSenderId: 'sijiofdsjdi',
    appId: '1:3209483200:web:u897j8ydq973342',
    };

    const app = getApps().length ? getApp() : initializeApp(firebaseConfig);

    const db = getFirestore(app);

    const provider = new GoogleAuthProvider();

    provider.setCustomParameters({
    login_hint: 'user@example.com',
    hd: 'cornell.edu',
    });
    provider.addScope('email');

    const auth = getAuth();
    signInWithPopup(auth, provider)
    .then((result) => {
    // This gives you a Google Access Token. You can use it to access the Google API.
    const credential = GoogleAuthProvider.credentialFromResult(result);
    const token = credential?.accessToken;
    // The signed-in user info.
    const user = result.user;
    userUpload(user, db);
    // ...
    })
    .catch((error) => {
    // Handle Errors here.
    const errorCode = error.code;
    const errorMessage = error.message;
    // The email of the user's account used.
    const email = error.email;
    // The AuthCredential type that was used.
    const credential = GoogleAuthProvider.credentialFromError(error);
    // ...
    });

    export { db };

    Architecting the App

    Avoid Hard-coding Routes!

    It's a good practice to avoid hard-coding constants such as the path to each collection. Better to include these into a fireRoutes.ts file:

    export const BOOKS_PATH = 'books';
    export const REVIEWERS_PATH = 'reviewers';
    export const REVIEWS_PATH = 'reviews';

    Writing our collection query hooks

    With the database set up, we need to build queries on the database as well as actions that can write to the database. To avoid prop drilling, we need to build custom React hooks that allow any component to use and "hook into" our data. Our custom hooks need to always have the most up-to-date data available (it is a real-time database after all), so we need to store the information in state variables (so that any components using these variables will be updated when the variable updates).

    We can start this a file fireHooks.ts:

    const useCollectionWithCallback = (
    collectionId: string,
    callback: () => void,
    ) => {
    const [coll, setColl] = useState<DocumentData[] | undefined>();
    const collectionRef = collection(db, collectionId);
    // Trigger an effect whenever the query returns a new snapshot
    useEffect(() => {
    const unsubscribe = onSnapshot(query(collectionRef), (querySnapshot) => {
    const docsInCollection: DocumentData[] = [];

    querySnapshot.forEach((doc) => docsInCollection.push(doc.data()));
    // in the effect, set the collection data. This triggers an update in any component using 'coll' (using this collection hook).
    setColl(docsInCollection);
    callback();
    });
    return () => {
    // run any any cleanup code
    unsubscribe();
    };
    }, [collectionId]);
    return coll;
    };

    Alternatively, in a slightly nicer (more functional, more Observable-y way), we can use the rxFire package to simplify some of the code for us:

    const useCollectionWithCallback2 = (
    collectionId: string,
    callback: () => void,
    ) => {
    const [coll, setColl] = useState<DocumentData[] | undefined>();
    const collectionRef = collection(db, collectionId);
    // trigger an effect whenever the collectionData observable publishes a new version of the data
    useEffect(() => {
    const subscription = collectionData(collectionRef).subscribe(
    (c: DocumentData[]) => {
    // in the effect, set the collection data. This triggers an update in any component using 'coll' (using this collection hook).
    setColl(c);
    callback();
    },
    );
    return () => {
    // run any any cleanup code
    subscription.unsubscribe();
    };
    }, [collectionId]);
    return coll;
    };

    Build Actions to Write to our Database

    Recall the 'anatomy of a Firestore real-time app' image. Now that we have hooked into our data, we need calls that will write to the data. In our case, we need calls to add, edit, and delete reviews. We also need calls to add books and get books/reviews by ID. NOTE: in this tutorial, we use the shortcut of concatenating titles and authors/reviewers to generate document IDs. DO NOT ACTUALLY DO THIS! Do the extra work of generating a Firestore document id with doc().

    Editing reviews:

    export const editReview = async (id: string, update: Partial<FireReview>) => {
    await setDoc(doc(db, REVIEWS_PATH, id), update, { merge: true });
    };

    Adding reviews:

    export const addReview = async (id: string, book: FireReview) => {
    // shh
    editReview(id, book);
    };

    Deleting reviews.

    export const deleteReview = async (id: string) => {
    await deleteDoc(doc(db, REVIEWS_PATH, id));
    };

    Adding a book (when there is a new revew on a book that does not quite exist). Note that we use a transaction to create the book, because multiple users can attempt to create a book at the same time, so there may be data races (and we want to avoid duplicate entries).

    export const addBook = async (id: string, book: FireBook) => {
    try {
    await runTransaction(db, async (transaction) => {
    const bookDocRef = doc(db, BOOKS_PATH, id);
    const bookDoc = await transaction.get(bookDocRef);

    if (bookDoc.exists()) {
    throw `Book ${book.title} by ${book.author} already exists!`;
    }

    transaction.update(bookDocRef, book);
    });
    } catch (e) {
    console.log('Transaction failed: ', e);
    }
    };

    Getting books and reviews by id:

    export const getBookId = (book: FireBook) => {
    return `${book.title}::${book.author}`;
    };
    export const getReviewId = (review: FireReview) => {
    return `${review.title}::${review.author}::${review.reviewer}`;
    };

    Uploading a user when auth:

    export const userUpload = (user: User | null, db: Firestore) => {
    if (user != null) {
    const uid = user.uid;
    const email = user.email || 'Dummy Email';

    runTransaction(db, async (transaction) => {
    const userDocumentReference = doc(collection(db, REVIEWERS_PATH), uid);

    const userDocument = await transaction.get(userDocumentReference);
    if (!userDocument.exists()) {
    const fullUserDocument: FireReviewer = {
    email,
    };
    transaction.set(userDocumentReference, fullUserDocument);
    }
    // eslint-disable-next-line no-console
    }).catch(() => console.error('Unable to upload user.'));
    }
    };

    Finally, the filters to search & sort reviews

    import { FireReview } from '../types';

    export const sortByRating = (reviews: FireReview[]) =>
    [...reviews].sort((reviewA, reviewB) => reviewA.rating - reviewB.rating);

    export const filterByTitle = (reviews: FireReview[], title: string) =>
    reviews.filter((review) => review.title === title);

    export const filterByAuthor = (reviews: FireReview[], author: string) =>
    reviews.filter((review) => review.author === author);

    export const filterByReviewer = (reviews: FireReview[], reviewer: string) =>
    reviews.filter((review) => review.reviewer === reviewer);

    export const filterByBook = (
    reviews: FireReview[],
    title: string,
    author: string,
    ) =>
    reviews.filter(
    (review) => review.title === title && review.author === author,
    );

    Now how can we use the above functions to implement the main feature of our books review platform?

    export const getAvgRatingForBook = (
    reviews: FireReview[],
    title: string,
    author: string,
    ) => {
    const filteredList = filterByBook(reviews, title, author);
    return (
    filteredList.reduce((prevSum, review) => prevSum + review.rating, 0) /
    filteredList.length
    );
    };

    export const paginateReviews = (
    reviews: FireReview[],
    resultsPerPage: number,
    page: number,
    ) => {
    const lastPage = Math.ceil((reviews.length + 1) / page);
    const pageSanitized = Math.min(Math.max(0, page), lastPage);

    return reviews.filter(
    (value, i) =>
    i > pageSanitized * resultsPerPage &&
    i < Math.min(pageSanitized + 1, lastPage),
    );
    };
    - + \ No newline at end of file diff --git a/docs/2023sp/lecture9/index.html b/docs/2023sp/lecture9/index.html index 887cf4cf6..a0616b232 100644 --- a/docs/2023sp/lecture9/index.html +++ b/docs/2023sp/lecture9/index.html @@ -5,7 +5,7 @@ Lecture 9 | Trends in Web Dev - + @@ -57,7 +57,7 @@ specifies who created the task.

    Then in our query to retrieve from the database, we can filter for only tasks that belong to the current user.

    Frodo.tsx
    const { user } = useAuth();

    //...

    const taskQuery = query(
    collection(db, 'tasks'),
    where('owner', '==', user!.uid),
    );

    Similarly, we can populate the owner field whenever we create a task.

    TaskAddControl.tsx
    const { user } = useAuth();

    //...

    const task: Task = {
    text: input,
    checked: false,
    owner: user!.uid,
    };
    addDoc(collection(db, 'tasks'), task);

    Demo code

    Feel free to reference our demo code to implement authentication in your final project!

    In addition, here is a supplemental video explaining the demo code: Video Link

    - + \ No newline at end of file diff --git a/docs/2023sp/setup-editor/index.html b/docs/2023sp/setup-editor/index.html index bfb44f601..27ee7e479 100644 --- a/docs/2023sp/setup-editor/index.html +++ b/docs/2023sp/setup-editor/index.html @@ -5,7 +5,7 @@ Setup your editor | Trends in Web Dev - + @@ -13,7 +13,7 @@
    Version: 2023sp

    Setup your editor

    For convenience, we assume you will use VSCode. If you are using WebStorm and Atom, you are likely to find some extensions that provide similar functionalities.

    To install extensions in VS Code, navigate to the left-hand sidebar, and click the building blocks icon at the bottom. This should take you to the Extensions marketplace.

    ESLint

    Installing ESLint in VSCode will give you real-time linter feedback in any JavaScript code you write, allowing you to quickly pinpoint many problems and have readable, proper formatting.

    Once installed, add these lines to your VSCode Settings (refer to this link if you need help getting there or alternatively open the command palette in VSCode with CMD/CTRL + SHIFT + P and search settings.json):

      // Other settings ...
    "eslint.alwaysShowStatus": true,
    "editor.codeActionsOnSave": {
    "source.fixAll.eslint": true
    },
    "eslint.packageManager": "yarn"

    Bracket Pair Colorizer

    Highlights matching brackets to make code easier to read.

    npm

    This will be useful later when inspecting package.json files.

    - + \ No newline at end of file diff --git a/docs/2023sp/setup-environment/index.html b/docs/2023sp/setup-environment/index.html index 7a961b1d6..67a7ecbb2 100644 --- a/docs/2023sp/setup-environment/index.html +++ b/docs/2023sp/setup-environment/index.html @@ -5,13 +5,13 @@ Setup your development environment | Trends in Web Dev - +
    Version: 2023sp

    Setup your development environment

    Install Node.js

    Go to this website and follow the instructions.

    For consistency, we will use Node LTS (currently, as of Spring 2023, this is Node 18).

    After installing, you should be able to run node in your terminal and get a command-line interface to execute JavaScript code, line-by-line.

    Install Yarn

    For convenience, we assume you will use Yarn instead of npm. However, you can use either for the purposes of this project.

    Go to this website and follow the instructions.

    - + \ No newline at end of file diff --git a/docs/assignment1/index.html b/docs/assignment1/index.html index 5f5e96518..ccedfd16e 100644 --- a/docs/assignment1/index.html +++ b/docs/assignment1/index.html @@ -5,14 +5,14 @@ Assignment 1 | Trends in Web Dev - +
    Version: 2024sp

    Assignment

    Due Mar 4 by 11:59pm

    Run the following command in an appropriate folder on your system:

    pnpm dlx degit cornell-dti/trends-mono/frontend-hw-1-starter my-project-name
    cd my-project-name
    pnpm install

    Then, see main.js for instructions.

    Submission

    Please submit to CMS your main.js file containing your implementations of each of the functions described above.

    - + \ No newline at end of file diff --git a/docs/assignment2/index.html b/docs/assignment2/index.html index 143bce17a..bbd1cf4eb 100644 --- a/docs/assignment2/index.html +++ b/docs/assignment2/index.html @@ -5,14 +5,14 @@ Assignment 2 | Trends in Web Dev - +
    Version: 2024sp

    Assignment

    Due Mar 11 by 11:59pm

    Run the following command in an appropriate folder on your system:

    pnpm dlx degit cornell-dti/trends-mono/frontend-hw-2-starter my-project-name
    cd my-project-name
    pnpm install

    Then, see src/main.ts for instructions.

    Submission

    Please submit to CMS your main.ts file containing your implementations of each of the functions described above.

    - + \ No newline at end of file diff --git a/docs/assignment3/index.html b/docs/assignment3/index.html index 6309de606..0e4ff0527 100644 --- a/docs/assignment3/index.html +++ b/docs/assignment3/index.html @@ -5,13 +5,13 @@ Assignment 3 | Trends in Web Dev - +
    Version: 2024sp

    Assignment

    Due Mar 25 by 11:59pm

    Run the following command in an appropriate folder on your system:

    pnpm dlx degit cornell-dti/trends-mono/frontend-hw-3-starter my-project-name
    cd my-project-name
    pnpm install

    Then, see src/Paginator.tsx for instructions.

    Submission

    Please submit to CMS your Paginator.tsx file.

    - + \ No newline at end of file diff --git a/docs/assignment4/index.html b/docs/assignment4/index.html index 5dded1c0d..3888eb5bd 100644 --- a/docs/assignment4/index.html +++ b/docs/assignment4/index.html @@ -5,13 +5,13 @@ Assignment 4 | Trends in Web Dev - +
    Version: 2024sp

    Assignment

    Due Apr 8 at 11:59pm

    Run the following command in an appropriate folder on your system:

    pnpm dlx degit cornell-dti/trends-mono/frontend-hw-4-starter my-project-name
    cd my-project-name
    pnpm install

    Then, see src/components/Gallery.tsx for instructions.

    Submission

    Please submit to CMS your Gallery.tsx file.

    - + \ No newline at end of file diff --git a/docs/assignments/index.html b/docs/assignments/index.html index 530d9db19..055696438 100644 --- a/docs/assignments/index.html +++ b/docs/assignments/index.html @@ -5,14 +5,14 @@ Assignments | Trends in Web Dev - +
    Version: 2024sp

    Assignments

    Assignments will be released here after lecture! There will be 5 assignments total, including a final project spanning the last few weeks of the class.

    You are allowed max 3 slip days (out of 6 total for all assignments and the final project) per an assignment.

    The class is scheduled to finish well before finals week.

    Assignment 1: Due on CMSX by Mar 4 at 11:59pm

    Assignment 2: Due on CMSX by Mar 11 at 11:59pm

    Assignment 3: Due on CMSX by Mar 25 at 11:59pm

    Assignment 4: Due on CMSX by Apr 8 at 11:59pm


    Independent Project Starter Templates

    Frontend Starters

    If you're trying to create a new frontend React project on your own, cd into a directory of your choice, and then use one of our starter templates (highly recommended):

    The following command creates an incredibly simple React project:

    pnpm dlx degit cornell-dti/trends-mono/frontend-starter your-project-name

    The following command creates a more advanced React project with a component library, icons, and routing built in for you:

    pnpm dlx degit cornell-dti/trends-mono/frontend-starter-advanced your-project-name

    Note that neither of these projects include a backend!


    Why use our custom frontend templates?

    In the past, people recommended using Create React App. However, in recent years, popular opinion has turned against this specific starter: in fact, it was removed from the official React documentation recently. Since we're literally "Trends in Web Development", we want to use the latest and greatest tools, and that means not using Create React App.

    Well, where are the Trends pointing towards? Past semesters of Trends used Next.js, a popular React metaframework (that is, a framework built atop another framework) that adds a lot of useful features to React. However, the future of Next.js is increasingly unclear: it's heavily opinionated, with a business focus on locking its users into its ecosystem, increasingly slow and unnecessarily complex, and just increasingly controversial.

    So, we've created our own lightweight starter template built atop Vite, an incredibly popular, fast, and un-opinionated build tool by Evan You, the creator of Vue.js. Using industry standard technologies like React Router and easy-to-get-started-with libraries like Mantine and Lucide, we've created a starter template that's easier to use, easier to understand, and easier to build upon.

    Disagree? That's fine! You're free to use whatever React-based metaframework you want for your project, whether that's Next.js, Remix, Create React App, or something else.

    - + \ No newline at end of file diff --git a/docs/finalproject/index.html b/docs/finalproject/index.html index cc2893ec6..74b244d68 100644 --- a/docs/finalproject/index.html +++ b/docs/finalproject/index.html @@ -5,7 +5,7 @@ Final Project | Trends in Web Dev - + @@ -19,7 +19,7 @@ get most of the points here; the only way to lose points is if it is clear that not a lot of effort has been put in or you are consistently missing milestones.
  • Milestones + Final Presentation (15%)

    • Each milestone is worth 5% of your grade.
    • Attending the final presentation is worth 5% of your grade.
  • Peer Review (5%)

  • Tips for Success!

    • Get in contact with your partner early! Better initial planning means less frustrations later on.
    • Be realistic. We know you are ambitious, but also understand your own capabilities, and avoid changing ideas halfway through.
    • Use git.
      • It is great for sharing code between you and your partner/team members. Please don't just email code back and forth.
      • Use git branches!
        • When developing a feature, you should open up a new GitHub branch rather than committing and pushing directly to the main branch. This will allow you to develop your feature independently of the current state of main (and what your partners are doing) and only merge in when you are sure your feature is done and works.
        • Branches can also protect you from weird frustrating merge conflicts (so you can focus on developing awesome features!)
    • Pair programming is fun!
      • Ideally, you should both be actively involved in the whole development process. A good way to achieve this is to step up a time to pair program and code together!
    • Also refer to tips in How to Lose in CS 2112
    - + \ No newline at end of file diff --git a/docs/introduction/index.html b/docs/introduction/index.html index 7305ce47d..156074722 100644 --- a/docs/introduction/index.html +++ b/docs/introduction/index.html @@ -5,12 +5,12 @@ Syllabus | Trends in Web Dev - +
    -
    Version: 2024sp

    INFO 1998: Trends in Modern Web Development

    Spring 2024

    Course Website: https://webdev.cornelldti.org/docs/introduction

    Learn to build and deploy scalable, modern full-stack web applications using best practices and the industry's most-used technologies. This is a student run course by Cornell DTI

    Faculty Advisor: Kyle Harms

    Email: kyle.harms@cornell.edu

    Instructors: Sarah Young, Sophia Pham

    Sarah Young

    Email: sy398@cornell.edu

    Sophia Pham

    Email: tpp38@cornell.edu

    Office Hours Schedule

    TBD

    Credits and Credit Hour Options

    1.0-credits, S/U

    Time and Location

    This course meets once a week for 9 weeks for a total of 9 lectures.

    We will meet weekly on Mondays, from 7:30pm to 8:20pm in Hollister 110. The start of the class will TENTATIVELY start on Monday, February 19.

    Course Description

    This class will teach students about modern web development technologies, +

    Version: 2024sp

    INFO 1998: Trends in Modern Web Development

    Spring 2024

    Course Website: https://webdev.cornelldti.org/docs/introduction

    Learn to build and deploy scalable, modern full-stack web applications using best practices and the industry's most-used technologies. This is a student run course by Cornell DTI

    Faculty Advisor: Kyle Harms

    Email: kyle.harms@cornell.edu

    Instructors: Sarah Young, Sophia Pham

    Sarah Young

    Email: sy398@cornell.edu

    Sophia Pham

    Email: tpp38@cornell.edu

    Office Hours Schedule

    TBD

    Credits and Credit Hour Options

    1.0-credits, S/U

    Time and Location

    This course meets once a week for 9 weeks for a total of 9 lectures.

    We will meet weekly on Mondays, from 7:30pm to 8:45pm in Hollister 110. The start of the class will TENTATIVELY start on Monday, February 19.

    Course Description

    This class will teach students about modern web development technologies, practices, and industry standards to better equip them for academic work, research, interviews, internships, and full-time employment. By the end of this course, you will gain key experience in designing systems on both the frontend @@ -20,7 +20,7 @@ Please note that you are NOT allowed to use slip days for the final submission of the final project.

    The class is scheduled to finish well before finals week.

    AssignmentTopicDue Date
    Assignment 1JavaScript FundamentalsMar 4 by 11:59pm
    Assignment 2TypeScript FundamentalsMar 11 by 11:59pm
    Assignment 3React FundamentalsMar 25 by 11:59pm
    Assignment 4Frontend CapstoneApr 8 by 11:59pm
    Assignment 5Final ProjectMay 4 by 11:59pm

    More details regarding the final project will be released upon starting the course.

    Method of Assessing Student Achievement

    Basis of Grade Determination

    First, we will determine your numerical grade. This will be done by the following:

    AssignmentPercentage of Grade
    Attendance20% (based on weekly lecture quizzes, can miss 1 of 9 without penalty)
    Filling out Feedback10%
    Final Project30%
    Assignments40%

    Your final grade will be determined by your numerical grade calculated above:

    Satisfactory (S) - 70% or higher

    Unsatisfactory (U) - 69% or lower

    Keep in mind that the class is S/U, and that a numerical score of a C- (70%) or higher would allow you to pass with an S grade.

    Grading Scale

    Web Development is a creative process, one that depends heavily on current technologies and how students decide to utilize them towards an end. Because of this, it is unnecessary to assign letter grades to students in this course. Rather, we will assign Satisfactory/Unsatisfactory grades. For a student to receive an Unsatisfactory grade, their numerical grade must be at or below 69% according to the numerical determination outlined above. Otherwise, they will receive a Satisfactory grade.

    Course Management

    For the following sections, the word "us" refers to the course staff.

    When in contact with us, contact with either of the two primary instructors (Sarah Young and Sophia Pham) is sufficient to qualify as contact with us.

    The contact information can be found above in the "Instructors" section.

    Academic Integrity

    Each student in this course is expected to abide by the Cornell University Code of Academic Integrity: http://cuinfo.cornell.edu/aic.cfm

    Under the provisions of the Code, anyone who gives or receives unauthorized assistance in the preparation of work at home or during tests in class will be subject to disciplinary action. A student's name on any piece of work is our assurance that they have neither given nor received any unauthorized help in its preparation. Students may assist each other on assignments by answering questions and explaining various concepts. However, one student should not allow another student to copy their work directly. All University policies with respect to cheating will be enforced. A student who is found to have cheated on an exam, or any other graded assignment, will receive a "Uโ€ in the course.

    SDS Accommodations

    Students with Disabilities: Your access in this course is important to us! Please request your accommodation letter early in the semester, or as soon as you become registered with Student Disability Services (SDS), so that we have adequate time to arrange your approved academic accommodations.

    Once SDS approves your accommodation letter, it will be emailed to both you and us. Please follow up with us to discuss the necessary logistics of your accommodations using the contact information provided in this syllabus. If you are approved for exam accommodations, please consult with us at least two weeks before the scheduled exam date to confirm the testing arrangements.

    If you experience any access barriers in this course, such as with printed content, graphics, online materials, or any communication barriers, reach out to us or SDS right away.

    If you need immediate accommodation, please speak with us after class or send an email message to us and SDS at sds_cu@cornell.edu.

    If you have, or think you may have, a disability, please contact Student Disability Services for a confidential discussion: sds_cu@cornell.edu or visit sds.cornell.edu to learn more.

    Mental Health and Well-being

    Your health and wellbeing are important to us.

    There are services and resources at Cornell designed specifically to bolster undergraduate, graduate, and professional student mental health and well-being. Remember, your mental health and emotional well-being are just as important as your physical health. If you or a friend are struggling emotionally or feeling stressed, fatigued, or burned out, there is a continuum of campus resources available to you: https://mentalhealth.cornell.edu/get-support/support-students.

    Help is also available any time day or night through Cornellโ€™s 24/7 phone consultation (607-255-5155). You can also reach out to me, your college student services office, your resident advisor, or Cornell Health for support.

    - + \ No newline at end of file diff --git a/docs/setup-editor/index.html b/docs/setup-editor/index.html index fcbf9b8de..b32b8d428 100644 --- a/docs/setup-editor/index.html +++ b/docs/setup-editor/index.html @@ -5,13 +5,13 @@ Setup your editor | Trends in Web Dev - +
    Version: 2024sp

    Install an Editor

    We recommend using VSCode, which is free for students. Install it from the website.

    Note: Course staff, instructors, and TAs will be using VSCode, so if you are using any other editors (such as WebStorm), you may need to do some extra work to get help from us.

    To install extensions in VS Code, navigate to the left-hand sidebar, and click the building blocks icon near the bottom. This should take you to the Extensions marketplace.

    ESLint

    Installing ESLint in VSCode will give you real-time linter feedback in any JavaScript code you write, allowing you to quickly pinpoint many problems and have readable, proper formatting.

    Once installed, add these lines to your VSCode Settings (refer to this link if you need help getting there or alternatively open the command palette in VSCode with CMD/CTRL + SHIFT + P and search settings.json):

      // Other settings ...
    "eslint.alwaysShowStatus": true,
    "editor.codeActionsOnSave": {
    "source.fixAll.eslint": true
    },
    "eslint.packageManager": "yarn"

    Prettier

    Prettier is a code formatter that will automatically format your code to be consistent with the rest of the class.

    Install it and it should automatically work!

    - + \ No newline at end of file diff --git a/docs/setup-environment/index.html b/docs/setup-environment/index.html index c2b88f80a..ff75428a4 100644 --- a/docs/setup-environment/index.html +++ b/docs/setup-environment/index.html @@ -5,13 +5,13 @@ Setup your development environment | Trends in Web Dev - +
    Version: 2024sp

    Setup your development environment

    Install Node.js

    Node is a JavaScript runtime that allows you to run JavaScript code outside of a browser! It powers all of web development today, and is critically necessary for this course.

    Go to this website and follow the instructions to install it on your workstation.

    For consistency, please use the latest Node LTS (currently, as of Fall 2023, this is Node 18).

    Install Additional Packages

    We will be using a few additional packages to help us with development.

    Deno

    Deno is a drop-in replacement for Node that's faster, more secure, and critically -- supports TypeScript out of the box. We use it for its "REPL" (Read-Eval-Print-Loop) functionality, which allows us to run TypeScript code interactively during demos in class. Install it if you want to follow along with the demos.

    Instructions

    Pnpm

    Pnpm is a package manager that is faster and more efficient than the default npm package manager.

    npm install -g pnpm

    Making a React Project

    To make sure you've got everything set up correctly, cd into a directory of your choice and run:

    pnpm dlx degit cornell-dti/trends-mono/frontend-starter your-project-name

    This will create a new directory called your-project-name with a React project inside. cd into it and run pnpm install to install all the dependencies.

    Take a look around and edit the code if you'd like.

    When you're ready, run pnpm dev to start the development server. If everything works as it should, you should be able to navigate to localhost:5173 and see a React app running!

    You can also use that command to create a new 'blank' React project at any time. Bookmark this page and come back to it!

    - + \ No newline at end of file diff --git a/docs/unit1/index.html b/docs/unit1/index.html index 9e1d956fd..5d6637d3d 100644 --- a/docs/unit1/index.html +++ b/docs/unit1/index.html @@ -5,7 +5,7 @@ Frontend / Unit 1 | Trends in Web Dev - + @@ -49,7 +49,7 @@ gets evaluated to 0, {} gets evaluated to [object Object], and they both get coerced to strings. Then, adding a list to a string simply adds the contents of the list to the string, so 1 gets appended to the end.

    Example 3

    const zero = +[]; // + coerce [] into 0
    const one = +!![]; // ! coerce [] into false, got inverted, then coerce to 1
    const two = +!![] + +!![]; // 2 = 1 + 1

    const fib2 = (__) =>
    __ === zero || __ === one ? __ : fib2(__ - one) + fib2(__ - two);

    This is the Fibonacci sequence implemented using type coercion.

    - + \ No newline at end of file diff --git a/index.html b/index.html index 15c0a728d..b5a51555a 100644 --- a/index.html +++ b/index.html @@ -5,13 +5,13 @@ Trends in Web Dev | Trends in Web Dev - +
    Trends in Web Development Mascot

    Trends in Web Dev

    Build and deploy modern full-stack web applications using best practices in today's most used tech stacks.

    - + \ No newline at end of file