├── .vscode └── launch.json ├── homework └── hw1 │ └── homework-1.md ├── readme.md ├── week1 ├── exercise │ ├── quick-sort.js │ ├── radix-sort.js │ ├── readme.md │ └── validate.js └── lecture │ ├── hoisting-parsing.js │ ├── iife.js │ └── index.js ├── week10 └── lecture │ ├── generator.js │ ├── genexample.py │ └── proxy.js ├── week11 ├── lecture │ ├── .gitignore │ ├── index.js │ ├── package-lock.json │ ├── package.json │ ├── public │ │ ├── main.js │ │ └── test.js │ └── views │ │ ├── about.ejs │ │ ├── main.ejs │ │ └── shared │ │ ├── footer.ejs │ │ └── header.ejs └── lecture2 │ ├── .gitignore │ ├── index.js │ ├── package-lock.json │ ├── package.json │ ├── public │ ├── _main.js │ ├── app-anchor.js │ ├── edit-user.js │ ├── index.html │ ├── main.js │ ├── router.js │ └── user-list.js │ └── user-router.js ├── week12 └── exercise │ └── readme.md ├── week13 └── lecture │ ├── .gitignore │ ├── client │ ├── README.md │ ├── dist │ │ ├── 855c92f49fddc8ca5d72.png │ │ ├── index.html │ │ ├── main.css │ │ ├── main.js │ │ └── main.js.LICENSE.txt │ ├── index.html │ ├── loaders │ │ └── environment-replacer.js │ ├── package-lock.json │ ├── package.json │ ├── src │ │ ├── app-anchor.js │ │ ├── assets │ │ │ └── webpack.png │ │ ├── edit-user.js │ │ ├── environments │ │ │ ├── environment.js │ │ │ └── environment.prod.js │ │ ├── main.js │ │ ├── router.js │ │ └── user-list.js │ └── webpack.config.js │ └── server │ ├── index.js │ ├── package-lock.json │ ├── package.json │ └── user-router.js ├── week14 └── lecture │ ├── .gitignore │ ├── client │ ├── .vscode │ │ └── settings.json │ ├── README.md │ ├── dist │ │ ├── 855c92f49fddc8ca5d72.png │ │ ├── index.html │ │ ├── main.css │ │ ├── main.js │ │ └── main.js.LICENSE.txt │ ├── index.html │ ├── jsconfig.json │ ├── loaders │ │ └── environment-replacer.js │ ├── package-lock.json │ ├── package.json │ ├── src │ │ ├── app-anchor.js │ │ ├── assets │ │ │ └── webpack.png │ │ ├── decorators │ │ │ ├── component.js │ │ │ └── property.js │ │ ├── edit-user.js │ │ ├── environments │ │ │ ├── environment.js │ │ │ └── environment.prod.js │ │ ├── main.js │ │ ├── router.js │ │ ├── test.js │ │ └── user-list.js │ └── webpack.config.js │ └── server │ ├── index.js │ ├── package-lock.json │ ├── package.json │ └── user-router.js ├── week15 └── index.html ├── week16 └── lecture │ ├── client │ ├── .gitignore │ ├── .vscode │ │ └── settings.json │ ├── README.md │ ├── dist │ │ ├── 855c92f49fddc8ca5d72.png │ │ ├── index.html │ │ ├── main.css │ │ ├── main.js │ │ └── main.js.LICENSE.txt │ ├── index.html │ ├── jsconfig.json │ ├── loaders │ │ └── environment-replacer.js │ ├── package-lock.json │ ├── package.json │ ├── src │ │ ├── app-anchor.js │ │ ├── assets │ │ │ └── webpack.png │ │ ├── decorators │ │ │ ├── component.js │ │ │ └── property.js │ │ ├── edit-user.js │ │ ├── environments │ │ │ ├── environment.js │ │ │ └── environment.prod.js │ │ ├── home.js │ │ ├── login.js │ │ ├── logout.js │ │ ├── main.js │ │ ├── router.js │ │ ├── test.js │ │ └── user-list.js │ └── webpack.config.js │ └── server │ ├── .gitignore │ ├── constants.js │ ├── index.js │ ├── jwt.js │ ├── package-lock.json │ ├── package.json │ ├── user-router.js │ └── worker.js ├── week2 ├── exercise │ ├── compose.js │ ├── curry.js │ ├── memoize.js │ └── readme.md └── lecture │ └── index.js ├── week3 ├── exercise │ └── readme.md └── lecture │ ├── example.cpp │ └── index.js ├── week4 ├── exercise │ └── README.md └── lecture │ └── intro-to-es6.js ├── week5 ├── exercise │ ├── readme.md │ ├── task-1.js │ └── task-2.js └── lecture │ ├── fs.js │ ├── index.js │ ├── server.js │ ├── test.js │ └── utils.js ├── week6 └── lecture │ ├── custom-copy-cmd-streams.js │ ├── event-emitter.js │ ├── streams.js │ ├── test (copy).txt │ └── test.txt ├── week7 ├── exercise │ ├── filter.ts.stream.js │ └── input.txt ├── lecture │ ├── desctructuring&spread-operator.js │ ├── index.js │ └── sample.js └── sample-solution-for-exam-1.js ├── week8 ├── exercise │ └── readme.md └── lecture │ ├── .gitignore │ ├── index.js │ ├── my-express-server.js │ ├── my-express.js │ ├── package-lock.json │ ├── package.json │ └── server.js └── week9 ├── example.json └── lecture ├── example.json ├── myprom.js ├── nevronka.txt ├── promise.js └── promises.png /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Launch Program", 11 | "skipFiles": [ 12 | "/**" 13 | ], 14 | "program": "${file}" 15 | } 16 | ] 17 | } -------------------------------------------------------------------------------- /homework/hw1/homework-1.md: -------------------------------------------------------------------------------- 1 | # Домашно 1 2 | 3 | ### Задание 4 | Създайте сървър, който по подаден URL извлича съдържанието на дадената страница, филтрира съдържанието ѝ и го връща. 5 | Филтрирането да се случва на база допълнителен файл с регулярни изрази. 6 | Това да става посредством крайната точка `sanitize` - `localhost:8000/sanitize?url=..."` 7 | 8 | Изразите и думите за филтриране да се държат държат локално и да може да се добавят и махат такива посредством крайната точка `condition`: 9 | - **POST** `localhost:8000/condition` за добавяне 10 | - **DELETE** `localhost:8000/condition/:id` за добавяне 11 | 12 | ### Предаване 13 | За предаване е създадено [задание в Moodle](https://learn.fmi.uni-sofia.bg/mod/assign/view.php?id=262895&forceview=1). -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Advanced JavaScript @ FMI / 2022-2023 / 2 | 3 | ## Lecturers 4 | 5 | * [Gheorgi Penkov](https://www.linkedin.com/in/penkov/) 6 | * [Iliya Idakiev](https://www.linkedin.com/in/idakiev/) 7 | * [Aleksander Kostov](https://www.linkedin.com/in/aleksander-kostov-800509105/) 8 | 9 | 16 | 17 | ## Poster from 2018/2019 18 | 19 | advanced-javascript-at-faculty-of-mathematics-and-informatics-sofia 20 | -------------------------------------------------------------------------------- /week1/exercise/quick-sort.js: -------------------------------------------------------------------------------- 1 | var quickSort = function (arr) { 2 | var partition = function (lIdx, rIdx) { 3 | var pivot = arr[lIdx]; 4 | 5 | var tmpL = lIdx; 6 | var tmpR = rIdx; 7 | 8 | while (true) { 9 | // Move L 10 | while (arr[tmpL] < pivot) { tmpL += 1; } 11 | // Move R 12 | while (arr[tmpR] > pivot) { tmpR -= 1; } 13 | 14 | 15 | // Return if all is well 16 | if (tmpL >= tmpR) { return tmpR; } 17 | 18 | // swap if not 19 | [arr[tmpL], arr[tmpR]] = [arr[tmpR], arr[tmpL]]; 20 | } 21 | } 22 | 23 | var sort = function (lIdx, rIdx) { 24 | if (lIdx >= rIdx) { return; } 25 | var pivotIdx = partition(lIdx, rIdx); 26 | sort(lIdx, pivotIdx); // sort left 27 | sort(pivotIdx + 1, rIdx); // sort right 28 | } 29 | 30 | sort(0, arr.length - 1); 31 | return arr; 32 | } 33 | 34 | var res = quickSort([4, 6, 1, 5, 2, 3, 8, 7, 9]) 35 | console.log(res); -------------------------------------------------------------------------------- /week1/exercise/radix-sort.js: -------------------------------------------------------------------------------- 1 | function radix(arr) { 2 | var change = false; 3 | var mask = 1; 4 | 5 | do { 6 | mask = mask * 10; 7 | change = false; 8 | 9 | var bucket = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; 10 | var res = new Array(arr.length); 11 | 12 | // counting 13 | arr.forEach(el => { 14 | if ((mask / 10) < el) { 15 | var d = Math.floor((el % mask) / (mask / 10)); 16 | bucket[d]++; 17 | change = true; 18 | } else { 19 | bucket[0]++; 20 | } 21 | }) 22 | 23 | if (change === false) { break; } 24 | 25 | // prepare indices 26 | bucket.forEach((e, i) => i ? bucket[i] = bucket[i - 1] + bucket[i] : bucket[i]) 27 | 28 | // reorder 29 | arr.reverse().forEach(el => { 30 | var d = ((mask / 10) < el) ? Math.floor(el % mask / (mask / 10)) : 0; 31 | bucket[d]--; 32 | res[bucket[d]] = el; 33 | }) 34 | 35 | // assert length is correct ! 36 | if (arr.length !== res.length) { throw "bucket length differs from res length"; } 37 | 38 | arr = res; // this is not a deep copy, but a reference assignment! 39 | } while (change) 40 | 41 | return arr; 42 | } 43 | var res = radix([412, 64, 331, 56, 83, 101, 11, 2]); 44 | console.log(res); -------------------------------------------------------------------------------- /week1/exercise/readme.md: -------------------------------------------------------------------------------- 1 | # Упражнение 1 2 | ## 12.10.22 3 | 4 | 5 | 1. Изберете едно от долните: 6 | * Напишете реализация на [Quick Sort](https://en.wikipedia.org/wiki/Quicksort) 7 | 8 | * Напишете реализация на [Radix Sort](https://en.wikipedia.org/wiki/Radix_sort) 9 | 10 | Бонус: 11 | 12 | 2. Напишете алгоритъм, който да помогне на любимият ви учител да оцени домашните по математика. Алгоритъмът трябва по дадени равенства (състоящи се от математически изрази, разделени със знаци за равенство) да даде оценка на коректността на равенството. 13 | 14 | Примери: 15 | 16 | * "3 + 2 = 5" => "1 / 1" 17 | 18 | Има само една стъпка и тя е изпълнена правилно. 19 | * "7 - 3 * 2 + 1 = 4 * 2 + 1 = 8 + 1 = 9" => "2 / 3" 20 | 21 | Първата стъпка е грешна, но следващите 2 са, поради тази причина пишен 2 от 3 точки. 22 | 23 | Допускаме, че операндите са винаги с по 1 цифра. 24 | -------------------------------------------------------------------------------- /week1/exercise/validate.js: -------------------------------------------------------------------------------- 1 | var validate = problem => { 2 | if (!problem || typeof problem !== 'string' || !problem.length) { return '0/0'; } 3 | var parts = problem.split('=').map(a => a.trim()); 4 | 5 | var executeOperation = ({ op, idx }, partArr) => { 6 | const lOper = +partArr[idx - 1]; 7 | const rOper = +partArr[idx + 1]; 8 | let res = null 9 | switch (op) { 10 | case '+': res = lOper + rOper; break; 11 | case '-': res = lOper - rOper; break; 12 | case '*': res = lOper * rOper; break; 13 | case '/': res = lOper / rOper; break; 14 | } 15 | return res; 16 | } 17 | var operators = ['+', '-', '*', '/']; 18 | var evaluatePart = (part) => { 19 | var partParts = part.split(' '); 20 | var operations = partParts.reduce((acc, curr, idx) => { 21 | if (!operators.includes(curr)) { return acc; } 22 | return [...acc, { op: curr, idx }]; 23 | }, []); 24 | 25 | if (!operations.length) { return part; } 26 | const operationToExecute = operations.find(({ op }) => ['*', '/'].includes(op)) || operations[0]; 27 | const operationRes = executeOperation(operationToExecute, partParts); 28 | const trimmedPart = part.split('').filter(pp => pp.trim()); 29 | const partEvaluationRes = [...trimmedPart.slice(0, operationToExecute.idx - 1), operationRes, ...trimmedPart.slice(operationToExecute.idx + 2)].join(' '); 30 | return partEvaluationRes; 31 | } 32 | 33 | const { good, bad } = parts.reduce((acc, cPart, idx) => { 34 | if (idx === parts.length - 1) { return acc; } 35 | const currPartRes = evaluatePart(cPart); 36 | const opIsGood = currPartRes === parts[idx + 1]; 37 | return { good: opIsGood ? acc.good + 1 : acc.good, bad: opIsGood ? acc.bad : acc.bad + 1 }; 38 | }, { good: 0, bad: 0 }) 39 | return `${good}/${good + bad}`; 40 | } 41 | 42 | console.log(validate('3 + 2 = 5')); 43 | console.log(validate('7 - 3 * 2 + 1 = 4 * 2 + 1 = 8 + 1 = 9')); -------------------------------------------------------------------------------- /week1/lecture/hoisting-parsing.js: -------------------------------------------------------------------------------- 1 | // console.log(test); 2 | // console.log(best); 3 | // console.log(1); 4 | // console.log(2); 5 | // console.log(3); 6 | 7 | // var test = 123; 8 | 9 | // function best() { 10 | // //... 11 | // //... 12 | // //... 13 | // //... 14 | 15 | // function rest() { 16 | 17 | // } 18 | 19 | // var best1 = 123; 20 | // } 21 | 22 | // console.log(best); 23 | 24 | 25 | 26 | 27 | function test1() { 28 | 29 | } 30 | var test2; 31 | console.log(test1()); 32 | try { 33 | console.log(test2()); // > undefined is not a function 34 | } catch (e) { 35 | console.error(e); 36 | } 37 | 38 | test2 = function () { 39 | console.log(123); 40 | }; 41 | test2(); 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /week1/lecture/iife.js: -------------------------------------------------------------------------------- 1 | var myLib = (function (global) { 2 | var one; 3 | var two; 4 | var three 5 | 6 | function myFunc() { 7 | 8 | } 9 | 10 | global.myFunc = myFunc; 11 | 12 | })(global); 13 | 14 | myFunc(); -------------------------------------------------------------------------------- /week1/lecture/index.js: -------------------------------------------------------------------------------- 1 | // // // Primitive types: 2 | 3 | // undefined // type undefined 4 | // null // type null ! 5 | // false | true // boolean 6 | // 'stata' | "dsdasdas" // string 7 | // 1000 // number 8 | // Symbol('dsada') // Symbol (ES6) 9 | // { } // Object 10 | 11 | // // typeof undefined // > "undefined" 12 | // // typeof null // > "object" 13 | 14 | // var test = 1000; 15 | // var obj = { 16 | // prop1: 1000, 17 | // prop2: { 18 | // prop1: 200 19 | // }, 20 | // prop3: [] 21 | // }; 22 | 23 | // function doSomething() { 24 | 25 | // } 26 | // doSomething(); 27 | // var value = 1213; 28 | // var myFunc = function test() { 29 | // var test = 13; 30 | // console.log(value); 31 | // console.log(123); 32 | // console.log(rest1); 33 | 34 | // function rest() { 35 | // var rest1 = 123; 36 | // console.log(test); 37 | // } 38 | 39 | // rest(); 40 | // }; 41 | 42 | // console.log(myFunc.name); 43 | // myFunc(); 44 | // test(); 45 | 46 | // // console.log(test == '1000'); 47 | 48 | // // console.log((1000).toFixed(2)); 49 | 50 | // var arr = [1, [{}], 3, 4]; 51 | // arr[10] = 10; 52 | // console.log(arr.length); 53 | 54 | 55 | 56 | 57 | function createPerson(name, age) { 58 | var nameArray = name.split(' '); 59 | function getAge() { 60 | return age; 61 | } 62 | return { 63 | firstName: nameArray[0], 64 | lastName: nameArray[1], 65 | getPersonAge: getAge, 66 | age: age 67 | }; 68 | } 69 | 70 | var ivan = createPerson('Ivan Ivanov', 20); 71 | ivan.age = 30; 72 | console.log(ivan.getPersonAge()); 73 | 74 | // for (var i = 0; i < 10; i++) { 75 | // switch (i % 2 == 0) { 76 | // case true: { 77 | // cons 78 | // break; 79 | // } 80 | // case false: { 81 | // break; 82 | // } 83 | // } 84 | // if (i % 2 == 0) { 85 | // console.log('even'); 86 | // continue; 87 | // } else if (true) { 88 | // console.log('odd'); 89 | // } else { 90 | 91 | // } 92 | // } 93 | 94 | // do { 95 | 96 | // } while (true); 97 | 98 | // while (true) { 99 | 100 | // } 101 | 102 | // for (var prop in ivan) { 103 | // console.log(ivan[prop]); 104 | // } 105 | 106 | 107 | function test(param) { 108 | console.log('1'); 109 | return function test() { 110 | console.log('2', param); 111 | return function test() { 112 | console.log('3', param); 113 | }; 114 | }; 115 | } 116 | 117 | var newTest = test(1); 118 | var newNewTest = newTest(); 119 | var newNewTestResult = newNewTest(); 120 | newNewTestResult === test(1)()(); 121 | 122 | var obj = {}; 123 | Object.defineProperty(obj, 'prop1', { 124 | value: 1, 125 | writable: false, 126 | enumerable: false, 127 | configurable: false, 128 | // set: function() { 129 | 130 | // }, 131 | // get: function() { 132 | 133 | // } 134 | }); -------------------------------------------------------------------------------- /week10/lecture/generator.js: -------------------------------------------------------------------------------- 1 | 2 | // Generators in JavaScript are special functions that can be paused and resumed, 3 | // allowing them to yield multiple values over time. 4 | // Generators are well-suited to a variety of tasks, such as: 5 | 6 | // Implementing lazy evaluation of expensive computations. 7 | // Simplifying asynchronous programming by allowing you to write 8 | // code that looks and behaves like synchronous code. 9 | // Allowing you to iterate over data structures or sequences 10 | // of values that may be too large to fit in memory all at once. 11 | // Providing a way to abstract away the details of iterating 12 | // over a data structure or sequence, making your code more readable and maintainable.// 13 | 14 | // classical Fibonnaci 15 | 16 | function fib(cnt) { 17 | let current = 0 18 | let next = 1 19 | while (cnt--) { 20 | [current, next] = [next, current + next] 21 | } 22 | return current 23 | } 24 | console.log(fib(10)) 25 | 26 | /// now lets try with generator 27 | 28 | function* fibgen() { 29 | let current = 0 30 | let next = 1 31 | while (current < Math.pow(10, 6)) { 32 | yield current; 33 | [current, next] = [next, current + next] 34 | } 35 | } 36 | 37 | let gen = fibgen() 38 | let cnt = 10; 39 | let res 40 | while (!(res = gen.next()).done) { 41 | console.log(res.value) 42 | } 43 | 44 | // https://oeis.org/A000045 45 | // 46 | // Fibonacci numbers: F(n) = F(n-1) + F(n-2) with F(0) = 0 and F(1) = 1. 47 | // 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 48 | // 4181, 6765, 10946, 17711, 28657, 46368, 75025, 121393, 196418, 317811, ... 49 | 50 | // lets see how we return back to the generator 51 | 52 | function* mgen(c) { 53 | let res = yield c * 2; 54 | res = yield res * 2; 55 | res = yield res * 2; 56 | } 57 | 58 | let genobj = mgen(25); 59 | console.log(genobj.next(100).value) 60 | console.log(genobj.next(123).value) 61 | console.log(genobj.next(235).value) 62 | 63 | // now this put to practical means 64 | 65 | function* loadData() { 66 | // Load the data asynchronously and yield the result 67 | const res1 = yield fetch('https://jsonplaceholder.typicode.com/todos/1') 68 | .then(response => response.json()) 69 | 70 | console.log(res1) 71 | 72 | const res2 = yield fetch('https://jsonplaceholder.typicode.com/comments/2') 73 | .then(response => response.json()) 74 | 75 | console.log(res2) 76 | 77 | // Return the result after the asynchronous operation is complete 78 | return true 79 | } 80 | 81 | function run(generator, val) { 82 | if (val === undefined) { 83 | val = generator.next().value 84 | } 85 | if (val instanceof Promise) { 86 | val.then(result => { // Handle the asynchronous result 87 | // Resume the generator 88 | // passing the result to the next yield statement 89 | newval = generator.next(result) 90 | if (newval.done !== true) { 91 | return run(generator, newval.value); // tail call 92 | } 93 | }).catch(err => { 94 | console.error('some error running the generator: %s', err) 95 | }) 96 | } 97 | } 98 | 99 | run(loadData()) 100 | 101 | //////////////////////////////////////////////////////////////////////////// 102 | 103 | function* iterateTree(node) { 104 | if (node.left) yield* iterateTree(node.left) 105 | yield node.value 106 | if (node.right) yield* iterateTree(node.right) 107 | } 108 | 109 | const tree = { 110 | value: 'A', 111 | left: { 112 | value: 'B', 113 | left: { 114 | value: 'C', 115 | }, 116 | right: { 117 | value: 'D', 118 | }, 119 | }, 120 | right: { 121 | value: 'E', 122 | left: { 123 | value: 'F', 124 | }, 125 | right: { 126 | value: 'G', 127 | }, 128 | }, 129 | } 130 | 131 | // Create a generator object by calling the generator function 132 | const generator = iterateTree(tree) 133 | 134 | // Iterate over the generator and print its values 135 | for (const value of generator) { 136 | console.log(value) 137 | } 138 | 139 | -------------------------------------------------------------------------------- /week10/lecture/genexample.py: -------------------------------------------------------------------------------- 1 | def fibonacci(): 2 | current, next = 0, 1 3 | while True: 4 | yield current 5 | current, next = next, current + next 6 | 7 | # Create a generator object by calling the fibonacci() function 8 | sequence = fibonacci() 9 | 10 | # Iterate over the first 10 numbers in the fibonacci sequence 11 | for _ in range(10): 12 | print(next(sequence)) -------------------------------------------------------------------------------- /week10/lecture/proxy.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const ERROR_MSG1 = 'une propriété bizarre %s a été passée'; 4 | 5 | const obj = { 6 | foo: 'Hello, world!', 7 | // baba: 'a missing BABA property', 8 | } 9 | 10 | // Create a proxy for the object 11 | // that intercepts all calls 12 | // to some properties 13 | const proxy = new Proxy(obj, { 14 | get(target, prop) { 15 | console.log(target[prop]) 16 | if (prop === 'foo') { 17 | // Modify the value of the 'foo' property before returning it 18 | return target[prop].toUpperCase() 19 | } 20 | // intercept missing properties 21 | if (target.hasOwnProperty(prop)) { 22 | return target[prop] 23 | } else { 24 | console.error() 25 | console.error(`some weird property ${prop} was passed`) 26 | console.error(ERROR_MSG1, prop) 27 | debugger; 28 | } 29 | }, 30 | set(target, prop, value) { 31 | if (prop === 'numval') { 32 | // introduce runtime validation for property values 33 | if (value > 0) { 34 | // target.prop = value 35 | // return true 36 | return Reflect.set(...arguments); 37 | } else { 38 | console.error('refusing to set negative value') 39 | return true 40 | } 41 | } 42 | } 43 | }) 44 | 45 | proxy.numval = 400 46 | proxy.numval = -1 47 | // Get the value of the 'foo' property from the proxy 48 | console.log(proxy.foo) // "HELLO, WORLD!" 49 | console.log(proxy.baba) // "BABA yeah!" 50 | 51 | 52 | -------------------------------------------------------------------------------- /week11/lecture/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /week11/lecture/index.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const users = ['Gosho', 'Pesho', 'Ivan']; 3 | 4 | const app = express(); 5 | app.use(express.json()); 6 | app.use(express.urlencoded()); 7 | app.use(express.static('public')); 8 | app.set('view engine', 'ejs'); 9 | 10 | app.post('/user', (req, res) => { 11 | const isJSONRequest = req.headers['content-type'] === 'application/json'; 12 | const index = req.body.index; 13 | const newUserName = req.body.username; 14 | if (index) { 15 | users[index] = newUserName; 16 | } else { 17 | users.push(newUserName); 18 | } 19 | if (isJSONRequest) { 20 | res.send(users); 21 | } else { 22 | res.redirect('/'); 23 | } 24 | }); 25 | 26 | app.get('/about', (req, res) => { 27 | res.render('about'); 28 | }); 29 | 30 | app.get('/', (req, res) => { 31 | res.render('main', { message: 'TESTING...', users }); 32 | }); 33 | 34 | app.listen(8080, () => { 35 | console.log('Server is listening on :8080'); 36 | }); -------------------------------------------------------------------------------- /week11/lecture/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lecture", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "node ." 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "ejs": "^3.1.8", 15 | "express": "^4.18.2" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /week11/lecture/public/main.js: -------------------------------------------------------------------------------- 1 | import { logger } from './test.js'; 2 | 3 | // logger('23123213'); 4 | 5 | const userList = document.getElementById('user-list'); 6 | const userIndexInput = document.getElementById('user-index-input'); 7 | const userNameInput = document.getElementById('user-name-input'); 8 | const addUserButton = document.getElementById('add-user-button'); 9 | 10 | const userIndexInput2 = document.getElementById('user-index-input-2'); 11 | const userNameInput2 = document.getElementById('user-name-input-2'); 12 | const addUserButton2 = document.getElementById('add-user-button-2'); 13 | const userForm = document.getElementById('user-form'); 14 | 15 | userForm.addEventListener('submit', function (e) { 16 | e.preventDefault(); 17 | 18 | // e.stopPropagation(); 19 | // e.stopImmediatePropagation(); 20 | 21 | const username = userNameInput2.value; 22 | if (!username) { 23 | alert('Name is required'); 24 | return; 25 | } 26 | userNameInput2.value = ''; 27 | const index = userIndexInput2.value || undefined; 28 | 29 | fetch('/user', { 30 | method: 'POST', 31 | headers: { 32 | 'content-type': 'application/json' 33 | }, 34 | body: JSON.stringify({ index, username }) 35 | }).then(res => res.ok ? res.json() : Promise.reject(new Error(res.statusText))) 36 | .then(users => { 37 | userList.innerHTML = ''; 38 | users.forEach(function (user, index) { 39 | const li = document.createElement('li'); 40 | li.setAttribute('data-index', index); 41 | li.classList.add('user-list-item'); 42 | li.innerHTML = user; 43 | userList.appendChild(li); 44 | }); 45 | }) 46 | .catch(err => { 47 | console.error(err); 48 | }); 49 | 50 | }); 51 | 52 | 53 | function userListClickHandler(e) { 54 | const target = e.target; 55 | if (target.tagName !== 'LI') { return; } 56 | const index = target.getAttribute('data-index'); 57 | const text = target.textContent.trim(); 58 | if (!index || !text) { 59 | alert('Missing name or index'); 60 | return; 61 | } 62 | userIndexInput.value = index; 63 | userNameInput.value = text; 64 | addUserButton.textContent = 'Edit user'; 65 | } 66 | 67 | userList.addEventListener('click', userListClickHandler); 68 | // userList.removeEventListener('click', userListClickHandler); 69 | 70 | // addUserButton.addEventListener('click', () => { 71 | // const userName = userNameInput.value; 72 | // if (!userName.length) { 73 | // alert('User name is required!'); 74 | // return; 75 | // } 76 | // userNameInput.value = ''; 77 | 78 | // const newLi = document.createElement('li'); 79 | // const textNode = document.createTextNode(userName); 80 | // newLi.appendChild(textNode); 81 | // userList.appendChild(newLi); 82 | // //newLi.innerHTML = userName; 83 | // }); 84 | -------------------------------------------------------------------------------- /week11/lecture/public/test.js: -------------------------------------------------------------------------------- 1 | export function logger(message) { 2 | console.log(message); 3 | } 4 | -------------------------------------------------------------------------------- /week11/lecture/views/about.ejs: -------------------------------------------------------------------------------- 1 | <%- include('shared/header'); -%> 2 |

3 | ABOUT 4 |

5 | <%- include('shared/footer'); -%> -------------------------------------------------------------------------------- /week11/lecture/views/main.ejs: -------------------------------------------------------------------------------- 1 | <%- include('shared/header'); -%> 2 |

3 | <%= message %> 4 |

5 | 6 |
7 |
8 |
    9 | <% users.forEach(function (user, index){ %> 10 |
  • 11 | <%= user %> 12 |
  • 13 | <% }); %> 14 |
15 | 16 |
17 | 18 | 19 | 20 |
21 | 22 |
23 | 24 | 25 | 26 |
27 |
28 |
29 | <%- include('shared/footer'); -%> -------------------------------------------------------------------------------- /week11/lecture/views/shared/footer.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /week11/lecture/views/shared/header.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Document 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /week11/lecture2/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /week11/lecture2/index.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const path = require('path'); 3 | const userRouter = require('./user-router.js'); 4 | 5 | 6 | const app = express(); 7 | app.use(express.json()); 8 | app.use(express.static('public')); 9 | app.use('/users', userRouter); 10 | 11 | app.get('*', (req, res) => { 12 | res.sendFile(path.resolve('./public/index.html')); 13 | }); 14 | 15 | app.listen(8080, () => { 16 | console.log('Server is listening on :8080'); 17 | }); -------------------------------------------------------------------------------- /week11/lecture2/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lecture", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "node ." 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "ejs": "^3.1.8", 15 | "express": "^4.18.2" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /week11/lecture2/public/_main.js: -------------------------------------------------------------------------------- 1 | const appComponentTemplate = document.getElementById('app-component-template'); 2 | 3 | class AppComponent extends HTMLElement { 4 | 5 | static observedAttributes = ['styles']; 6 | 7 | get date() { 8 | return this._date; 9 | } 10 | 11 | set date(newValue) { 12 | this._date = newValue; 13 | this._content.innerHTML = newValue; 14 | } 15 | 16 | constructor() { 17 | super(); 18 | const showRoot = this.attachShadow({ mode: 'closed' }) 19 | 20 | 21 | // const styleContent = this.getAttribute('styles') 22 | const style = document.createElement('style'); 23 | showRoot.appendChild(style); 24 | showRoot.appendChild(appComponentTemplate.content.cloneNode(true)); 25 | 26 | this._content = showRoot.querySelector('#content'); 27 | 28 | this.setComponentStyle = function (innerHTML) { 29 | style.innerHTML = innerHTML; 30 | // let currentStyle = showRoot.querySelector('#cmp-style'); 31 | // if (currentStyle) { showRoot.removeChild(currentStyle); } 32 | // currentStyle = document.createElement('style'); 33 | // currentStyle.id = 'cmp-style'; 34 | // currentStyle.innerHTML = innerHTML; 35 | // showRoot.appendChild(currentStyle); 36 | } 37 | } 38 | 39 | connectedCallback() { 40 | this.date = new Date(); 41 | this._timerId = setInterval(() => { 42 | this.date = new Date(); 43 | }, 1000); 44 | 45 | console.log('connectedCallback'); 46 | } 47 | 48 | disconnectedCallback() { 49 | clearInterval(this._timerId); 50 | console.log('disconnectedCallback'); 51 | } 52 | 53 | attributeChangedCallback(attrName, oldValue, newValue) { 54 | switch (attrName) { 55 | case 'styles': { 56 | this.setComponentStyle(newValue); 57 | break; 58 | } 59 | } 60 | } 61 | } 62 | 63 | customElements.define('app-component', AppComponent); 64 | 65 | const appCmp = new AppComponent(); 66 | 67 | 68 | // setTimeout(() => { 69 | // document.body.appendChild(appCmp); 70 | // setTimeout(() => { 71 | // document.body.removeChild(appCmp); 72 | // setTimeout(() => { 73 | // document.body.appendChild(appCmp); 74 | // }, 5000); 75 | 76 | // }, 5000); 77 | // }, 4000); -------------------------------------------------------------------------------- /week11/lecture2/public/app-anchor.js: -------------------------------------------------------------------------------- 1 | export class AppAnchor extends HTMLAnchorElement { 2 | 3 | constructor() { 4 | super(); 5 | } 6 | 7 | clickHandler(e) { 8 | e.preventDefault(); 9 | this.dispatchEvent(new CustomEvent('app-render', { 10 | detail: this.href.replace(window.location.origin, ''), 11 | bubbles: true 12 | })); 13 | } 14 | 15 | connectedCallback() { 16 | this.addEventListener('click', this.clickHandler); 17 | } 18 | 19 | disconnectedCallback() { 20 | this.removeEventListener('click', this.clickHandler); 21 | } 22 | 23 | } 24 | 25 | customElements.define('app-anchor', AppAnchor, { extends: 'a' }); -------------------------------------------------------------------------------- /week11/lecture2/public/edit-user.js: -------------------------------------------------------------------------------- 1 | export class EditUser extends HTMLElement { 2 | #_shadowRoot = null; 3 | 4 | constructor() { 5 | super(); 6 | this.#_shadowRoot = this.attachShadow({ mode: 'closed' }); 7 | } 8 | } 9 | 10 | customElements.define('app-edit-user', EditUser); -------------------------------------------------------------------------------- /week11/lecture2/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Document 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /week11/lecture2/public/main.js: -------------------------------------------------------------------------------- 1 | import './app-anchor.js'; 2 | import { Router } from './router.js'; 3 | 4 | function createMainTempalte() { 5 | const templateString = ` 6 | 10 | AAAAAAAAAAAAAAA 11 | `; 12 | 13 | const templateElement = document.createElement('template'); 14 | templateElement.innerHTML = templateString; 15 | return templateElement; 16 | } 17 | 18 | const template = createMainTempalte(); 19 | 20 | export class AppComponent extends HTMLElement { 21 | #_shadowRoot = null; 22 | 23 | constructor() { 24 | super(); 25 | this.#_shadowRoot = this.attachShadow({ mode: 'closed' }); 26 | const router = new Router(); 27 | this.#_shadowRoot.appendChild(template.content.cloneNode(true)); 28 | this.#_shadowRoot.appendChild(router); 29 | 30 | const nav = this.#_shadowRoot.querySelector('#nav'); 31 | nav.addEventListener('app-render', (e) => { 32 | router.render(e.detail); 33 | }); 34 | } 35 | } 36 | 37 | customElements.define('app-root', AppComponent); 38 | 39 | 40 | function createSlotTempalte() { 41 | const templateString = ` 42 | 43 | `; 44 | 45 | const templateElement = document.createElement('template'); 46 | templateElement.innerHTML = templateString; 47 | return templateElement; 48 | } 49 | 50 | const slotTemplate = createSlotTempalte(); 51 | export class SlotComponent extends HTMLElement { 52 | #_shadowRoot = null; 53 | constructor() { 54 | super(); 55 | this.#_shadowRoot = this.attachShadow({ mode: 'closed' }); 56 | this.#_shadowRoot.appendChild(slotTemplate.content.cloneNode(true)); 57 | } 58 | } 59 | 60 | customElements.define('app-slot', SlotComponent); 61 | 62 | const app = new AppComponent(); 63 | document.body.appendChild(app); 64 | -------------------------------------------------------------------------------- /week11/lecture2/public/router.js: -------------------------------------------------------------------------------- 1 | import { UserList } from './user-list.js'; 2 | import { EditUser } from './edit-user.js'; 3 | 4 | export class Router extends HTMLElement { 5 | #_shadowRoot = null; 6 | #currentPath = null; 7 | 8 | config = { 9 | '/edit/:id': EditUser, 10 | '/': UserList, 11 | }; 12 | 13 | constructor() { 14 | super(); 15 | this.#_shadowRoot = this.attachShadow({ mode: 'closed' }); 16 | } 17 | 18 | render(path, skipStatePush = false) { 19 | let ctor = null; 20 | for (const [key, value] of Object.entries(this.config)) { 21 | const keyRe = exports.pathToRegexp(key); 22 | if (!keyRe.test(path)) { continue; } 23 | ctor = value; 24 | break; 25 | } 26 | 27 | if (!ctor) { 28 | console.error('Route not found!') 29 | } 30 | if (this.#currentPath === path) { return; } 31 | this.#currentPath = path; 32 | 33 | const instance = new ctor(); 34 | if (this.#_shadowRoot.children[0]) { 35 | this.#_shadowRoot.removeChild(this.#_shadowRoot.children[0]); 36 | } 37 | this.#_shadowRoot.appendChild(instance); 38 | 39 | if (skipStatePush) { return; } 40 | history.pushState('', '', path); 41 | } 42 | 43 | popstateHandler = (e) => { 44 | e.preventDefault(); 45 | this.render(location.pathname, true); 46 | } 47 | 48 | connectedCallback() { 49 | this.render(location.pathname); 50 | window.addEventListener('popstate', this.popstateHandler); 51 | } 52 | 53 | disconnectedCallback() { 54 | window.removeEventListener('popstate', this.popstateHandler); 55 | } 56 | } 57 | 58 | customElements.define('app-router', Router); 59 | 60 | -------------------------------------------------------------------------------- /week11/lecture2/public/user-list.js: -------------------------------------------------------------------------------- 1 | function createUserListTempalte() { 2 | const templateString = ` 3 | 8 |
9 |
Loading...
10 | 11 |
`; 12 | 13 | const templateElement = document.createElement('template'); 14 | templateElement.innerHTML = templateString; 15 | return templateElement; 16 | } 17 | 18 | const template = createUserListTempalte(); 19 | 20 | export class UserList extends HTMLElement { 21 | #_shadowRoot = null; 22 | #queryAll = null; 23 | 24 | constructor() { 25 | super(); 26 | this.#_shadowRoot = this.attachShadow({ mode: 'closed' }); 27 | this.#_shadowRoot.appendChild(template.content.cloneNode(true)); 28 | this.#queryAll = function (selector) { 29 | return this.#_shadowRoot.querySelectorAll(selector); 30 | }; 31 | } 32 | 33 | renderUsers(users) { 34 | const [userList] = this.#queryAll('#user-list'); 35 | userList.innerHTML = ''; 36 | let i = 0; 37 | for (const user of users) { 38 | const userLi = document.createElement('li'); 39 | const userA = document.createElement('a'); 40 | userA.setAttribute('is', 'app-anchor'); 41 | userA.href = '/edit/' + i++; 42 | userA.innerHTML = user; 43 | userLi.appendChild(userA); 44 | userList.appendChild(userLi); 45 | } 46 | } 47 | 48 | loadUsers() { 49 | const [loader] = this.#queryAll('#loading'); 50 | loader.classList.add('visible'); 51 | fetch('/users') 52 | .then(res => res.ok ? res.json() : Promise.reject(new Error('Error loading users'))) 53 | .then(users => this.renderUsers(users)) 54 | .catch(err => console.error(err)) 55 | .finally(() => loader.classList.remove('visible')); 56 | } 57 | 58 | connectedCallback() { 59 | this.loadUsers(); 60 | } 61 | } 62 | 63 | customElements.define('app-user-list', UserList); 64 | -------------------------------------------------------------------------------- /week11/lecture2/user-router.js: -------------------------------------------------------------------------------- 1 | const router = new require('express').Router(); 2 | const users = ['Gosho', 'Pesho', 'Ivan']; 3 | 4 | router.get('/', (req, res) => { 5 | res.send(users); 6 | }); 7 | 8 | router.post('/', (req, res) => { 9 | const newUserName = req.body.username; 10 | users.push(newUserName); 11 | res.send(users); 12 | }); 13 | 14 | 15 | router.get('/:id', (req, res) => { 16 | const id = req.params.id; 17 | res.send(users[id]); 18 | }); 19 | 20 | router.put('/:id', (req, res) => { 21 | const index = req.params.id; 22 | const newUserName = req.body.username; 23 | users[index] = newUserName; 24 | res.send(users); 25 | }); 26 | 27 | router.delete('/:id', (req, res) => { 28 | const index = req.params.id; 29 | delete users[id]; 30 | res.send(users); 31 | }); 32 | 33 | module.exports = router; -------------------------------------------------------------------------------- /week12/exercise/readme.md: -------------------------------------------------------------------------------- 1 | # Упражнение 12 2 | 3 | ## 04.01.23 4 | 5 | 6 | 1. Напишете програма, която с помощтта на два генератора изчита инормация от подаден списък от линкове към файлове (съдържащи дървовидна структура) и печата на екрана стойностите на всички "листа". 7 | Първият генератор асинхронно да чете данните от подадените линкове, а другият синхронно да ги обработва 8 | Пример: 9 | 10 | файл1: 11 | ```json 12 | { 13 | "leaf1": 1, 14 | "leaf2": 2, 15 | "subtree1": { 16 | "leaf3": 3, 17 | "leaf4": 4, 18 | "subtree": { 19 | "leaf5": 5, 20 | "leaf6": 6 21 | } 22 | } 23 | } 24 | ``` 25 | изход след прочит на файла: 26 | ``` 27 | leaf1 - 1 28 | leaf2 - 2 29 | leaf3 - 3 30 | leaf4 - 4 31 | leaf5 - 5 32 | leaf6 - 6 33 | ``` -------------------------------------------------------------------------------- /week13/lecture/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /week13/lecture/client/README.md: -------------------------------------------------------------------------------- 1 | # 🚀 Welcome to your new awesome project! 2 | 3 | This project has been created using **webpack-cli**, you can now run 4 | 5 | ``` 6 | npm run build 7 | ``` 8 | 9 | or 10 | 11 | ``` 12 | yarn build 13 | ``` 14 | 15 | to bundle your application 16 | -------------------------------------------------------------------------------- /week13/lecture/client/dist/855c92f49fddc8ca5d72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FMIjs/advanced-javascript-2022-2023/98f8cabb830dbfd82edd0ef1686b049f15be968d/week13/lecture/client/dist/855c92f49fddc8ca5d72.png -------------------------------------------------------------------------------- /week13/lecture/client/dist/index.html: -------------------------------------------------------------------------------- 1 | Webpack App

Hello world!

Tip: Check your console

-------------------------------------------------------------------------------- /week13/lecture/client/dist/main.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: gray; 3 | } 4 | -------------------------------------------------------------------------------- /week13/lecture/client/dist/main.js: -------------------------------------------------------------------------------- 1 | /*! For license information please see main.js.LICENSE.txt */ 2 | (()=>{"use strict";class t extends HTMLAnchorElement{constructor(){super()}clickHandler(t){t.preventDefault(),this.dispatchEvent(new CustomEvent("app-render",{detail:this.href.replace(window.location.origin,""),bubbles:!0}))}connectedCallback(){this.addEventListener("click",this.clickHandler)}disconnectedCallback(){this.removeEventListener("click",this.clickHandler)}}function e(t){return t.replace(/([.+*?=^!:${}()[\]|/\\])/g,"\\$1")}function i(t){return t&&t.sensitive?"":"i"}function s(t,s,n){return function(t,s,n){void 0===n&&(n={});for(var o=n.strict,r=void 0!==o&&o,l=n.start,a=void 0===l||l,h=n.end,c=void 0===h||h,d=n.encode,u=void 0===d?function(t){return t}:d,p=n.delimiter,v=void 0===p?"/#?":p,f=n.endsWith,$="[".concat(e(void 0===f?"":f),"]|$"),_="[".concat(e(v),"]"),A=a?"^":"",m=0,g=t;m-1:void 0===b;r||(A+="(?:".concat(_,"(?=").concat($,"))?")),C||(A+="(?=".concat(_,"|").concat($,")"))}return new RegExp(A,i(n))}(function(t,i){void 0===i&&(i={});for(var s=function(t){for(var e=[],i=0;i=48&&a<=57||a>=65&&a<=90||a>=97&&a<=122||95===a))break;r+=t[l++]}if(!r)throw new TypeError("Missing parameter name at ".concat(i));e.push({type:"NAME",index:i,value:r}),i=l}else e.push({type:"CLOSE",index:i,value:t[i++]});else e.push({type:"OPEN",index:i,value:t[i++]});else e.push({type:"ESCAPED_CHAR",index:i++,value:t[i++]});else e.push({type:"MODIFIER",index:i,value:t[i++]})}return e.push({type:"END",index:i,value:""}),e}(t),n=i.prefixes,o=void 0===n?"./":n,r="[^".concat(e(i.delimiter||"/#?"),"]+?"),l=[],a=0,h=0,c="",d=function(t){if(h)?(?!\?)/g,s=0,n=i.exec(t.source);n;)e.push({name:n[1]||s++,prefix:"",suffix:"",modifier:"",pattern:""}),n=i.exec(t.source);return t}(t,e):Array.isArray(t)?function(t,e,s){var o=t.map((function(t){return n(t,e,s).source}));return new RegExp("(?:".concat(o.join("|"),")"),i(s))}(t,e,o):s(t,e,o)}customElements.define("app-anchor",t,{extends:"a"});const o=window,r=o.ShadowRoot&&(void 0===o.ShadyCSS||o.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,l=Symbol(),a=new WeakMap;class h{constructor(t,e,i){if(this._$cssResult$=!0,i!==l)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=t,this.t=e}get styleSheet(){let t=this.o;const e=this.t;if(r&&void 0===t){const i=void 0!==e&&1===e.length;i&&(t=a.get(e)),void 0===t&&((this.o=t=new CSSStyleSheet).replaceSync(this.cssText),i&&a.set(e,t))}return t}toString(){return this.cssText}}const c=r?t=>t:t=>t instanceof CSSStyleSheet?(t=>{let e="";for(const i of t.cssRules)e+=i.cssText;return(t=>new h("string"==typeof t?t:t+"",void 0,l))(e)})(t):t;var d;const u=window,p=u.trustedTypes,v=p?p.emptyScript:"",f=u.reactiveElementPolyfillSupport,$={toAttribute(t,e){switch(e){case Boolean:t=t?v:null;break;case Object:case Array:t=null==t?t:JSON.stringify(t)}return t},fromAttribute(t,e){let i=t;switch(e){case Boolean:i=null!==t;break;case Number:i=null===t?null:Number(t);break;case Object:case Array:try{i=JSON.parse(t)}catch(t){i=null}}return i}},_=(t,e)=>e!==t&&(e==e||t==t),A={attribute:!0,type:String,converter:$,reflect:!1,hasChanged:_};class m extends HTMLElement{constructor(){super(),this._$Ei=new Map,this.isUpdatePending=!1,this.hasUpdated=!1,this._$El=null,this.u()}static addInitializer(t){var e;this.finalize(),(null!==(e=this.h)&&void 0!==e?e:this.h=[]).push(t)}static get observedAttributes(){this.finalize();const t=[];return this.elementProperties.forEach(((e,i)=>{const s=this._$Ep(i,e);void 0!==s&&(this._$Ev.set(s,i),t.push(s))})),t}static createProperty(t,e=A){if(e.state&&(e.attribute=!1),this.finalize(),this.elementProperties.set(t,e),!e.noAccessor&&!this.prototype.hasOwnProperty(t)){const i="symbol"==typeof t?Symbol():"__"+t,s=this.getPropertyDescriptor(t,i,e);void 0!==s&&Object.defineProperty(this.prototype,t,s)}}static getPropertyDescriptor(t,e,i){return{get(){return this[e]},set(s){const n=this[t];this[e]=s,this.requestUpdate(t,n,i)},configurable:!0,enumerable:!0}}static getPropertyOptions(t){return this.elementProperties.get(t)||A}static finalize(){if(this.hasOwnProperty("finalized"))return!1;this.finalized=!0;const t=Object.getPrototypeOf(this);if(t.finalize(),void 0!==t.h&&(this.h=[...t.h]),this.elementProperties=new Map(t.elementProperties),this._$Ev=new Map,this.hasOwnProperty("properties")){const t=this.properties,e=[...Object.getOwnPropertyNames(t),...Object.getOwnPropertySymbols(t)];for(const i of e)this.createProperty(i,t[i])}return this.elementStyles=this.finalizeStyles(this.styles),!0}static finalizeStyles(t){const e=[];if(Array.isArray(t)){const i=new Set(t.flat(1/0).reverse());for(const t of i)e.unshift(c(t))}else void 0!==t&&e.push(c(t));return e}static _$Ep(t,e){const i=e.attribute;return!1===i?void 0:"string"==typeof i?i:"string"==typeof t?t.toLowerCase():void 0}u(){var t;this._$E_=new Promise((t=>this.enableUpdating=t)),this._$AL=new Map,this._$Eg(),this.requestUpdate(),null===(t=this.constructor.h)||void 0===t||t.forEach((t=>t(this)))}addController(t){var e,i;(null!==(e=this._$ES)&&void 0!==e?e:this._$ES=[]).push(t),void 0!==this.renderRoot&&this.isConnected&&(null===(i=t.hostConnected)||void 0===i||i.call(t))}removeController(t){var e;null===(e=this._$ES)||void 0===e||e.splice(this._$ES.indexOf(t)>>>0,1)}_$Eg(){this.constructor.elementProperties.forEach(((t,e)=>{this.hasOwnProperty(e)&&(this._$Ei.set(e,this[e]),delete this[e])}))}createRenderRoot(){var t;const e=null!==(t=this.shadowRoot)&&void 0!==t?t:this.attachShadow(this.constructor.shadowRootOptions);return((t,e)=>{r?t.adoptedStyleSheets=e.map((t=>t instanceof CSSStyleSheet?t:t.styleSheet)):e.forEach((e=>{const i=document.createElement("style"),s=o.litNonce;void 0!==s&&i.setAttribute("nonce",s),i.textContent=e.cssText,t.appendChild(i)}))})(e,this.constructor.elementStyles),e}connectedCallback(){var t;void 0===this.renderRoot&&(this.renderRoot=this.createRenderRoot()),this.enableUpdating(!0),null===(t=this._$ES)||void 0===t||t.forEach((t=>{var e;return null===(e=t.hostConnected)||void 0===e?void 0:e.call(t)}))}enableUpdating(t){}disconnectedCallback(){var t;null===(t=this._$ES)||void 0===t||t.forEach((t=>{var e;return null===(e=t.hostDisconnected)||void 0===e?void 0:e.call(t)}))}attributeChangedCallback(t,e,i){this._$AK(t,i)}_$EO(t,e,i=A){var s;const n=this.constructor._$Ep(t,i);if(void 0!==n&&!0===i.reflect){const o=(void 0!==(null===(s=i.converter)||void 0===s?void 0:s.toAttribute)?i.converter:$).toAttribute(e,i.type);this._$El=t,null==o?this.removeAttribute(n):this.setAttribute(n,o),this._$El=null}}_$AK(t,e){var i;const s=this.constructor,n=s._$Ev.get(t);if(void 0!==n&&this._$El!==n){const t=s.getPropertyOptions(n),o="function"==typeof t.converter?{fromAttribute:t.converter}:void 0!==(null===(i=t.converter)||void 0===i?void 0:i.fromAttribute)?t.converter:$;this._$El=n,this[n]=o.fromAttribute(e,t.type),this._$El=null}}requestUpdate(t,e,i){let s=!0;void 0!==t&&(((i=i||this.constructor.getPropertyOptions(t)).hasChanged||_)(this[t],e)?(this._$AL.has(t)||this._$AL.set(t,e),!0===i.reflect&&this._$El!==t&&(void 0===this._$EC&&(this._$EC=new Map),this._$EC.set(t,i))):s=!1),!this.isUpdatePending&&s&&(this._$E_=this._$Ej())}async _$Ej(){this.isUpdatePending=!0;try{await this._$E_}catch(t){Promise.reject(t)}const t=this.scheduleUpdate();return null!=t&&await t,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){var t;if(!this.isUpdatePending)return;this.hasUpdated,this._$Ei&&(this._$Ei.forEach(((t,e)=>this[e]=t)),this._$Ei=void 0);let e=!1;const i=this._$AL;try{e=this.shouldUpdate(i),e?(this.willUpdate(i),null===(t=this._$ES)||void 0===t||t.forEach((t=>{var e;return null===(e=t.hostUpdate)||void 0===e?void 0:e.call(t)})),this.update(i)):this._$Ek()}catch(t){throw e=!1,this._$Ek(),t}e&&this._$AE(i)}willUpdate(t){}_$AE(t){var e;null===(e=this._$ES)||void 0===e||e.forEach((t=>{var e;return null===(e=t.hostUpdated)||void 0===e?void 0:e.call(t)})),this.hasUpdated||(this.hasUpdated=!0,this.firstUpdated(t)),this.updated(t)}_$Ek(){this._$AL=new Map,this.isUpdatePending=!1}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$E_}shouldUpdate(t){return!0}update(t){void 0!==this._$EC&&(this._$EC.forEach(((t,e)=>this._$EO(e,this[e],t))),this._$EC=void 0),this._$Ek()}updated(t){}firstUpdated(t){}}var g;m.finalized=!0,m.elementProperties=new Map,m.elementStyles=[],m.shadowRootOptions={mode:"open"},null==f||f({ReactiveElement:m}),(null!==(d=u.reactiveElementVersions)&&void 0!==d?d:u.reactiveElementVersions=[]).push("1.5.0");const E=window,y=E.trustedTypes,w=y?y.createPolicy("lit-html",{createHTML:t=>t}):void 0,S=`lit$${(Math.random()+"").slice(9)}$`,b="?"+S,C=`<${b}>`,x=document,R=(t="")=>x.createComment(t),H=t=>null===t||"object"!=typeof t&&"function"!=typeof t,P=Array.isArray,T=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,U=/-->/g,O=/>/g,N=RegExp(">|[ \t\n\f\r](?:([^\\s\"'>=/]+)([ \t\n\f\r]*=[ \t\n\f\r]*(?:[^ \t\n\f\r\"'`<>=]|(\"|')|))|$)","g"),L=/'/g,M=/"/g,k=/^(?:script|style|textarea|title)$/i,D=t=>(e,...i)=>({_$litType$:t,strings:e,values:i}),j=D(1),I=(D(2),Symbol.for("lit-noChange")),z=Symbol.for("lit-nothing"),B=new WeakMap,W=x.createTreeWalker(x,129,null,!1),V=(t,e)=>{const i=t.length-1,s=[];let n,o=2===e?"":"",r=T;for(let e=0;e"===a[0]?(r=null!=n?n:T,h=-1):void 0===a[1]?h=-2:(h=r.lastIndex-a[2].length,l=a[1],r=void 0===a[3]?N:'"'===a[3]?M:L):r===M||r===L?r=N:r===U||r===O?r=T:(r=N,n=void 0);const d=r===N&&t[e+1].startsWith("/>")?" ":"";o+=r===T?i+C:h>=0?(s.push(l),i.slice(0,h)+"$lit$"+i.slice(h)+S+d):i+S+(-2===h?(s.push(void 0),e):d)}const l=o+(t[i]||"")+(2===e?"":"");if(!Array.isArray(t)||!t.hasOwnProperty("raw"))throw Error("invalid template strings array");return[void 0!==w?w.createHTML(l):l,s]};class q{constructor({strings:t,_$litType$:e},i){let s;this.parts=[];let n=0,o=0;const r=t.length-1,l=this.parts,[a,h]=V(t,e);if(this.el=q.createElement(a,i),W.currentNode=this.el.content,2===e){const t=this.el.content,e=t.firstChild;e.remove(),t.append(...e.childNodes)}for(;null!==(s=W.nextNode())&&l.length0){s.textContent=y?y.emptyScript:"";for(let i=0;iP(t)||"function"==typeof(null==t?void 0:t[Symbol.iterator]))(t)?this.k(t):this.g(t)}O(t,e=this._$AB){return this._$AA.parentNode.insertBefore(t,e)}T(t){this._$AH!==t&&(this._$AR(),this._$AH=this.O(t))}g(t){this._$AH!==z&&H(this._$AH)?this._$AA.nextSibling.data=t:this.T(x.createTextNode(t)),this._$AH=t}$(t){var e;const{values:i,_$litType$:s}=t,n="number"==typeof s?this._$AC(t):(void 0===s.el&&(s.el=q.createElement(s.h,this.options)),s);if((null===(e=this._$AH)||void 0===e?void 0:e._$AD)===n)this._$AH.p(i);else{const t=new J(n,this),e=t.v(this.options);t.p(i),this.T(e),this._$AH=t}}_$AC(t){let e=B.get(t.strings);return void 0===e&&B.set(t.strings,e=new q(t)),e}k(t){P(this._$AH)||(this._$AH=[],this._$AR());const e=this._$AH;let i,s=0;for(const n of t)s===e.length?e.push(i=new K(this.O(R()),this.O(R()),this,this.options)):i=e[s],i._$AI(n),s++;s2||""!==i[0]||""!==i[1]?(this._$AH=Array(i.length-1).fill(new String),this.strings=i):this._$AH=z}get tagName(){return this.element.tagName}get _$AU(){return this._$AM._$AU}_$AI(t,e=this,i,s){const n=this.strings;let o=!1;if(void 0===n)t=F(this,t,e,0),o=!H(t)||t!==this._$AH&&t!==I,o&&(this._$AH=t);else{const s=t;let r,l;for(t=n[0],r=0;r{var s,n;const o=null!==(s=null==i?void 0:i.renderBefore)&&void 0!==s?s:e;let r=o._$litPart$;if(void 0===r){const t=null!==(n=null==i?void 0:i.renderBefore)&&void 0!==n?n:null;o._$litPart$=r=new K(e.insertBefore(R(),t),t,void 0,null!=i?i:{})}return r._$AI(t),r};var st,nt;class ot extends m{constructor(){super(...arguments),this.renderOptions={host:this},this._$Do=void 0}createRenderRoot(){var t,e;const i=super.createRenderRoot();return null!==(t=(e=this.renderOptions).renderBefore)&&void 0!==t||(e.renderBefore=i.firstChild),i}update(t){const e=this.render();this.hasUpdated||(this.renderOptions.isConnected=this.isConnected),super.update(t),this._$Do=it(e,this.renderRoot,this.renderOptions)}connectedCallback(){var t;super.connectedCallback(),null===(t=this._$Do)||void 0===t||t.setConnected(!0)}disconnectedCallback(){var t;super.disconnectedCallback(),null===(t=this._$Do)||void 0===t||t.setConnected(!1)}render(){return I}}ot.finalized=!0,ot._$litElement$=!0,null===(st=globalThis.litElementHydrateSupport)||void 0===st||st.call(globalThis,{LitElement:ot});const rt=globalThis.litElementPolyfillSupport;null==rt||rt({LitElement:ot}),(null!==(nt=globalThis.litElementVersions)&&void 0!==nt?nt:globalThis.litElementVersions=[]).push("3.2.2");class lt extends HTMLElement{#t=null;set isLoading(t){this._isLoading=t,this.render()}get isLoading(){return this._isLoading}set users(t){this._users=t,this.render()}get users(){return this._users}constructor(){super(),this.#t=this.attachShadow({mode:"closed"}),this.isRenderScheduled=!1,this.isLoading=!1,this.users=[]}render(){this.isRenderScheduled||(this.isRenderScheduled=!0,Promise.resolve().then((()=>{const t=j` 3 | 8 |
9 | ${this.isLoading?j`
Loading...
`:""} 10 | ${this.isLoading?"":j`
    ${this?.users?.map((t=>j`
  • ${t}
  • `))}
`} 11 |
12 | `;it(t,this.#t),this.isRenderScheduled=!1})))}loadUsers(){this.isLoading=!0,fetch("http://localhost:8081/users").then((t=>t.ok?t.json():Promise.reject(new Error("Error loading users")))).then((t=>this.users=t)).catch((t=>console.error(t))).finally((()=>this.isLoading=!1))}connectedCallback(){this.loadUsers()}}customElements.define("app-user-list",lt);class at extends HTMLElement{#t=null;constructor(){super(),this.#t=this.attachShadow({mode:"closed"})}}customElements.define("app-edit-user",at);class ht extends HTMLElement{#t=null;#e=null;config={"/edit/:id":at,"/":lt};constructor(){super(),this.#t=this.attachShadow({mode:"closed"})}render(t,e=!1){let i=null;for(const[e,s]of Object.entries(this.config))if(n(e).test(t)){i=s;break}if(i||console.error("Route not found!"),this.#e===t)return;this.#e=t;const s=new i;this.#t.children[0]&&this.#t.removeChild(this.#t.children[0]),this.#t.appendChild(s),e||history.pushState("","",t)}popstateHandler=t=>{t.preventDefault(),this.render(location.pathname,!0)};connectedCallback(){this.render(location.pathname),window.addEventListener("popstate",this.popstateHandler)}disconnectedCallback(){window.removeEventListener("popstate",this.popstateHandler)}}customElements.define("app-router",ht);const ct=function(){const t=document.createElement("template");return t.innerHTML='\n \n AAAAAAAAAAAAAAA\n ',t}();class dt extends HTMLElement{#t=null;constructor(){super(),this.#t=this.attachShadow({mode:"closed"});const t=new ht;this.#t.appendChild(ct.content.cloneNode(!0)),this.#t.appendChild(t),this.#t.querySelector("#nav").addEventListener("app-render",(e=>{t.render(e.detail)}))}}customElements.define("app-root",dt);const ut=function(){const t=document.createElement("template");return t.innerHTML="\n \n ",t}();class pt extends HTMLElement{#t=null;constructor(){super(),this.#t=this.attachShadow({mode:"closed"}),this.#t.appendChild(ut.content.cloneNode(!0))}}customElements.define("app-slot",pt);const vt=new dt;document.body.appendChild(vt)})(); -------------------------------------------------------------------------------- /week13/lecture/client/dist/main.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2017 Google LLC 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | */ 6 | 7 | /** 8 | * @license 9 | * Copyright 2019 Google LLC 10 | * SPDX-License-Identifier: BSD-3-Clause 11 | */ 12 | -------------------------------------------------------------------------------- /week13/lecture/client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Webpack App 6 | 7 | 8 |

Hello world!

9 |

Tip: Check your console

10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /week13/lecture/client/loaders/environment-replacer.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | 3 | module.exports = function environmentReplacer(source) { 4 | if (process.env.NODE_ENV === 'production') { 5 | return fs.readFileSync(this.resourcePath.replace('environment.js', 'environment.prod.js')); 6 | } 7 | return source; 8 | } 9 | -------------------------------------------------------------------------------- /week13/lecture/client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "@webpack-cli/generators": "^3.0.1", 4 | "css-loader": "^6.7.3", 5 | "html-webpack-plugin": "^5.5.0", 6 | "mini-css-extract-plugin": "^2.7.2", 7 | "style-loader": "^3.3.1", 8 | "webpack": "^5.75.0", 9 | "webpack-cli": "^5.0.1", 10 | "webpack-dev-server": "^4.11.1" 11 | }, 12 | "version": "1.0.0", 13 | "description": "My webpack project", 14 | "name": "my-webpack-project", 15 | "scripts": { 16 | "build": "webpack --mode=production --node-env=production", 17 | "build:dev": "webpack --mode=development", 18 | "build:prod": "webpack --mode=production --node-env=production", 19 | "watch": "webpack --watch", 20 | "serve": "webpack serve" 21 | }, 22 | "dependencies": { 23 | "lit": "^2.5.0", 24 | "path-to-regexp": "^6.2.1" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /week13/lecture/client/src/app-anchor.js: -------------------------------------------------------------------------------- 1 | export class AppAnchor extends HTMLAnchorElement { 2 | 3 | constructor() { 4 | super(); 5 | } 6 | 7 | clickHandler(e) { 8 | e.preventDefault(); 9 | this.dispatchEvent(new CustomEvent('app-render', { 10 | detail: this.href.replace(window.location.origin, ''), 11 | bubbles: true 12 | })); 13 | } 14 | 15 | connectedCallback() { 16 | this.addEventListener('click', this.clickHandler); 17 | } 18 | 19 | disconnectedCallback() { 20 | this.removeEventListener('click', this.clickHandler); 21 | } 22 | 23 | } 24 | 25 | customElements.define('app-anchor', AppAnchor, { extends: 'a' }); -------------------------------------------------------------------------------- /week13/lecture/client/src/assets/webpack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FMIjs/advanced-javascript-2022-2023/98f8cabb830dbfd82edd0ef1686b049f15be968d/week13/lecture/client/src/assets/webpack.png -------------------------------------------------------------------------------- /week13/lecture/client/src/edit-user.js: -------------------------------------------------------------------------------- 1 | export class EditUser extends HTMLElement { 2 | #_shadowRoot = null; 3 | 4 | constructor() { 5 | super(); 6 | this.#_shadowRoot = this.attachShadow({ mode: 'closed' }); 7 | } 8 | } 9 | 10 | customElements.define('app-edit-user', EditUser); -------------------------------------------------------------------------------- /week13/lecture/client/src/environments/environment.js: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | apiURL: 'http://localhost:8081' 3 | } 4 | -------------------------------------------------------------------------------- /week13/lecture/client/src/environments/environment.prod.js: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | apiURL: 'http://localhost:8081' 3 | } 4 | -------------------------------------------------------------------------------- /week13/lecture/client/src/main.js: -------------------------------------------------------------------------------- 1 | import './app-anchor'; 2 | import { Router } from './router'; 3 | 4 | function createMainTemplate() { 5 | const templateString = ` 6 | 10 | AAAAAAAAAAAAAAA 11 | `; 12 | 13 | const templateElement = document.createElement('template'); 14 | templateElement.innerHTML = templateString; 15 | return templateElement; 16 | } 17 | 18 | const template = createMainTemplate(); 19 | 20 | export class AppComponent extends HTMLElement { 21 | #_shadowRoot = null; 22 | 23 | constructor() { 24 | super(); 25 | this.#_shadowRoot = this.attachShadow({ mode: 'closed' }); 26 | const router = new Router(); 27 | this.#_shadowRoot.appendChild(template.content.cloneNode(true)); 28 | this.#_shadowRoot.appendChild(router); 29 | 30 | const nav = this.#_shadowRoot.querySelector('#nav'); 31 | nav.addEventListener('app-render', (e) => { 32 | router.render(e.detail); 33 | }); 34 | } 35 | } 36 | 37 | customElements.define('app-root', AppComponent); 38 | 39 | 40 | function createSlotTempalte() { 41 | const templateString = ` 42 | 43 | `; 44 | 45 | const templateElement = document.createElement('template'); 46 | templateElement.innerHTML = templateString; 47 | return templateElement; 48 | } 49 | 50 | const slotTemplate = createSlotTempalte(); 51 | export class SlotComponent extends HTMLElement { 52 | #_shadowRoot = null; 53 | constructor() { 54 | super(); 55 | this.#_shadowRoot = this.attachShadow({ mode: 'closed' }); 56 | this.#_shadowRoot.appendChild(slotTemplate.content.cloneNode(true)); 57 | } 58 | } 59 | 60 | customElements.define('app-slot', SlotComponent); 61 | 62 | const app = new AppComponent(); 63 | document.body.appendChild(app); 64 | -------------------------------------------------------------------------------- /week13/lecture/client/src/router.js: -------------------------------------------------------------------------------- 1 | import { pathToRegexp } from 'path-to-regexp'; 2 | import { UserList } from './user-list'; 3 | import { EditUser } from './edit-user'; 4 | 5 | export class Router extends HTMLElement { 6 | #_shadowRoot = null; 7 | #currentPath = null; 8 | 9 | config = { 10 | '/edit/:id': EditUser, 11 | '/': UserList, 12 | }; 13 | 14 | constructor() { 15 | super(); 16 | this.#_shadowRoot = this.attachShadow({ mode: 'closed' }); 17 | } 18 | 19 | render(path, skipStatePush = false) { 20 | let ctor = null; 21 | for (const [key, value] of Object.entries(this.config)) { 22 | const keyRe = pathToRegexp(key); 23 | if (!keyRe.test(path)) { continue; } 24 | ctor = value; 25 | break; 26 | } 27 | 28 | if (!ctor) { 29 | console.error('Route not found!') 30 | } 31 | if (this.#currentPath === path) { return; } 32 | this.#currentPath = path; 33 | 34 | const instance = new ctor(); 35 | if (this.#_shadowRoot.children[0]) { 36 | this.#_shadowRoot.removeChild(this.#_shadowRoot.children[0]); 37 | } 38 | this.#_shadowRoot.appendChild(instance); 39 | 40 | if (skipStatePush) { return; } 41 | history.pushState('', '', path); 42 | } 43 | 44 | popstateHandler = (e) => { 45 | e.preventDefault(); 46 | this.render(location.pathname, true); 47 | } 48 | 49 | connectedCallback() { 50 | this.render(location.pathname); 51 | window.addEventListener('popstate', this.popstateHandler); 52 | } 53 | 54 | disconnectedCallback() { 55 | window.removeEventListener('popstate', this.popstateHandler); 56 | } 57 | } 58 | 59 | customElements.define('app-router', Router); 60 | 61 | -------------------------------------------------------------------------------- /week13/lecture/client/src/user-list.js: -------------------------------------------------------------------------------- 1 | import { environment } from './environments/environment'; 2 | import { html, render } from 'lit'; 3 | 4 | const renderUserListTemplate = (context) => html` 5 | 10 |
11 | ${context.isLoading ? html`
Loading...
` : ''} 12 | ${context.isLoading ? '' : html`
    ${context?.users?.map(u => html`
  • ${u}
  • `)}
`} 13 |
14 | `; 15 | 16 | export class UserList extends HTMLElement { 17 | #_shadowRoot = null; 18 | 19 | set isLoading(newValue) { 20 | this._isLoading = newValue; 21 | this.render(); 22 | } 23 | 24 | get isLoading() { 25 | return this._isLoading; 26 | } 27 | 28 | set users(newValue) { 29 | this._users = newValue; 30 | this.render(); 31 | } 32 | 33 | get users() { 34 | return this._users; 35 | } 36 | 37 | constructor() { 38 | super(); 39 | this.#_shadowRoot = this.attachShadow({ mode: 'closed' }); 40 | this.isRenderScheduled = false; 41 | this.isLoading = false; 42 | this.users = []; 43 | } 44 | 45 | render() { 46 | if (this.isRenderScheduled) { return; } 47 | this.isRenderScheduled = true; 48 | Promise.resolve().then(() => { 49 | const templateResult = renderUserListTemplate(this); 50 | render(templateResult, this.#_shadowRoot); 51 | this.isRenderScheduled = false; 52 | }); 53 | } 54 | 55 | loadUsers() { 56 | this.isLoading = true; 57 | fetch(environment.apiURL + '/users') 58 | .then(res => res.ok ? res.json() : Promise.reject(new Error('Error loading users'))) 59 | .then(users => this.users = users) 60 | .catch(err => console.error(err)) 61 | .finally(() => this.isLoading = false); 62 | } 63 | 64 | connectedCallback() { 65 | this.loadUsers(); 66 | } 67 | } 68 | 69 | customElements.define('app-user-list', UserList); 70 | -------------------------------------------------------------------------------- /week13/lecture/client/webpack.config.js: -------------------------------------------------------------------------------- 1 | // Generated using webpack-cli https://github.com/webpack/webpack-cli 2 | 3 | const path = require('path'); 4 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 5 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 6 | 7 | const isProduction = process.env.NODE_ENV == 'production'; 8 | 9 | 10 | const stylesHandler = MiniCssExtractPlugin.loader; 11 | 12 | 13 | 14 | const config = { 15 | entry: './src/main.js', 16 | output: { 17 | path: path.resolve(__dirname, 'dist'), 18 | }, 19 | devServer: { 20 | open: true, 21 | host: 'localhost', 22 | }, 23 | plugins: [ 24 | new HtmlWebpackPlugin({ 25 | template: 'index.html', 26 | }), 27 | 28 | new MiniCssExtractPlugin(), 29 | 30 | // Add your plugins here 31 | // Learn more about plugins from https://webpack.js.org/configuration/plugins/ 32 | ], 33 | module: { 34 | rules: [ 35 | { 36 | test: /environment.js/, 37 | use: { 38 | loader: path.resolve(__dirname, 'loaders', 'environment-replacer') 39 | } 40 | }, 41 | { 42 | test: /\.css$/i, 43 | use: [stylesHandler, 'css-loader'], 44 | }, 45 | { 46 | test: /\.(eot|svg|ttf|woff|woff2|png|jpg|gif)$/i, 47 | type: 'asset', 48 | }, 49 | 50 | // Add your rules for custom modules here 51 | // Learn more about loaders from https://webpack.js.org/loaders/ 52 | ], 53 | }, 54 | }; 55 | 56 | module.exports = () => { 57 | if (isProduction) { 58 | config.mode = 'production'; 59 | 60 | 61 | } else { 62 | config.mode = 'development'; 63 | } 64 | return config; 65 | }; 66 | -------------------------------------------------------------------------------- /week13/lecture/server/index.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const cors = require('cors'); 3 | const path = require('path'); 4 | const userRouter = require('./user-router.js'); 5 | 6 | const corsOptions = { 7 | origin: 'http://localhost:8080', 8 | optionsSuccessStatus: 200 // some legacy browsers (IE11, various SmartTVs) choke on 204 9 | }; 10 | 11 | 12 | const app = express(); 13 | app.use(cors(corsOptions)); 14 | app.use(express.json()); 15 | app.use(express.static(path.resolve(__dirname, '../client/dist'))); 16 | app.use('/users', userRouter); 17 | 18 | app.get('*', (req, res) => { 19 | res.sendFile(path.resolve(__dirname, '../client/dist/index.html')); 20 | }); 21 | 22 | app.listen(8081, () => { 23 | console.log('Server is listening on :8081'); 24 | }); -------------------------------------------------------------------------------- /week13/lecture/server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lecture", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "node ." 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "cors": "^2.8.5", 15 | "ejs": "^3.1.8", 16 | "express": "^4.18.2" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /week13/lecture/server/user-router.js: -------------------------------------------------------------------------------- 1 | const router = new require('express').Router(); 2 | const users = ['Gosho', 'Pesho', 'Ivan']; 3 | 4 | router.get('/', (req, res) => { 5 | res.send(users); 6 | }); 7 | 8 | router.post('/', (req, res) => { 9 | const newUserName = req.body.username; 10 | users.push(newUserName); 11 | res.send(users); 12 | }); 13 | 14 | 15 | router.get('/:id', (req, res) => { 16 | const id = req.params.id; 17 | res.send(users[id]); 18 | }); 19 | 20 | router.put('/:id', (req, res) => { 21 | const index = req.params.id; 22 | const newUserName = req.body.username; 23 | users[index] = newUserName; 24 | res.send(users); 25 | }); 26 | 27 | router.delete('/:id', (req, res) => { 28 | const index = req.params.id; 29 | delete users[id]; 30 | res.send(users); 31 | }); 32 | 33 | module.exports = router; -------------------------------------------------------------------------------- /week14/lecture/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /week14/lecture/client/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "js/ts.implicitProjectConfig.experimentalDecorators": false 3 | } -------------------------------------------------------------------------------- /week14/lecture/client/README.md: -------------------------------------------------------------------------------- 1 | # 🚀 Welcome to your new awesome project! 2 | 3 | This project has been created using **webpack-cli**, you can now run 4 | 5 | ``` 6 | npm run build 7 | ``` 8 | 9 | or 10 | 11 | ``` 12 | yarn build 13 | ``` 14 | 15 | to bundle your application 16 | -------------------------------------------------------------------------------- /week14/lecture/client/dist/855c92f49fddc8ca5d72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FMIjs/advanced-javascript-2022-2023/98f8cabb830dbfd82edd0ef1686b049f15be968d/week14/lecture/client/dist/855c92f49fddc8ca5d72.png -------------------------------------------------------------------------------- /week14/lecture/client/dist/index.html: -------------------------------------------------------------------------------- 1 | Webpack App

Hello world!

Tip: Check your console

-------------------------------------------------------------------------------- /week14/lecture/client/dist/main.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: gray; 3 | } 4 | -------------------------------------------------------------------------------- /week14/lecture/client/dist/main.js: -------------------------------------------------------------------------------- 1 | /*! For license information please see main.js.LICENSE.txt */ 2 | (()=>{"use strict";class t extends HTMLAnchorElement{constructor(){super()}clickHandler(t){t.preventDefault(),this.dispatchEvent(new CustomEvent("app-render",{detail:this.href.replace(window.location.origin,""),bubbles:!0}))}connectedCallback(){this.addEventListener("click",this.clickHandler)}disconnectedCallback(){this.removeEventListener("click",this.clickHandler)}}function e(t){return t.replace(/([.+*?=^!:${}()[\]|/\\])/g,"\\$1")}function i(t){return t&&t.sensitive?"":"i"}function s(t,s,n){return function(t,s,n){void 0===n&&(n={});for(var o=n.strict,r=void 0!==o&&o,l=n.start,a=void 0===l||l,h=n.end,c=void 0===h||h,d=n.encode,u=void 0===d?function(t){return t}:d,p=n.delimiter,v=void 0===p?"/#?":p,f=n.endsWith,$="[".concat(e(void 0===f?"":f),"]|$"),_="[".concat(e(v),"]"),A=a?"^":"",m=0,g=t;m-1:void 0===b;r||(A+="(?:".concat(_,"(?=").concat($,"))?")),C||(A+="(?=".concat(_,"|").concat($,")"))}return new RegExp(A,i(n))}(function(t,i){void 0===i&&(i={});for(var s=function(t){for(var e=[],i=0;i=48&&a<=57||a>=65&&a<=90||a>=97&&a<=122||95===a))break;r+=t[l++]}if(!r)throw new TypeError("Missing parameter name at ".concat(i));e.push({type:"NAME",index:i,value:r}),i=l}else e.push({type:"CLOSE",index:i,value:t[i++]});else e.push({type:"OPEN",index:i,value:t[i++]});else e.push({type:"ESCAPED_CHAR",index:i++,value:t[i++]});else e.push({type:"MODIFIER",index:i,value:t[i++]})}return e.push({type:"END",index:i,value:""}),e}(t),n=i.prefixes,o=void 0===n?"./":n,r="[^".concat(e(i.delimiter||"/#?"),"]+?"),l=[],a=0,h=0,c="",d=function(t){if(h)?(?!\?)/g,s=0,n=i.exec(t.source);n;)e.push({name:n[1]||s++,prefix:"",suffix:"",modifier:"",pattern:""}),n=i.exec(t.source);return t}(t,e):Array.isArray(t)?function(t,e,s){var o=t.map((function(t){return n(t,e,s).source}));return new RegExp("(?:".concat(o.join("|"),")"),i(s))}(t,e,o):s(t,e,o)}customElements.define("app-anchor",t,{extends:"a"});const o=window,r=o.ShadowRoot&&(void 0===o.ShadyCSS||o.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,l=Symbol(),a=new WeakMap;class h{constructor(t,e,i){if(this._$cssResult$=!0,i!==l)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=t,this.t=e}get styleSheet(){let t=this.o;const e=this.t;if(r&&void 0===t){const i=void 0!==e&&1===e.length;i&&(t=a.get(e)),void 0===t&&((this.o=t=new CSSStyleSheet).replaceSync(this.cssText),i&&a.set(e,t))}return t}toString(){return this.cssText}}const c=r?t=>t:t=>t instanceof CSSStyleSheet?(t=>{let e="";for(const i of t.cssRules)e+=i.cssText;return(t=>new h("string"==typeof t?t:t+"",void 0,l))(e)})(t):t;var d;const u=window,p=u.trustedTypes,v=p?p.emptyScript:"",f=u.reactiveElementPolyfillSupport,$={toAttribute(t,e){switch(e){case Boolean:t=t?v:null;break;case Object:case Array:t=null==t?t:JSON.stringify(t)}return t},fromAttribute(t,e){let i=t;switch(e){case Boolean:i=null!==t;break;case Number:i=null===t?null:Number(t);break;case Object:case Array:try{i=JSON.parse(t)}catch(t){i=null}}return i}},_=(t,e)=>e!==t&&(e==e||t==t),A={attribute:!0,type:String,converter:$,reflect:!1,hasChanged:_};class m extends HTMLElement{constructor(){super(),this._$Ei=new Map,this.isUpdatePending=!1,this.hasUpdated=!1,this._$El=null,this.u()}static addInitializer(t){var e;this.finalize(),(null!==(e=this.h)&&void 0!==e?e:this.h=[]).push(t)}static get observedAttributes(){this.finalize();const t=[];return this.elementProperties.forEach(((e,i)=>{const s=this._$Ep(i,e);void 0!==s&&(this._$Ev.set(s,i),t.push(s))})),t}static createProperty(t,e=A){if(e.state&&(e.attribute=!1),this.finalize(),this.elementProperties.set(t,e),!e.noAccessor&&!this.prototype.hasOwnProperty(t)){const i="symbol"==typeof t?Symbol():"__"+t,s=this.getPropertyDescriptor(t,i,e);void 0!==s&&Object.defineProperty(this.prototype,t,s)}}static getPropertyDescriptor(t,e,i){return{get(){return this[e]},set(s){const n=this[t];this[e]=s,this.requestUpdate(t,n,i)},configurable:!0,enumerable:!0}}static getPropertyOptions(t){return this.elementProperties.get(t)||A}static finalize(){if(this.hasOwnProperty("finalized"))return!1;this.finalized=!0;const t=Object.getPrototypeOf(this);if(t.finalize(),void 0!==t.h&&(this.h=[...t.h]),this.elementProperties=new Map(t.elementProperties),this._$Ev=new Map,this.hasOwnProperty("properties")){const t=this.properties,e=[...Object.getOwnPropertyNames(t),...Object.getOwnPropertySymbols(t)];for(const i of e)this.createProperty(i,t[i])}return this.elementStyles=this.finalizeStyles(this.styles),!0}static finalizeStyles(t){const e=[];if(Array.isArray(t)){const i=new Set(t.flat(1/0).reverse());for(const t of i)e.unshift(c(t))}else void 0!==t&&e.push(c(t));return e}static _$Ep(t,e){const i=e.attribute;return!1===i?void 0:"string"==typeof i?i:"string"==typeof t?t.toLowerCase():void 0}u(){var t;this._$E_=new Promise((t=>this.enableUpdating=t)),this._$AL=new Map,this._$Eg(),this.requestUpdate(),null===(t=this.constructor.h)||void 0===t||t.forEach((t=>t(this)))}addController(t){var e,i;(null!==(e=this._$ES)&&void 0!==e?e:this._$ES=[]).push(t),void 0!==this.renderRoot&&this.isConnected&&(null===(i=t.hostConnected)||void 0===i||i.call(t))}removeController(t){var e;null===(e=this._$ES)||void 0===e||e.splice(this._$ES.indexOf(t)>>>0,1)}_$Eg(){this.constructor.elementProperties.forEach(((t,e)=>{this.hasOwnProperty(e)&&(this._$Ei.set(e,this[e]),delete this[e])}))}createRenderRoot(){var t;const e=null!==(t=this.shadowRoot)&&void 0!==t?t:this.attachShadow(this.constructor.shadowRootOptions);return((t,e)=>{r?t.adoptedStyleSheets=e.map((t=>t instanceof CSSStyleSheet?t:t.styleSheet)):e.forEach((e=>{const i=document.createElement("style"),s=o.litNonce;void 0!==s&&i.setAttribute("nonce",s),i.textContent=e.cssText,t.appendChild(i)}))})(e,this.constructor.elementStyles),e}connectedCallback(){var t;void 0===this.renderRoot&&(this.renderRoot=this.createRenderRoot()),this.enableUpdating(!0),null===(t=this._$ES)||void 0===t||t.forEach((t=>{var e;return null===(e=t.hostConnected)||void 0===e?void 0:e.call(t)}))}enableUpdating(t){}disconnectedCallback(){var t;null===(t=this._$ES)||void 0===t||t.forEach((t=>{var e;return null===(e=t.hostDisconnected)||void 0===e?void 0:e.call(t)}))}attributeChangedCallback(t,e,i){this._$AK(t,i)}_$EO(t,e,i=A){var s;const n=this.constructor._$Ep(t,i);if(void 0!==n&&!0===i.reflect){const o=(void 0!==(null===(s=i.converter)||void 0===s?void 0:s.toAttribute)?i.converter:$).toAttribute(e,i.type);this._$El=t,null==o?this.removeAttribute(n):this.setAttribute(n,o),this._$El=null}}_$AK(t,e){var i;const s=this.constructor,n=s._$Ev.get(t);if(void 0!==n&&this._$El!==n){const t=s.getPropertyOptions(n),o="function"==typeof t.converter?{fromAttribute:t.converter}:void 0!==(null===(i=t.converter)||void 0===i?void 0:i.fromAttribute)?t.converter:$;this._$El=n,this[n]=o.fromAttribute(e,t.type),this._$El=null}}requestUpdate(t,e,i){let s=!0;void 0!==t&&(((i=i||this.constructor.getPropertyOptions(t)).hasChanged||_)(this[t],e)?(this._$AL.has(t)||this._$AL.set(t,e),!0===i.reflect&&this._$El!==t&&(void 0===this._$EC&&(this._$EC=new Map),this._$EC.set(t,i))):s=!1),!this.isUpdatePending&&s&&(this._$E_=this._$Ej())}async _$Ej(){this.isUpdatePending=!0;try{await this._$E_}catch(t){Promise.reject(t)}const t=this.scheduleUpdate();return null!=t&&await t,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){var t;if(!this.isUpdatePending)return;this.hasUpdated,this._$Ei&&(this._$Ei.forEach(((t,e)=>this[e]=t)),this._$Ei=void 0);let e=!1;const i=this._$AL;try{e=this.shouldUpdate(i),e?(this.willUpdate(i),null===(t=this._$ES)||void 0===t||t.forEach((t=>{var e;return null===(e=t.hostUpdate)||void 0===e?void 0:e.call(t)})),this.update(i)):this._$Ek()}catch(t){throw e=!1,this._$Ek(),t}e&&this._$AE(i)}willUpdate(t){}_$AE(t){var e;null===(e=this._$ES)||void 0===e||e.forEach((t=>{var e;return null===(e=t.hostUpdated)||void 0===e?void 0:e.call(t)})),this.hasUpdated||(this.hasUpdated=!0,this.firstUpdated(t)),this.updated(t)}_$Ek(){this._$AL=new Map,this.isUpdatePending=!1}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$E_}shouldUpdate(t){return!0}update(t){void 0!==this._$EC&&(this._$EC.forEach(((t,e)=>this._$EO(e,this[e],t))),this._$EC=void 0),this._$Ek()}updated(t){}firstUpdated(t){}}var g;m.finalized=!0,m.elementProperties=new Map,m.elementStyles=[],m.shadowRootOptions={mode:"open"},null==f||f({ReactiveElement:m}),(null!==(d=u.reactiveElementVersions)&&void 0!==d?d:u.reactiveElementVersions=[]).push("1.5.0");const E=window,y=E.trustedTypes,w=y?y.createPolicy("lit-html",{createHTML:t=>t}):void 0,S=`lit$${(Math.random()+"").slice(9)}$`,b="?"+S,C=`<${b}>`,x=document,R=(t="")=>x.createComment(t),H=t=>null===t||"object"!=typeof t&&"function"!=typeof t,P=Array.isArray,T=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,U=/-->/g,O=/>/g,N=RegExp(">|[ \t\n\f\r](?:([^\\s\"'>=/]+)([ \t\n\f\r]*=[ \t\n\f\r]*(?:[^ \t\n\f\r\"'`<>=]|(\"|')|))|$)","g"),L=/'/g,M=/"/g,k=/^(?:script|style|textarea|title)$/i,D=t=>(e,...i)=>({_$litType$:t,strings:e,values:i}),j=D(1),I=(D(2),Symbol.for("lit-noChange")),z=Symbol.for("lit-nothing"),B=new WeakMap,W=x.createTreeWalker(x,129,null,!1),V=(t,e)=>{const i=t.length-1,s=[];let n,o=2===e?"":"",r=T;for(let e=0;e"===a[0]?(r=null!=n?n:T,h=-1):void 0===a[1]?h=-2:(h=r.lastIndex-a[2].length,l=a[1],r=void 0===a[3]?N:'"'===a[3]?M:L):r===M||r===L?r=N:r===U||r===O?r=T:(r=N,n=void 0);const d=r===N&&t[e+1].startsWith("/>")?" ":"";o+=r===T?i+C:h>=0?(s.push(l),i.slice(0,h)+"$lit$"+i.slice(h)+S+d):i+S+(-2===h?(s.push(void 0),e):d)}const l=o+(t[i]||"")+(2===e?"":"");if(!Array.isArray(t)||!t.hasOwnProperty("raw"))throw Error("invalid template strings array");return[void 0!==w?w.createHTML(l):l,s]};class q{constructor({strings:t,_$litType$:e},i){let s;this.parts=[];let n=0,o=0;const r=t.length-1,l=this.parts,[a,h]=V(t,e);if(this.el=q.createElement(a,i),W.currentNode=this.el.content,2===e){const t=this.el.content,e=t.firstChild;e.remove(),t.append(...e.childNodes)}for(;null!==(s=W.nextNode())&&l.length0){s.textContent=y?y.emptyScript:"";for(let i=0;iP(t)||"function"==typeof(null==t?void 0:t[Symbol.iterator]))(t)?this.k(t):this.g(t)}O(t,e=this._$AB){return this._$AA.parentNode.insertBefore(t,e)}T(t){this._$AH!==t&&(this._$AR(),this._$AH=this.O(t))}g(t){this._$AH!==z&&H(this._$AH)?this._$AA.nextSibling.data=t:this.T(x.createTextNode(t)),this._$AH=t}$(t){var e;const{values:i,_$litType$:s}=t,n="number"==typeof s?this._$AC(t):(void 0===s.el&&(s.el=q.createElement(s.h,this.options)),s);if((null===(e=this._$AH)||void 0===e?void 0:e._$AD)===n)this._$AH.p(i);else{const t=new J(n,this),e=t.v(this.options);t.p(i),this.T(e),this._$AH=t}}_$AC(t){let e=B.get(t.strings);return void 0===e&&B.set(t.strings,e=new q(t)),e}k(t){P(this._$AH)||(this._$AH=[],this._$AR());const e=this._$AH;let i,s=0;for(const n of t)s===e.length?e.push(i=new K(this.O(R()),this.O(R()),this,this.options)):i=e[s],i._$AI(n),s++;s2||""!==i[0]||""!==i[1]?(this._$AH=Array(i.length-1).fill(new String),this.strings=i):this._$AH=z}get tagName(){return this.element.tagName}get _$AU(){return this._$AM._$AU}_$AI(t,e=this,i,s){const n=this.strings;let o=!1;if(void 0===n)t=F(this,t,e,0),o=!H(t)||t!==this._$AH&&t!==I,o&&(this._$AH=t);else{const s=t;let r,l;for(t=n[0],r=0;r{var s,n;const o=null!==(s=null==i?void 0:i.renderBefore)&&void 0!==s?s:e;let r=o._$litPart$;if(void 0===r){const t=null!==(n=null==i?void 0:i.renderBefore)&&void 0!==n?n:null;o._$litPart$=r=new K(e.insertBefore(R(),t),t,void 0,null!=i?i:{})}return r._$AI(t),r};var st,nt;class ot extends m{constructor(){super(...arguments),this.renderOptions={host:this},this._$Do=void 0}createRenderRoot(){var t,e;const i=super.createRenderRoot();return null!==(t=(e=this.renderOptions).renderBefore)&&void 0!==t||(e.renderBefore=i.firstChild),i}update(t){const e=this.render();this.hasUpdated||(this.renderOptions.isConnected=this.isConnected),super.update(t),this._$Do=it(e,this.renderRoot,this.renderOptions)}connectedCallback(){var t;super.connectedCallback(),null===(t=this._$Do)||void 0===t||t.setConnected(!0)}disconnectedCallback(){var t;super.disconnectedCallback(),null===(t=this._$Do)||void 0===t||t.setConnected(!1)}render(){return I}}ot.finalized=!0,ot._$litElement$=!0,null===(st=globalThis.litElementHydrateSupport)||void 0===st||st.call(globalThis,{LitElement:ot});const rt=globalThis.litElementPolyfillSupport;null==rt||rt({LitElement:ot}),(null!==(nt=globalThis.litElementVersions)&&void 0!==nt?nt:globalThis.litElementVersions=[]).push("3.2.2");class lt extends HTMLElement{#t=null;set isLoading(t){this._isLoading=t,this.render()}get isLoading(){return this._isLoading}set users(t){this._users=t,this.render()}get users(){return this._users}constructor(){super(),this.#t=this.attachShadow({mode:"closed"}),this.isRenderScheduled=!1,this.isLoading=!1,this.users=[]}render(){this.isRenderScheduled||(this.isRenderScheduled=!0,Promise.resolve().then((()=>{const t=j` 3 | 8 |
9 | ${this.isLoading?j`
Loading...
`:""} 10 | ${this.isLoading?"":j`
    ${this?.users?.map((t=>j`
  • ${t}
  • `))}
`} 11 |
12 | `;it(t,this.#t),this.isRenderScheduled=!1})))}loadUsers(){this.isLoading=!0,fetch("http://localhost:8081/users").then((t=>t.ok?t.json():Promise.reject(new Error("Error loading users")))).then((t=>this.users=t)).catch((t=>console.error(t))).finally((()=>this.isLoading=!1))}connectedCallback(){this.loadUsers()}}customElements.define("app-user-list",lt);class at extends HTMLElement{#t=null;constructor(){super(),this.#t=this.attachShadow({mode:"closed"})}}customElements.define("app-edit-user",at);class ht extends HTMLElement{#t=null;#e=null;config={"/edit/:id":at,"/":lt};constructor(){super(),this.#t=this.attachShadow({mode:"closed"})}render(t,e=!1){let i=null;for(const[e,s]of Object.entries(this.config))if(n(e).test(t)){i=s;break}if(i||console.error("Route not found!"),this.#e===t)return;this.#e=t;const s=new i;this.#t.children[0]&&this.#t.removeChild(this.#t.children[0]),this.#t.appendChild(s),e||history.pushState("","",t)}popstateHandler=t=>{t.preventDefault(),this.render(location.pathname,!0)};connectedCallback(){this.render(location.pathname),window.addEventListener("popstate",this.popstateHandler)}disconnectedCallback(){window.removeEventListener("popstate",this.popstateHandler)}}customElements.define("app-router",ht);const ct=function(){const t=document.createElement("template");return t.innerHTML='\n \n AAAAAAAAAAAAAAA\n ',t}();class dt extends HTMLElement{#t=null;constructor(){super(),this.#t=this.attachShadow({mode:"closed"});const t=new ht;this.#t.appendChild(ct.content.cloneNode(!0)),this.#t.appendChild(t),this.#t.querySelector("#nav").addEventListener("app-render",(e=>{t.render(e.detail)}))}}customElements.define("app-root",dt);const ut=function(){const t=document.createElement("template");return t.innerHTML="\n \n ",t}();class pt extends HTMLElement{#t=null;constructor(){super(),this.#t=this.attachShadow({mode:"closed"}),this.#t.appendChild(ut.content.cloneNode(!0))}}customElements.define("app-slot",pt);const vt=new dt;document.body.appendChild(vt)})(); -------------------------------------------------------------------------------- /week14/lecture/client/dist/main.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2017 Google LLC 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | */ 6 | 7 | /** 8 | * @license 9 | * Copyright 2019 Google LLC 10 | * SPDX-License-Identifier: BSD-3-Clause 11 | */ 12 | -------------------------------------------------------------------------------- /week14/lecture/client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Webpack App 6 | 7 | 8 |

Hello world!

9 |

Tip: Check your console

10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /week14/lecture/client/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "experimentalDecorators": true 4 | } 5 | } -------------------------------------------------------------------------------- /week14/lecture/client/loaders/environment-replacer.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | 3 | module.exports = function environmentReplacer(source) { 4 | if (process.env.NODE_ENV === 'production') { 5 | return fs.readFileSync(this.resourcePath.replace('environment.js', 'environment.prod.js')); 6 | } 7 | return source; 8 | } 9 | -------------------------------------------------------------------------------- /week14/lecture/client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "@babel/core": "^7.20.12", 4 | "@babel/plugin-proposal-decorators": "^7.20.7", 5 | "@babel/preset-env": "^7.20.2", 6 | "@webpack-cli/generators": "^3.0.1", 7 | "babel-loader": "^9.1.2", 8 | "css-loader": "^6.7.3", 9 | "html-webpack-plugin": "^5.5.0", 10 | "mini-css-extract-plugin": "^2.7.2", 11 | "style-loader": "^3.3.1", 12 | "webpack": "^5.75.0", 13 | "webpack-cli": "^5.0.1", 14 | "webpack-dev-server": "^4.11.1" 15 | }, 16 | "version": "1.0.0", 17 | "description": "My webpack project", 18 | "name": "my-webpack-project", 19 | "scripts": { 20 | "build": "webpack --mode=production --node-env=production", 21 | "build:dev": "webpack --mode=development", 22 | "build:prod": "webpack --mode=production --node-env=production", 23 | "watch": "webpack --watch", 24 | "serve": "webpack serve" 25 | }, 26 | "dependencies": { 27 | "lit": "^2.5.0", 28 | "path-to-regexp": "^6.2.1" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /week14/lecture/client/src/app-anchor.js: -------------------------------------------------------------------------------- 1 | export class AppAnchor extends HTMLAnchorElement { 2 | 3 | constructor() { 4 | super(); 5 | } 6 | 7 | clickHandler(e) { 8 | e.preventDefault(); 9 | this.dispatchEvent(new CustomEvent('app-render', { 10 | detail: this.href.replace(window.location.origin, ''), 11 | bubbles: true 12 | })); 13 | } 14 | 15 | connectedCallback() { 16 | this.addEventListener('click', this.clickHandler); 17 | } 18 | 19 | disconnectedCallback() { 20 | this.removeEventListener('click', this.clickHandler); 21 | } 22 | 23 | } 24 | 25 | customElements.define('app-anchor', AppAnchor, { extends: 'a' }); -------------------------------------------------------------------------------- /week14/lecture/client/src/assets/webpack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FMIjs/advanced-javascript-2022-2023/98f8cabb830dbfd82edd0ef1686b049f15be968d/week14/lecture/client/src/assets/webpack.png -------------------------------------------------------------------------------- /week14/lecture/client/src/decorators/component.js: -------------------------------------------------------------------------------- 1 | import { render } from 'lit'; 2 | export function Component(config) { 3 | return function (target) { 4 | target.prototype._render = function () { 5 | if (!this.shadowRoot) { this.attachShadow({ mode: 'open' }); } 6 | if (this.isRenderScheduled) { return; } 7 | this.isRenderScheduled = true; 8 | Promise.resolve().then(() => { 9 | const templateResult = config.template(this); 10 | render(templateResult, this.shadowRoot); 11 | this.isRenderScheduled = false; 12 | }); 13 | } 14 | }; 15 | } -------------------------------------------------------------------------------- /week14/lecture/client/src/decorators/property.js: -------------------------------------------------------------------------------- 1 | export function property(target, propertyKey) { 2 | let value; 3 | Object.defineProperty(target, propertyKey, { 4 | set(newValue) { 5 | value = newValue; 6 | this._render(); 7 | }, 8 | get() { 9 | return value; 10 | } 11 | }); 12 | return target; 13 | } -------------------------------------------------------------------------------- /week14/lecture/client/src/edit-user.js: -------------------------------------------------------------------------------- 1 | export class EditUser extends HTMLElement { 2 | #_shadowRoot = null; 3 | 4 | constructor() { 5 | super(); 6 | this.#_shadowRoot = this.attachShadow({ mode: 'closed' }); 7 | } 8 | } 9 | 10 | customElements.define('app-edit-user', EditUser); -------------------------------------------------------------------------------- /week14/lecture/client/src/environments/environment.js: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | apiURL: 'http://localhost:8081' 3 | } 4 | -------------------------------------------------------------------------------- /week14/lecture/client/src/environments/environment.prod.js: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | apiURL: 'http://localhost:8081' 3 | } 4 | -------------------------------------------------------------------------------- /week14/lecture/client/src/main.js: -------------------------------------------------------------------------------- 1 | import './app-anchor'; 2 | import { Router } from './router'; 3 | 4 | function createMainTemplate() { 5 | const templateString = ` 6 | 10 | AAAAAAAAAAAAAAA 11 | `; 12 | 13 | const templateElement = document.createElement('template'); 14 | templateElement.innerHTML = templateString; 15 | return templateElement; 16 | } 17 | 18 | const template = createMainTemplate(); 19 | 20 | export class AppComponent extends HTMLElement { 21 | #_shadowRoot = null; 22 | 23 | constructor() { 24 | super(); 25 | this.#_shadowRoot = this.attachShadow({ mode: 'closed' }); 26 | const router = new Router(); 27 | this.#_shadowRoot.appendChild(template.content.cloneNode(true)); 28 | this.#_shadowRoot.appendChild(router); 29 | 30 | const nav = this.#_shadowRoot.querySelector('#nav'); 31 | nav.addEventListener('app-render', (e) => { 32 | router.render(e.detail); 33 | }); 34 | } 35 | } 36 | 37 | customElements.define('app-root', AppComponent); 38 | 39 | 40 | function createSlotTempalte() { 41 | const templateString = ` 42 | 43 | `; 44 | 45 | const templateElement = document.createElement('template'); 46 | templateElement.innerHTML = templateString; 47 | return templateElement; 48 | } 49 | 50 | const slotTemplate = createSlotTempalte(); 51 | export class SlotComponent extends HTMLElement { 52 | #_shadowRoot = null; 53 | constructor() { 54 | super(); 55 | this.#_shadowRoot = this.attachShadow({ mode: 'closed' }); 56 | this.#_shadowRoot.appendChild(slotTemplate.content.cloneNode(true)); 57 | } 58 | } 59 | 60 | customElements.define('app-slot', SlotComponent); 61 | 62 | const app = new AppComponent(); 63 | document.body.appendChild(app); 64 | -------------------------------------------------------------------------------- /week14/lecture/client/src/router.js: -------------------------------------------------------------------------------- 1 | import { pathToRegexp } from 'path-to-regexp'; 2 | import { UserList } from './user-list'; 3 | import { EditUser } from './edit-user'; 4 | 5 | export class Router extends HTMLElement { 6 | #_shadowRoot = null; 7 | #currentPath = null; 8 | 9 | config = { 10 | '/edit/:id': EditUser, 11 | '/': UserList, 12 | }; 13 | 14 | constructor() { 15 | super(); 16 | this.#_shadowRoot = this.attachShadow({ mode: 'closed' }); 17 | } 18 | 19 | render(path, skipStatePush = false) { 20 | let ctor = null; 21 | for (const [key, value] of Object.entries(this.config)) { 22 | const keyRe = pathToRegexp(key); 23 | if (!keyRe.test(path)) { continue; } 24 | ctor = value; 25 | break; 26 | } 27 | 28 | if (!ctor) { 29 | console.error('Route not found!') 30 | } 31 | if (this.#currentPath === path) { return; } 32 | this.#currentPath = path; 33 | 34 | const instance = new ctor(); 35 | if (this.#_shadowRoot.children[0]) { 36 | this.#_shadowRoot.removeChild(this.#_shadowRoot.children[0]); 37 | } 38 | this.#_shadowRoot.appendChild(instance); 39 | 40 | if (skipStatePush) { return; } 41 | history.pushState('', '', path); 42 | } 43 | 44 | popstateHandler = (e) => { 45 | e.preventDefault(); 46 | this.render(location.pathname, true); 47 | } 48 | 49 | connectedCallback() { 50 | this.render(location.pathname); 51 | window.addEventListener('popstate', this.popstateHandler); 52 | } 53 | 54 | disconnectedCallback() { 55 | window.removeEventListener('popstate', this.popstateHandler); 56 | } 57 | } 58 | 59 | customElements.define('app-router', Router); 60 | 61 | -------------------------------------------------------------------------------- /week14/lecture/client/src/test.js: -------------------------------------------------------------------------------- 1 | 2 | function logger(target, propertyKey, descriptor) { 3 | let value; 4 | Object.defineProperty(target, propertyKey, { 5 | set(newValue) { 6 | console.log(`new value for ${propertyKey} is ${newValue}`); 7 | value = newValue; 8 | }, 9 | get() { 10 | return value; 11 | } 12 | }); 13 | return target; 14 | } 15 | 16 | class Person { 17 | @logger name; 18 | constructor() { 19 | } 20 | } 21 | 22 | const ivan = new Person('Test'); 23 | 24 | ivan.name = 'Ivan'; 25 | -------------------------------------------------------------------------------- /week14/lecture/client/src/user-list.js: -------------------------------------------------------------------------------- 1 | import { environment } from './environments/environment'; 2 | import { html } from 'lit'; 3 | import { Component } from './decorators/component'; 4 | import { property } from './decorators/property'; 5 | 6 | 7 | const template = (context) => html` 8 | 13 |
14 | ${context.isLoading ? html`
Loading...
` : ''} 15 | ${context.isLoading ? '' : html`
    ${context?.users?.map(u => html`
  • ${u}
  • `)}
`} 16 |
17 | `; 18 | 19 | @Component({ template }) 20 | export class UserList extends HTMLElement { 21 | @property isLoading; 22 | @property users; 23 | 24 | constructor() { 25 | super(); 26 | this.isRenderScheduled = false; 27 | this.isLoading = false; 28 | this.users = []; 29 | } 30 | 31 | loadUsers() { 32 | this.isLoading = true; 33 | fetch(environment.apiURL + '/users') 34 | .then(res => res.ok ? res.json() : Promise.reject(new Error('Error loading users'))) 35 | .then(users => this.users = users) 36 | .catch(err => console.error(err)) 37 | .finally(() => this.isLoading = false); 38 | } 39 | 40 | connectedCallback() { 41 | this.loadUsers(); 42 | } 43 | } 44 | 45 | customElements.define('app-user-list', UserList); 46 | -------------------------------------------------------------------------------- /week14/lecture/client/webpack.config.js: -------------------------------------------------------------------------------- 1 | // Generated using webpack-cli https://github.com/webpack/webpack-cli 2 | 3 | const path = require('path'); 4 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 5 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 6 | 7 | const isProduction = process.env.NODE_ENV == 'production'; 8 | const stylesHandler = MiniCssExtractPlugin.loader; 9 | 10 | const config = { 11 | entry: './src/main.js', 12 | output: { 13 | path: path.resolve(__dirname, 'dist'), 14 | }, 15 | devServer: { 16 | open: true, 17 | host: 'localhost', 18 | }, 19 | plugins: [ 20 | new HtmlWebpackPlugin({ 21 | template: 'index.html', 22 | }), 23 | 24 | new MiniCssExtractPlugin(), 25 | 26 | // Add your plugins here 27 | // Learn more about plugins from https://webpack.js.org/configuration/plugins/ 28 | ], 29 | module: { 30 | rules: [ 31 | { 32 | test: /\.m?js$/, 33 | exclude: /node_modules/, 34 | use: { 35 | loader: 'babel-loader', 36 | options: { 37 | presets: [ 38 | ['@babel/preset-env', { targets: "defaults" }] 39 | ], 40 | plugins: [ 41 | ["@babel/plugin-proposal-decorators", { legacy: true }], 42 | ["@babel/plugin-proposal-class-properties", { loose: true }] 43 | ] 44 | } 45 | } 46 | }, 47 | { 48 | test: /environment.js/, 49 | use: { 50 | loader: path.resolve(__dirname, 'loaders', 'environment-replacer') 51 | } 52 | }, 53 | { 54 | test: /\.css$/i, 55 | use: [stylesHandler, 'css-loader'], 56 | }, 57 | { 58 | test: /\.(eot|svg|ttf|woff|woff2|png|jpg|gif)$/i, 59 | type: 'asset', 60 | }, 61 | 62 | // Add your rules for custom modules here 63 | // Learn more about loaders from https://webpack.js.org/loaders/ 64 | ], 65 | }, 66 | }; 67 | 68 | module.exports = () => { 69 | if (isProduction) { 70 | config.mode = 'production'; 71 | 72 | 73 | } else { 74 | config.mode = 'development'; 75 | } 76 | return config; 77 | }; 78 | -------------------------------------------------------------------------------- /week14/lecture/server/index.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const cors = require('cors'); 3 | const path = require('path'); 4 | const userRouter = require('./user-router.js'); 5 | 6 | const corsOptions = { 7 | origin: 'http://localhost:8080', 8 | optionsSuccessStatus: 200 // some legacy browsers (IE11, various SmartTVs) choke on 204 9 | }; 10 | 11 | 12 | const app = express(); 13 | app.use(cors(corsOptions)); 14 | app.use(express.json()); 15 | app.use(express.static(path.resolve(__dirname, '../client/dist'))); 16 | app.use('/users', userRouter); 17 | 18 | app.get('*', (req, res) => { 19 | res.sendFile(path.resolve(__dirname, '../client/dist/index.html')); 20 | }); 21 | 22 | app.listen(8081, () => { 23 | console.log('Server is listening on :8081'); 24 | }); -------------------------------------------------------------------------------- /week14/lecture/server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lecture", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "node ." 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "cors": "^2.8.5", 15 | "ejs": "^3.1.8", 16 | "express": "^4.18.2" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /week14/lecture/server/user-router.js: -------------------------------------------------------------------------------- 1 | const router = new require('express').Router(); 2 | const users = ['Gosho', 'Pesho', 'Ivan']; 3 | 4 | router.get('/', (req, res) => { 5 | res.send(users); 6 | }); 7 | 8 | router.post('/', (req, res) => { 9 | const newUserName = req.body.username; 10 | users.push(newUserName); 11 | res.send(users); 12 | }); 13 | 14 | 15 | router.get('/:id', (req, res) => { 16 | const id = req.params.id; 17 | res.send(users[id]); 18 | }); 19 | 20 | router.put('/:id', (req, res) => { 21 | const index = req.params.id; 22 | const newUserName = req.body.username; 23 | users[index] = newUserName; 24 | res.send(users); 25 | }); 26 | 27 | router.delete('/:id', (req, res) => { 28 | const index = req.params.id; 29 | delete users[id]; 30 | res.send(users); 31 | }); 32 | 33 | module.exports = router; -------------------------------------------------------------------------------- /week15/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Document 9 | 28 | 29 | 30 | 31 |
32 | 34 | 35 | 36 | 40 | 41 | 42 | 47 | 48 | 49 | 54 | 55 | 56 | 59 | 60 | 61 | 65 | 70 | 75 | 78 | 79 |
80 | 81 | 83 | 152 | 153 | 154 | 155 | 156 | 02 157 | 158 | OKT 159 | 160 | 161 | 2022 162 | 163 | 164 | 165 | Цял ден работилници и музика на 166 | открито. 167 | 168 | 169 | 170 | 171 | Маймунарника, 172 | парк Борисова Градина, 173 | София, България 174 | Начален час 10:00. 175 | Вход Свободен. 176 | 177 | 178 | TECHNIVERSE 179 | 180 | 181 | 183 | 185 | 187 | 188 | 190 | 192 | 193 | 195 | 197 | 199 | 201 | 203 | 205 | 206 | 209 | 210 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 222 | 224 | 226 | 229 | 230 | 231 | 232 | С поглед към 233 | технологии за 234 | мобилност от 235 | 236 | 237 | 238 | 239 | 275 | 276 | 277 | -------------------------------------------------------------------------------- /week16/lecture/client/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /week16/lecture/client/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "js/ts.implicitProjectConfig.experimentalDecorators": false 3 | } -------------------------------------------------------------------------------- /week16/lecture/client/README.md: -------------------------------------------------------------------------------- 1 | # 🚀 Welcome to your new awesome project! 2 | 3 | This project has been created using **webpack-cli**, you can now run 4 | 5 | ``` 6 | npm run build 7 | ``` 8 | 9 | or 10 | 11 | ``` 12 | yarn build 13 | ``` 14 | 15 | to bundle your application 16 | -------------------------------------------------------------------------------- /week16/lecture/client/dist/855c92f49fddc8ca5d72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FMIjs/advanced-javascript-2022-2023/98f8cabb830dbfd82edd0ef1686b049f15be968d/week16/lecture/client/dist/855c92f49fddc8ca5d72.png -------------------------------------------------------------------------------- /week16/lecture/client/dist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Webpack App 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /week16/lecture/client/dist/main.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: gray; 3 | } 4 | -------------------------------------------------------------------------------- /week16/lecture/client/dist/main.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2017 Google LLC 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | */ 6 | 7 | /** 8 | * @license 9 | * Copyright 2019 Google LLC 10 | * SPDX-License-Identifier: BSD-3-Clause 11 | */ 12 | -------------------------------------------------------------------------------- /week16/lecture/client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Webpack App 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /week16/lecture/client/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "experimentalDecorators": true 4 | } 5 | } -------------------------------------------------------------------------------- /week16/lecture/client/loaders/environment-replacer.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | 3 | module.exports = function environmentReplacer(source) { 4 | if (process.env.NODE_ENV === 'production') { 5 | return fs.readFileSync(this.resourcePath.replace('environment.js', 'environment.prod.js')); 6 | } 7 | return source; 8 | } 9 | -------------------------------------------------------------------------------- /week16/lecture/client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "@babel/core": "^7.20.12", 4 | "@babel/plugin-proposal-decorators": "^7.20.7", 5 | "@babel/preset-env": "^7.20.2", 6 | "@webpack-cli/generators": "^3.0.1", 7 | "babel-loader": "^9.1.2", 8 | "css-loader": "^6.7.3", 9 | "html-webpack-plugin": "^5.5.0", 10 | "mini-css-extract-plugin": "^2.7.2", 11 | "style-loader": "^3.3.1", 12 | "webpack": "^5.75.0", 13 | "webpack-cli": "^5.0.1", 14 | "webpack-dev-server": "^4.11.1" 15 | }, 16 | "version": "1.0.0", 17 | "description": "My webpack project", 18 | "name": "my-webpack-project", 19 | "scripts": { 20 | "build": "webpack --mode=production --node-env=production", 21 | "build:dev": "webpack --mode=development", 22 | "build:prod": "webpack --mode=production --node-env=production", 23 | "watch": "webpack --watch", 24 | "serve": "webpack serve" 25 | }, 26 | "dependencies": { 27 | "lit": "^2.5.0", 28 | "path-to-regexp": "^6.2.1" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /week16/lecture/client/src/app-anchor.js: -------------------------------------------------------------------------------- 1 | export class AppAnchor extends HTMLAnchorElement { 2 | 3 | constructor() { 4 | super(); 5 | } 6 | 7 | clickHandler(e) { 8 | e.preventDefault(); 9 | this.dispatchEvent(new CustomEvent('app-render', { 10 | detail: this.href.replace(window.location.origin, ''), 11 | bubbles: true 12 | })); 13 | } 14 | 15 | connectedCallback() { 16 | this.addEventListener('click', this.clickHandler); 17 | } 18 | 19 | disconnectedCallback() { 20 | this.removeEventListener('click', this.clickHandler); 21 | } 22 | 23 | } 24 | 25 | customElements.define('app-anchor', AppAnchor, { extends: 'a' }); -------------------------------------------------------------------------------- /week16/lecture/client/src/assets/webpack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FMIjs/advanced-javascript-2022-2023/98f8cabb830dbfd82edd0ef1686b049f15be968d/week16/lecture/client/src/assets/webpack.png -------------------------------------------------------------------------------- /week16/lecture/client/src/decorators/component.js: -------------------------------------------------------------------------------- 1 | import { render } from 'lit'; 2 | export function Component(config) { 3 | return function (target) { 4 | target.prototype._render = function () { 5 | if (this.isRenderScheduled) { return; } 6 | this.isRenderScheduled = true; 7 | Promise.resolve().then(() => { 8 | const templateResult = config.template(this); 9 | render(templateResult, this.shadowRoot); 10 | this.isRenderScheduled = false; 11 | }); 12 | } 13 | return class extends target { 14 | constructor(...args) { 15 | const result = super(...args); 16 | result.attachShadow({ mode: 'open' }); 17 | result._render(); 18 | return result; 19 | } 20 | } 21 | }; 22 | } -------------------------------------------------------------------------------- /week16/lecture/client/src/decorators/property.js: -------------------------------------------------------------------------------- 1 | export function property(target, propertyKey) { 2 | let value; 3 | Object.defineProperty(target, propertyKey, { 4 | set(newValue) { 5 | value = newValue; 6 | this._render(); 7 | }, 8 | get() { 9 | return value; 10 | } 11 | }); 12 | return target; 13 | } -------------------------------------------------------------------------------- /week16/lecture/client/src/edit-user.js: -------------------------------------------------------------------------------- 1 | import { html } from 'lit'; 2 | import { Component } from './decorators/component'; 3 | 4 | 5 | const template = (context) => html` 6 |

EDIT USER TEMPLATE

7 | `; 8 | 9 | @Component({ template }) 10 | export class EditUser extends HTMLElement { 11 | 12 | constructor() { 13 | super(); 14 | } 15 | 16 | 17 | } 18 | 19 | customElements.define('app-edit-user', EditUser); 20 | -------------------------------------------------------------------------------- /week16/lecture/client/src/environments/environment.js: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | apiURL: 'http://localhost:8081' 3 | } 4 | -------------------------------------------------------------------------------- /week16/lecture/client/src/environments/environment.prod.js: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | apiURL: 'http://localhost:8081' 3 | } 4 | -------------------------------------------------------------------------------- /week16/lecture/client/src/home.js: -------------------------------------------------------------------------------- 1 | import { html } from 'lit'; 2 | import { Component } from './decorators/component'; 3 | 4 | 5 | const template = (context) => html` 6 |

HOME PAGE

7 | `; 8 | 9 | @Component({ template }) 10 | export class Home extends HTMLElement { 11 | 12 | constructor() { 13 | super(); 14 | } 15 | 16 | 17 | } 18 | 19 | customElements.define('app-home', Home); 20 | -------------------------------------------------------------------------------- /week16/lecture/client/src/login.js: -------------------------------------------------------------------------------- 1 | import { html } from 'lit'; 2 | import { Component } from './decorators/component'; 3 | import { environment } from './environments/environment'; 4 | 5 | 6 | const template = (context) => html` 7 |

Login

8 |
9 |
10 | 11 | 12 |
13 |
14 | 15 | 16 |
17 | 18 |
19 | `; 20 | 21 | @Component({ template }) 22 | export class Login extends HTMLElement { 23 | 24 | router = null; 25 | state = null; 26 | 27 | constructor({ router, state }) { 28 | super(); 29 | this.router = router; 30 | this.state = state; 31 | } 32 | 33 | loginHandler = (event) => { 34 | event.preventDefault(); 35 | const data = new FormData(event.target); 36 | const email = data.get('email'); 37 | const password = data.get('password'); 38 | fetch(`${environment.apiURL}/users?action=login`, { 39 | method: 'POST', 40 | headers: { 41 | 'Content-Type': 'application/json', 42 | // 'access-token': token 43 | }, 44 | credentials: 'include', 45 | body: JSON.stringify({ email, password }) 46 | }).then(res => res.ok ? res.json() : null) 47 | .then(({ user, token }) => { 48 | if (!user) { return void console.error('SHOW ERROR IN HTML'); } 49 | console.log(token); 50 | this.state.user = user; 51 | this.router.navigate('/list'); 52 | }); 53 | } 54 | } 55 | 56 | customElements.define('app-login', Login); 57 | -------------------------------------------------------------------------------- /week16/lecture/client/src/logout.js: -------------------------------------------------------------------------------- 1 | import { html } from 'lit'; 2 | import { Component } from './decorators/component'; 3 | import { environment } from './environments/environment'; 4 | 5 | const template = (context) => html` 6 |

Signing out...

7 | `; 8 | 9 | @Component({ template }) 10 | export class Logout extends HTMLElement { 11 | 12 | router = null; 13 | state = null; 14 | constructor({ router, state }) { 15 | super(); 16 | this.router = router; 17 | this.state = state; 18 | } 19 | 20 | connectedCallback() { 21 | this.logout(); 22 | } 23 | 24 | logout() { 25 | fetch(`${environment.apiURL}/users?action=logout`, { method: 'GET', credentials: 'include' }) 26 | .then(() => { 27 | this.state.user = null; 28 | this.router.navigate('/'); 29 | }); 30 | } 31 | 32 | } 33 | 34 | customElements.define('app-logout', Logout); 35 | -------------------------------------------------------------------------------- /week16/lecture/client/src/main.js: -------------------------------------------------------------------------------- 1 | 2 | import './app-anchor'; 3 | import { Router } from './router'; 4 | import { environment } from './environments/environment'; 5 | 6 | try { 7 | const worker = new Worker(environment.apiURL + '/worker.js'); 8 | worker.addEventListener('message', (message) => { 9 | console.log(message.data); 10 | }); 11 | worker.postMessage('start'); 12 | 13 | setTimeout(() => { 14 | worker.postMessage('stop'); 15 | }, 5000); 16 | } catch (err) { 17 | console.error('Error starting worker. User api server in order to start the worker!'); 18 | } 19 | 20 | function createMainTemplate() { 21 | const templateString = ` 22 | 29 | SLOT EXAMPLE 30 | `; 31 | 32 | const templateElement = document.createElement('template'); 33 | templateElement.innerHTML = templateString; 34 | return templateElement; 35 | } 36 | 37 | const template = createMainTemplate(); 38 | 39 | const state = { user: null }; 40 | 41 | export class AppComponent extends HTMLElement { 42 | #_shadowRoot = null; 43 | 44 | constructor() { 45 | super(); 46 | this.#_shadowRoot = this.attachShadow({ mode: 'closed' }); 47 | } 48 | 49 | connectedCallback() { 50 | this.authenticate().then(() => { 51 | this.init(); 52 | }); 53 | } 54 | 55 | init() { 56 | const router = new Router({ state }); 57 | this.#_shadowRoot.appendChild(template.content.cloneNode(true)); 58 | this.#_shadowRoot.appendChild(router); 59 | 60 | const nav = this.#_shadowRoot.querySelector('#nav'); 61 | nav.addEventListener('app-render', (e) => { 62 | router.render(e.detail); 63 | }); 64 | } 65 | 66 | authenticate = () => { 67 | return fetch(`${environment.apiURL}/users?action=auth`, { 68 | headers: { 69 | 'Content-Type': 'application/json' 70 | }, 71 | credentials: 'include' 72 | }).then(res => res.ok ? res.json() : null) 73 | .then((result) => state.user = result); 74 | } 75 | } 76 | 77 | customElements.define('app-root', AppComponent); 78 | 79 | 80 | function createSlotTempalte() { 81 | const templateString = ` 82 | 83 | `; 84 | 85 | const templateElement = document.createElement('template'); 86 | templateElement.innerHTML = templateString; 87 | return templateElement; 88 | } 89 | 90 | const slotTemplate = createSlotTempalte(); 91 | export class SlotComponent extends HTMLElement { 92 | #_shadowRoot = null; 93 | constructor() { 94 | super(); 95 | this.#_shadowRoot = this.attachShadow({ mode: 'closed' }); 96 | this.#_shadowRoot.appendChild(slotTemplate.content.cloneNode(true)); 97 | } 98 | } 99 | 100 | customElements.define('app-slot', SlotComponent); 101 | 102 | const app = new AppComponent(); 103 | document.body.appendChild(app); 104 | -------------------------------------------------------------------------------- /week16/lecture/client/src/router.js: -------------------------------------------------------------------------------- 1 | import { pathToRegexp } from 'path-to-regexp'; 2 | import { UserList } from './user-list'; 3 | import { EditUser } from './edit-user'; 4 | import { Home } from './home'; 5 | import { Logout } from './logout'; 6 | import { Login } from './login'; 7 | 8 | export class Router extends HTMLElement { 9 | #_shadowRoot = null; 10 | #currentPath = null; 11 | state = null; 12 | 13 | config = { 14 | '/edit/:id': { component: EditUser, isRestricted: true }, 15 | '/list': { component: UserList, isRestricted: true }, 16 | '/logout': { component: Logout, isRestricted: true }, 17 | '/login': { component: Login, isRestricted: false }, 18 | '/': { component: Home, isRestricted: false } 19 | }; 20 | 21 | constructor({ state }) { 22 | super(); 23 | this.state = state; 24 | this.#_shadowRoot = this.attachShadow({ mode: 'closed' }); 25 | } 26 | 27 | render(path, skipStatePush = false) { 28 | let ctor = null; 29 | let isRestricted = null; 30 | for (const [key, value] of Object.entries(this.config)) { 31 | const keyRe = pathToRegexp(key); 32 | if (!keyRe.test(path)) { continue; } 33 | ctor = value.component; 34 | isRestricted = value.isRestricted; 35 | if (isRestricted && !this.state.user) { 36 | this.navigate('/login'); 37 | return; 38 | } 39 | break; 40 | } 41 | 42 | if (!ctor) { 43 | console.error('Route not found!') 44 | } 45 | if (this.#currentPath === path) { return; } 46 | this.#currentPath = path; 47 | 48 | const instance = new ctor({ router: this, state: this.state }); 49 | if (this.#_shadowRoot.children[0]) { 50 | this.#_shadowRoot.removeChild(this.#_shadowRoot.children[0]); 51 | } 52 | this.#_shadowRoot.appendChild(instance); 53 | 54 | if (skipStatePush) { return; } 55 | history.pushState('', '', path); 56 | } 57 | 58 | navigate = (path) => { 59 | setTimeout(() => { this.render(path); }); 60 | } 61 | 62 | popstateHandler = (e) => { 63 | e.preventDefault(); 64 | this.render(location.pathname, true); 65 | } 66 | 67 | connectedCallback() { 68 | this.render(location.pathname); 69 | window.addEventListener('popstate', this.popstateHandler); 70 | } 71 | 72 | disconnectedCallback() { 73 | window.removeEventListener('popstate', this.popstateHandler); 74 | } 75 | } 76 | 77 | customElements.define('app-router', Router); 78 | 79 | -------------------------------------------------------------------------------- /week16/lecture/client/src/test.js: -------------------------------------------------------------------------------- 1 | 2 | function logger(target, propertyKey, descriptor) { 3 | let value; 4 | Object.defineProperty(target, propertyKey, { 5 | set(newValue) { 6 | console.log(`new value for ${propertyKey} is ${newValue}`); 7 | value = newValue; 8 | }, 9 | get() { 10 | return value; 11 | } 12 | }); 13 | return target; 14 | } 15 | 16 | class Person { 17 | @logger name; 18 | constructor() { 19 | } 20 | } 21 | 22 | const ivan = new Person('Test'); 23 | 24 | ivan.name = 'Ivan'; 25 | -------------------------------------------------------------------------------- /week16/lecture/client/src/user-list.js: -------------------------------------------------------------------------------- 1 | import { environment } from './environments/environment'; 2 | import { html } from 'lit'; 3 | import { Component } from './decorators/component'; 4 | import { property } from './decorators/property'; 5 | 6 | 7 | const template = (context) => html` 8 | 13 |
14 | ${context.isLoading ? html`
Loading...
` : ''} 15 | ${context.isLoading ? '' : html`
    ${context?.users?.map(u => html`
  • ${u}
  • `)}
`} 16 |
17 | `; 18 | 19 | @Component({ template }) 20 | export class UserList extends HTMLElement { 21 | @property isLoading; 22 | @property users; 23 | 24 | constructor() { 25 | super(); 26 | this.isRenderScheduled = false; 27 | this.isLoading = false; 28 | this.users = []; 29 | } 30 | 31 | loadUsers() { 32 | this.isLoading = true; 33 | fetch(environment.apiURL + '/users', { credentials: 'include' }) 34 | .then(res => res.ok ? res.json() : Promise.reject(new Error('Error loading users'))) 35 | .then(users => this.users = users) 36 | .catch(err => console.error(err)) 37 | .finally(() => this.isLoading = false); 38 | } 39 | 40 | connectedCallback() { 41 | this.loadUsers(); 42 | } 43 | } 44 | 45 | customElements.define('app-user-list', UserList); 46 | -------------------------------------------------------------------------------- /week16/lecture/client/webpack.config.js: -------------------------------------------------------------------------------- 1 | // Generated using webpack-cli https://github.com/webpack/webpack-cli 2 | 3 | const path = require('path'); 4 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 5 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 6 | 7 | const isProduction = process.env.NODE_ENV == 'production'; 8 | const stylesHandler = MiniCssExtractPlugin.loader; 9 | 10 | const config = { 11 | entry: './src/main.js', 12 | output: { 13 | path: path.resolve(__dirname, 'dist'), 14 | }, 15 | devServer: { 16 | open: true, 17 | host: 'localhost', 18 | historyApiFallback: true, 19 | }, 20 | plugins: [ 21 | new HtmlWebpackPlugin({ 22 | template: 'index.html', 23 | }), 24 | 25 | new MiniCssExtractPlugin(), 26 | 27 | // Add your plugins here 28 | // Learn more about plugins from https://webpack.js.org/configuration/plugins/ 29 | ], 30 | module: { 31 | rules: [ 32 | { 33 | test: /\.m?js$/, 34 | exclude: /node_modules/, 35 | use: { 36 | loader: 'babel-loader', 37 | options: { 38 | presets: [ 39 | ['@babel/preset-env', { targets: "defaults" }] 40 | ], 41 | plugins: [ 42 | ["@babel/plugin-proposal-decorators", { legacy: true }], 43 | ["@babel/plugin-proposal-class-properties", { loose: true }] 44 | ] 45 | } 46 | } 47 | }, 48 | { 49 | test: /environment.js/, 50 | use: { 51 | loader: path.resolve(__dirname, 'loaders', 'environment-replacer') 52 | } 53 | }, 54 | { 55 | test: /\.css$/i, 56 | use: [stylesHandler, 'css-loader'], 57 | }, 58 | { 59 | test: /\.(eot|svg|ttf|woff|woff2|png|jpg|gif)$/i, 60 | type: 'asset', 61 | }, 62 | 63 | // Add your rules for custom modules here 64 | // Learn more about loaders from https://webpack.js.org/loaders/ 65 | ], 66 | }, 67 | }; 68 | 69 | module.exports = () => { 70 | if (isProduction) { 71 | config.mode = 'production'; 72 | 73 | 74 | } else { 75 | config.mode = 'development'; 76 | } 77 | return config; 78 | }; 79 | -------------------------------------------------------------------------------- /week16/lecture/server/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /week16/lecture/server/constants.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | ACCESS_COOKIE_NAME: 'access-cookie', 3 | ACCESS_HEADER_NAME: 'access-token' 4 | }; 5 | 6 | -------------------------------------------------------------------------------- /week16/lecture/server/index.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const cors = require('cors'); 3 | const path = require('path'); 4 | const cookieParser = require('cookie-parser'); 5 | // const session = require('express-session'); 6 | const userRouter = require('./user-router.js'); 7 | 8 | const corsOptions = { 9 | origin: 'http://localhost:8080', 10 | credentials: true, 11 | optionsSuccessStatus: 200 // some legacy browsers (IE11, various SmartTVs) choke on 204 12 | }; 13 | 14 | 15 | const app = express(); 16 | 17 | // app.use(session({ 18 | // secret: 'keyboard cat', 19 | // resave: true, 20 | // saveUninitialized: true, 21 | // cookie: { httpOnly: true, maxAge: 60000 } 22 | // })); 23 | app.use(cookieParser()); 24 | app.use(cors(corsOptions)); 25 | app.use(express.json()); 26 | app.use(express.static(path.resolve(__dirname, '../client/dist'))); 27 | app.use('/users', userRouter); 28 | 29 | app.get('/worker.js', (req, res) => { 30 | res.sendFile(path.resolve(__dirname, './worker.js')) 31 | }); 32 | 33 | app.get('*', (req, res) => { 34 | res.sendFile(path.resolve(__dirname, '../client/dist/index.html')); 35 | }); 36 | 37 | app.listen(8081, () => { 38 | console.log('Server is listening on :8081'); 39 | }); -------------------------------------------------------------------------------- /week16/lecture/server/jwt.js: -------------------------------------------------------------------------------- 1 | var jwt = require('jsonwebtoken'); 2 | const secret = 'Very secret'; 3 | 4 | module.exports.sign = function (data) { 5 | return jwt.sign({ data: JSON.stringify(data), exp: Math.floor(Date.now() / 1000) + (60 * 60) }, secret); 6 | }; 7 | 8 | module.exports.verify = function (token, cb) { 9 | jwt.verify(token, secret, function (err, decoded) { 10 | if (err) { return void cb(err); } 11 | cb(null, decoded); 12 | }); 13 | }; 14 | -------------------------------------------------------------------------------- /week16/lecture/server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lecture", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "node ." 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "cookie-parser": "^1.4.6", 15 | "cors": "^2.8.5", 16 | "ejs": "^3.1.8", 17 | "express": "^4.18.2", 18 | "express-session": "^1.17.3", 19 | "jsonwebtoken": "^9.0.0" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /week16/lecture/server/user-router.js: -------------------------------------------------------------------------------- 1 | const router = new require('express').Router(); 2 | const jwt = require('./jwt'); 3 | const { ACCESS_HEADER_NAME, ACCESS_COOKIE_NAME } = require('./constants'); 4 | const users = ['Gosho', 'Pesho', 'Ivan']; 5 | const tokenBlacklist = []; 6 | const auth = [{ 7 | email: 'test@best.com', 8 | name: "Test User", 9 | password: '123' 10 | }]; 11 | 12 | 13 | // function isAuth(req, res, next) { 14 | // const user = req.session.user; 15 | // if (!user) { return void res.status(401).send({ errorCode: 'USER_NOT_FOUND' }); } // TODO move to global error handler and use next here 16 | // next(); 17 | // } 18 | 19 | function isAuth(req, res, next) { 20 | const token = req.cookies[ACCESS_COOKIE_NAME] || req.headers[ACCESS_HEADER_NAME]; 21 | if (!token || tokenBlacklist.includes(token)) { return void res.status(401).send({ errorCode: 'USER_NOT_FOUND' }); } // TODO move to global error handler and use next here 22 | jwt.verify(token, (err, decoded) => { 23 | const user = JSON.parse(decoded.data); 24 | if (err || !user) { return void res.status(401).send({ errorCode: 'USER_NOT_FOUND' }); } // TODO move to global error handler and use next here 25 | req.user = user; 26 | next(); 27 | }); 28 | } 29 | 30 | router.get('/', isAuth, (req, res) => { 31 | 32 | if (req.query['action'] === 'logout') { 33 | // req.session.destroy(); 34 | const token = req.cookies[ACCESS_COOKIE_NAME] || req.headers[ACCESS_HEADER_NAME]; 35 | tokenBlacklist.push(token); 36 | res.clearCookie(ACCESS_COOKIE_NAME); 37 | return void res.end(); 38 | } 39 | 40 | if (req.query['action'] === 'auth') { 41 | // const user = req.session.user; 42 | const user = req.user; 43 | const { password: _, ...data } = user; 44 | return void res.send(data); 45 | } 46 | 47 | res.send(users); 48 | }); 49 | 50 | router.post('/', (req, res, next) => { 51 | 52 | if (req.query['action'] === 'login') { 53 | const { email, password } = req.body; 54 | const user = auth.find(u => u.email === email && u.password === password); 55 | if (!user) { return void res.status(401).send({ errorCode: 'USER_NOT_FOUND' }); } // TODO move to global error handler and use next here 56 | // req.session.user = user; 57 | const { password: _, ...data } = user; 58 | const token = jwt.sign(data); 59 | res.cookie(ACCESS_COOKIE_NAME, token, { httpOnly: true }); 60 | return void res.send({ user: data, token }); 61 | } 62 | 63 | isAuth(req, res, (err) => { 64 | if (err) { return void next(err); } 65 | const newUserName = req.body.username; 66 | users.push(newUserName); 67 | res.send(users); 68 | }); 69 | 70 | }); 71 | 72 | 73 | router.get('/:id', isAuth, (req, res) => { 74 | const id = req.params.id; 75 | res.send(users[id]); 76 | }); 77 | 78 | router.put('/:id', isAuth, (req, res) => { 79 | const index = req.params.id; 80 | const newUserName = req.body.username; 81 | users[index] = newUserName; 82 | res.send(users); 83 | }); 84 | 85 | router.delete('/:id', isAuth, (req, res) => { 86 | const index = req.params.id; 87 | delete users[id]; 88 | res.send(users); 89 | }); 90 | 91 | module.exports = router; -------------------------------------------------------------------------------- /week16/lecture/server/worker.js: -------------------------------------------------------------------------------- 1 | console.log('worker is working!'); 2 | let cond = false; 3 | let counter = 0; 4 | 5 | function asyncLoop(fn, cb) { 6 | setTimeout(() => { 7 | fn(); 8 | if (cond) { return asyncLoop(fn, cb); } 9 | cb(); 10 | }, 0); 11 | } 12 | 13 | self.onmessage = function (message) { 14 | if (message.data === 'start') { 15 | if (cond) { return; } 16 | cond = true; 17 | asyncLoop(() => counter++, () => self.postMessage(counter)); 18 | } 19 | if (message.data === 'stop') { 20 | cond = false; 21 | } 22 | } -------------------------------------------------------------------------------- /week2/exercise/compose.js: -------------------------------------------------------------------------------- 1 | function compose() { 2 | var fns = Array.prototype.slice.call(arguments); 3 | return function () { 4 | var args = Array.prototype.slice.call(arguments); 5 | return fns.reduceRight(function (acc, curr, idx) { 6 | return curr.apply(undefined, idx === fns.length - 1 ? acc : [acc]); 7 | }, args); 8 | } 9 | } 10 | 11 | function sum(a, b) { return a + b; } 12 | function multiplyBy3(a) { return a * 3; } 13 | 14 | var addMultiply = compose(multiplyBy3, sum); 15 | console.log(addMultiply(10, 29)); -------------------------------------------------------------------------------- /week2/exercise/curry.js: -------------------------------------------------------------------------------- 1 | function curry(fn) { 2 | return function helper() { 3 | var arity = fn.length; 4 | var args = [].slice.call(arguments); 5 | if (args.length === arity) { 6 | return fn.apply(undefined, args); 7 | } 8 | return function () { 9 | var allArgs = args.concat([].slice.call(arguments)); 10 | return helper.apply(undefined, allArgs); 11 | } 12 | } 13 | } 14 | 15 | function sum(a, b) { return a + b; } 16 | function sum4(a, b, c, d) { return sum(sum(a, b), sum(c, d)); } 17 | 18 | console.log(curry(sum)(1)(3)); 19 | console.log(curry(sum4)(1)()(3, 4)(5)); 20 | 21 | // some extra `bind` magic 22 | var sum4With1and2AsBoundArguments = sum4.bind(undefined, 1, 2); 23 | console.log(sum4With1and2AsBoundArguments(3,4)); -------------------------------------------------------------------------------- /week2/exercise/memoize.js: -------------------------------------------------------------------------------- 1 | function memoize(func) { 2 | var cache = {}; 3 | return function () { 4 | var key = JSON.stringify(arguments); 5 | var result = cache[key]; 6 | if (!result) { 7 | result = func.apply(undefined, arguments); 8 | cache[key] = result; 9 | } 10 | return result; 11 | }; 12 | } -------------------------------------------------------------------------------- /week2/exercise/readme.md: -------------------------------------------------------------------------------- 1 | # Упражнение 2 2 | ## 17.10.22 3 | 4 | 1. Напишете фунцкия `memoize`, която запаметява изпълнените до момента резултати на дадена функция, в зависимост от подадените аргументи. Т.е. ако при подаване на същите аргументи, тя директно връща резултат. 5 | 6 | Пример: 7 | 8 | ```js 9 | var sum = function (x, y) { return x + y; } 10 | var memSum = memoize(sum); 11 | console.log(memSum(2,3)); // пресмята, връща 5 12 | console.log(memSum(3,3)); // пресмята, връща 6 13 | console.log(memSum(2,3)); // директно връща 5 без да смята 14 | ``` 15 | 16 | 2. Напишете функция `curry`, която взима дадена функция f като аргумент и ни връща нова функция, чрез която частично можем да прилагаме f. 17 | 18 | Пример: 19 | 20 | ```js 21 | function trippleAdd(a, b, c) { 22 | return a + b + c; 23 | } 24 | 25 | cTrippleAdd = curry(trippleAdd); 26 | 27 | console.log(cTrippleAdd(1)(2)(3)); //6 28 | console.log(cTrippleAdd(1, 2)(3)); //6 29 | console.log(cTrippleAdd(1, 2, 3)); //6 30 | ``` 31 | 32 | 3. Напишете функция `compose` която ни прави композиция от n на брой функции. 33 | 34 | Пример: 35 | 36 | ```js 37 | var addOne = (x) => x + 1; 38 | var sqrt = (x) => x * x; 39 | var log = (x) => console.log(x); 40 | 41 | addOneSqrtAndPrint = compose(log, sqrt, addOne); 42 | 43 | addOneSqrtAndPrint(1); // 4 44 | ``` 45 | 46 | Бонус: 47 | 48 | 4. Напишете функция, която по подаден списък и фунцкия за сравнение, връща най-малкият елемент 49 | 5. Напишете функция, която по подаден списък и фунцкия за проверка, разделя подадения масив на две групи в зависимост от резултата на подадената функция 50 | 6. Напишете функция, която по подаден списък и фунцкия за сравнение, филтрира не-уникалните елементи 51 | -------------------------------------------------------------------------------- /week2/lecture/index.js: -------------------------------------------------------------------------------- 1 | function archiverFactory() { 2 | var archive = []; 3 | var number = 4; 4 | var temperature = null; 5 | 6 | var obj = { 7 | temp: null, 8 | getArchive: function () { 9 | // scope - the values that can be used inside the function body 10 | // we can use values from upper scopes but can't use values from inner scopes 11 | return archive.slice(); 12 | }, 13 | getNumber: function () { 14 | return number; 15 | } 16 | }; 17 | 18 | 19 | Object.defineProperty(obj, 'temp', { 20 | get: function () { // closure - a function that uses a value from outer scope 21 | return temperature; 22 | }, 23 | set: function (newValue) { 24 | temperature = newValue; 25 | archive.push(newValue); 26 | } 27 | }); 28 | 29 | return obj; 30 | } 31 | 32 | var obj = archiverFactory(); 33 | console.log(obj.getArchive()); 34 | console.log(obj.temp); 35 | 36 | obj.temp = 4; 37 | obj.temp = 5; 38 | 39 | var archive = obj.getArchive(); 40 | archive.push(4, 5, 6); 41 | console.log(obj.getArchive()); 42 | 43 | // var result = false || {}; 44 | 45 | // if (true) { 46 | 47 | // } else { 48 | 49 | // } 50 | 51 | // function test(arg1) { 52 | // arg1 = arg1 || null; 53 | // return arg1; 54 | // } 55 | 56 | // function createPerson(name) { 57 | // name = name || 'Ivan Ivanov'; 58 | // return { name: name }; 59 | // } 60 | 61 | function createSomething(logPersonName) { 62 | return { 63 | name: 'Test', 64 | logPersonName: logPersonName 65 | }; 66 | } 67 | 68 | function logName() { 69 | console.log(this.name); 70 | } 71 | 72 | var user = createPerson(); 73 | user.logName = logName; 74 | // user.logName(); 75 | 76 | var result = createSomething(user.logName.bind(user)); 77 | result.logPersonName(); 78 | 79 | 80 | 81 | // function test(arg1, arg2, arg3) { 82 | // console.log(this, arg1, arg2, arg3); 83 | // } 84 | 85 | // var fn = test.bind(1000, 1, 2); 86 | 87 | // test.call(1000, 1, 2, 3); 88 | // test.apply(1000, [1, 2, 3]); 89 | 90 | // var obj2 = { 91 | // test1: fn 92 | // }; 93 | 94 | // obj2.test1(100, 200, 300); 95 | 96 | function test() { 97 | var args = [].slice.call(arguments); 98 | console.log(args, args instanceof Array); 99 | } 100 | 101 | test(1, 2, 3, 4); 102 | test.length; 103 | 104 | // var testCr = curry(test); 105 | // testCr(1)(2)(3)(4) - this is currying - https://en.wikipedia.org/wiki/Currying; 106 | // testCr(1)(2,3)(4); 107 | 108 | function compose() { 109 | var fns = [].slice.call(arguments); 110 | return function (x) { 111 | fns.forEach(function (fn, index, fns) { 112 | x = fn(x); 113 | }); 114 | // fns.map(function(fn) { 115 | // x = fn(x) 116 | // return ; 117 | // }); 118 | // for (var i = 0; i < fns.length; i++) { 119 | // x = fns[i](x); 120 | // } 121 | return x; 122 | }; 123 | } 124 | 125 | 126 | 127 | function createEmployeeFactory(salary) { 128 | return function createEmployee(person) { 129 | person.salary = salary || 2000; 130 | return person; 131 | }; 132 | } 133 | 134 | var createUserEmployeeWith4000Salary = compose( 135 | createPerson, 136 | createEmployeeFactory(4000) 137 | ); 138 | 139 | console.log(createUserEmployeeWith4000Salary('Ivan')); 140 | 141 | 142 | // var fn = compose(function (a) { return a + 1 }, function (a) { return a * a; }); 143 | // fn(1); 144 | 145 | 146 | [1, 2, 3, 4].reduce(function (acc, currElement) { 147 | return acc + currElement; 148 | }, 0); 149 | 150 | 151 | var res = [1, 2, 3, 4, 5].reduce(function (acc, curr) { 152 | return acc.concat(curr + 2); 153 | }, []); 154 | 155 | console.log(res); 156 | 157 | var res2 = [['prop1', 1], ['prop2', 2], ['prop3', 3], ['prop4', 4]] 158 | .reduce(function (acc, curr) { 159 | var key = curr[0]; 160 | var value = curr[1]; 161 | acc[key] = value; 162 | return acc; 163 | }, {}); 164 | 165 | console.log(res2); 166 | 167 | [1, 2, 3, 4] 168 | .map(function (x) { return x + 2; }).map(function (x) { return x * 2; }); 169 | 170 | 171 | function mixin(obj1, obj2) { 172 | var result = {}; 173 | for (var prop1 in obj1) { 174 | result[prop1] = obj1[prop1]; 175 | } 176 | for (var prop2 in obj2) { 177 | result[prop2] = obj2[prop2]; 178 | } 179 | return result; 180 | } 181 | 182 | var prototype = { 183 | getName: function () { 184 | // context - the value of this 185 | return this.name; 186 | }, 187 | getAge: function () { 188 | return this.age; 189 | } 190 | }; 191 | 192 | function createAnimal(name, age, breed) { 193 | var animalWithPrototype = Object.create(prototype); 194 | animalWithPrototype.name = name; 195 | animalWithPrototype.age = age; 196 | animalWithPrototype.breed = breed; 197 | 198 | return animalWithPrototype; 199 | } 200 | 201 | var a = createAnimal('a', 10, 'test'); 202 | a.getAge(); 203 | 204 | function createPerson(name, age) { 205 | var personWithPrototype = Object.create(prototype); 206 | personWithPrototype.name = name; 207 | personWithPrototype.age = age; 208 | 209 | return person; 210 | } 211 | 212 | var a = createPerson('a', 10); 213 | a.getAge(); -------------------------------------------------------------------------------- /week3/exercise/readme.md: -------------------------------------------------------------------------------- 1 | # Упражнение 2 2 | 3 | ## 26.10.22 4 | 5 | 1. Създайте конструтор функция `Point`, която получва два аргумента: `x` и `y`, описващи позицията на точката в пространството. 6 | Да се реализират методите: 7 | * `getDistance(point2)` - намира разтоянието между нашата точка и `point2` 8 | 1. Използвайки класическо прототипно наследяване направете конструктор функция `Circle`, която наследява функционалността на `Point`. Освен координатите (които ще определят центъра на кръга), ще има и аргумент r`, определящ радиус на кръга. 9 | 10 | Да се реализират и методите: 11 | * `getCircumference()` - връща обиколката на кръга 12 | * `getArea()` - връща лицето на кръга 13 | * `intersects(circle2)` - проверява дали нашият кръг има сечение с подадения circle2 14 | 1. Използвайки класическо прототипно наследяване направете конструктор функция `Rectangle`, която наследява функционалността на `Point`. Освен координатите (които ще определят долният ляв ъгъл на правоъгълника), ще има и аргументи `a` и `b`, определящи двете дължини на страни на правоъгълника. 15 | 16 | Да се реализират и методите: 17 | * `getPerimeter()` - връща периметъра на правоъгълника 18 | * `getArea()` - връща лицето на правоъгълника 19 | * `getLengthOfDiagonals()` - връща масив от дължините на двата диагонала 20 | * `getBiggestCircle()` - връща кръга с най-голям възможен радиус и с начало центъра на правоъгълника 21 | 22 | 1. Използвайки класическо прототипно наследяване направете конструктор функция `RectanglePrism`, която наследява функционалността на `Rectangle`. Освен входните данни на `Rectangle`, приема също и `c`, което описва третата стена на правоъгълната призма. 23 | 24 | Да се реализират и методите: 25 | * `getVolume()` - връща обема на правоъгълната призма -------------------------------------------------------------------------------- /week3/lecture/example.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | 4 | class Cls { 5 | unsigned long int a, b; 6 | 7 | public: 8 | 9 | Cls() { } 10 | Cls(int _a, int _b) : a(_a), b(_b) { 11 | 12 | } 13 | 14 | // getters 15 | int geta() { return a; } 16 | int getb() { return b; } 17 | 18 | void method() { 19 | printf("%lu %lu \n", a, b); // operates directly on the this pointer! 20 | printf("%lu \n", (unsigned long)this); // &variable 21 | } 22 | 23 | virtual void print() { 24 | printf("printing from ClsA"); 25 | }; 26 | }; 27 | 28 | class ClsB : public Cls { 29 | int c; 30 | virtual void print() { 31 | printf("!!! printing from ClsB \n\n"); 32 | }; 33 | }; 34 | 35 | // method resolution order 36 | // DFS - depth first search 37 | 38 | Cls variable(100, 200); 39 | // Cls* rvar = &variable; // 0xfe8e4800 40 | 41 | void Cls_method(Cls& __this) { 42 | printf("%u %u \n", __this.geta(), __this.getb()); 43 | printf("%lu \n", (unsigned long)(void*)&__this); // &variable 44 | } 45 | 46 | // Cls_method2, Cls_method_3 47 | 48 | int main() { 49 | variable.method(); 50 | // auto vptr = Cls::method; 51 | // vptr(variable); 52 | Cls_method(variable); 53 | 54 | printf("%lu is the address of Cls_method", (unsigned long) &Cls_method); 55 | ClsB var; 56 | Cls* ptr = &var; 57 | ptr->print(); 58 | } 59 | 60 | // ----- Cls instance 61 | // 4 bytes -> int a 62 | // 4 bytes -> int b 63 | // virtual table [] 64 | // ----- Cls2 instance 65 | // 4 bytes -> int a 66 | // 4 bytes -> int b 67 | // 4 bytes -> int c 68 | // virtual table [] 69 | // ----- 70 | 71 | -------------------------------------------------------------------------------- /week3/lecture/index.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | var obj = { 4 | prop1: 10, 5 | prop2: 20, 6 | f:function() { 7 | console.log("properties? : ", this.prop1, this.prop2) 8 | console.log("i was called in this context: ", this) 9 | } 10 | } 11 | 12 | // console.log(obj.f); 13 | // obj.f.call(); 14 | obj.f.call(obj); 15 | // obj.f.call('baba') 16 | 17 | function f() { 18 | var [a,b,c] = [1,2,3]; // destructuring 19 | function g() { 20 | console.log(a, b, c) 21 | } 22 | g(); 23 | } 24 | 25 | f(); 26 | 27 | 28 | // функцията се извиква в някакъв контекст! 29 | // по подразбиране това е global или текущия контектс 30 | 31 | // каква е разликата между изпълнението на : 32 | // obj.f() и obj.call.f() 33 | // ?? 34 | 35 | obj.__proto__ = { 36 | omg: "here", 37 | val: 1000 38 | } 39 | 40 | obj.__proto__.__proto__ = { 41 | wow: "😱", bow: "🐶" } 42 | 43 | // в С++ наследяването се описва от родител към наследници 44 | // в JavaScript наследявнето на практика се описва от наследници към предци! 45 | // obj -> obj.__proto__ -> obj.__proto__.__proto__ 46 | 47 | // в JavaScript няма такова нещо като множествено наследяване! 48 | 49 | function Food(_type, _weight, _amount) { 50 | // var type = _type; 51 | this.type = _type; 52 | this.weight = _weight; 53 | this.amount = _amount; 54 | 55 | return this; 56 | } 57 | 58 | Food.prototype.print = function() { 59 | // console.log(this.type, this.weight, this.amount, ) 60 | var self = this; 61 | console.log( 62 | Object.getOwnPropertyNames(self) 63 | .map(function (e) { 64 | return [ e, ":", self[e] ].join('') }) 65 | .join(',')); 66 | } 67 | 68 | Food.prototype.totalWeight = function() { 69 | console.log("total weight is:", this.weight * this.amount) 70 | } 71 | 72 | function Fish( _weight, _amount ) { 73 | // this.type = 'fish' 74 | // this.weight = _weight; 75 | // this.amount = _amount; 76 | return Food.call(this, 'fish', _weight, _amount) 77 | } 78 | 79 | Fish.prototype = Object.create(Food.prototype) 80 | // Fish.prototype.__proto__ = Food.prototype; 81 | 82 | Fish.prototype.print = function() { 83 | console.log("imma fish, eye don print!"); 84 | } 85 | 86 | var mycatch = new Food('Mahi Mahi', 2400, 1); 87 | mycatch.print(); 88 | 89 | var myfish = new Fish(1200, 3); 90 | myfish.print() // apply a method from own constructor's prototype 91 | myfish.totalWeight(); // apply a method from 'parent' (inherited)'s prototype 92 | 93 | console.log(Object.getOwnPropertyNames(myfish)); 94 | 95 | // същото като 96 | // mycatch.__proto__.print.call(mycatch) 97 | 98 | // var weird__proto = Object.create(Food.prototype); 99 | // var weird = Food.call(weird__proto, 'Mahi Mahi', 2400, 1) 100 | // weird.print() 101 | 102 | // mixins 103 | // horizontal method resolution -- not 104 | // Food Person 105 | // | | 106 | // Fish --- SwimMixin --- Swimmer 107 | 108 | // We are just adding the specific set of methods to the given "class" 109 | // ER Modelling 110 | 111 | 112 | var swimMixin = { 113 | swimUp: function() { 114 | // this --> the instance of the "class" this mixin has extended 115 | console.log(this.amount + 'fish will swim up'); 116 | debugger; 117 | }, 118 | swimDown: function() { 119 | console.log(this.amount + 'fish will swim down'); 120 | } 121 | } 122 | 123 | Object.assign(Fish.prototype, swimMixin); 124 | 125 | var swimmingFish = new Fish(1200, 3) 126 | 127 | swimmingFish.swimUp(); 128 | 129 | debugger; -------------------------------------------------------------------------------- /week4/exercise/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FMIjs/advanced-javascript-2022-2023/98f8cabb830dbfd82edd0ef1686b049f15be968d/week4/exercise/README.md -------------------------------------------------------------------------------- /week4/lecture/intro-to-es6.js: -------------------------------------------------------------------------------- 1 | // const / let 2 | 3 | console.log(a); // undefined // Variable with var initialization gets *hoisted* 4 | // console.log(b); // ! Uncaught ReferenceError ReferenceError: Cannot access 'b' before initialization 5 | var a = 5; 6 | let b = 6; 7 | const c = 7; 8 | // c = 8; // ! Uncaught TypeError TypeError: Assignment to constant variable. 9 | // The 'const' is constant only constant for the given *reference* 10 | // We can change the value, but not the reference (we can't say x = y) 11 | 12 | const arr = [1,2,3]; 13 | arr.push(4); 14 | // arr.pop(); 15 | console.log(arr); 16 | 17 | const obj = {}; 18 | obj['1'] = 1 19 | obj['a'] = 'a'; 20 | 21 | console.log(obj); 22 | 23 | 24 | // function vs arrow function 25 | 26 | // arrow function syntax 27 | 28 | function myFn(x) { 29 | return x + 5; 30 | } 31 | (x) => { return x + 5; } 32 | x => { return x + 5;} 33 | x => x + 5; 34 | x => ({ prop: x }); 35 | 36 | 37 | function add1(x) { 38 | return x + 1; 39 | } 40 | 41 | const arrowAdd1 = x => x + 1; 42 | 43 | // ... 44 | 45 | function SomeClass(fn) { 46 | this.someVariable = 5; 47 | this.fn = fn; 48 | this.fn(); 49 | } 50 | 51 | function someFunction() { 52 | // in this case the context is the one of SomeClass, meaning we have access to `someVariable` 53 | // we have the context where the function was called 54 | console.log(this); 55 | } 56 | const someArrowFunction = () => { 57 | // in this case the context is {}, meaning we have access to `someVariable` 58 | console.log(this); 59 | } 60 | const someClassObj = new SomeClass(someFunction); 61 | const someClassArrowObj = new SomeClass(someArrowFunction); 62 | 63 | 64 | // setTimeout(function() { 65 | // console.log('timeout'); // this gets called after *at least* 100ms 66 | // }, 100); 67 | 68 | function callMeAsync(fn, interval) { 69 | setTimeout(fn, interval); 70 | console.log('---'); 71 | } 72 | 73 | // callMeAsync(someFunction, 1000); 74 | // callMeAsync(someArrowFunction, 1000); 75 | // callMeAsync(someClassObj.fn, 1000); 76 | // callMeAsync(someClassArrowObj.fn, 1000); 77 | 78 | SomeClass.prototype.asyncExec = function() { 79 | callMeAsync(this.fn); 80 | } 81 | 82 | // someClassObj.asyncExec(); 83 | // someClassArrowObj.asyncExec(); 84 | 85 | // function test3() { 86 | // const arrow = () => { 87 | // console.log(this.var); 88 | // } 89 | 90 | // const self = this; 91 | // function internalNormalFunction() { 92 | // console.log(self.var); 93 | // console.log(this.var); 94 | // } 95 | // // arrow(); // ==> 42 96 | // // setTimeout(arrow, 100) // ==> 42 97 | 98 | // // internalNormalFunction(); // ==> 42 / undefined 99 | // // setTimeout(internalNormalFunction, 100); // ==> 42 / undefined 100 | 101 | // internalNormalFunction.bind(this)(); // ==> 42 / 42 102 | // setTimeout(internalNormalFunction.bind(this), 100); // ==> 42 / 42 103 | // } 104 | 105 | // test3.call({ var: 42 }); 106 | 107 | function MySecondClass() { 108 | this.var = 5; 109 | 110 | this.fn = function() { 111 | console.log(this.var); 112 | } 113 | this.arrowFn = () => { 114 | console.log(this.var); 115 | } 116 | } 117 | 118 | // const someSecondClassInstance = new MySecondClass(); 119 | // secondClassInstance.fn(); // ==> 5 120 | // secondClassInstance.arrowFn(); // ==> 5 121 | 122 | function letsBreakTheSecondClass() { 123 | this.var = 6; 124 | 125 | // const secondClassInstance = new MySecondClass(); 126 | // secondClassInstance.fn(); // ==> 5 127 | // secondClassInstance.arrowFn(); // ==> 5 128 | 129 | this.innerSecondClassInstance = new MySecondClass(); 130 | this.innerSecondClassInstance.fn.bind(this)(); // ==> 6 131 | this.innerSecondClassInstance.fn(); // ==> 5 132 | this.innerSecondClassInstance.arrowFn(); // ==> 5 133 | } 134 | 135 | // letsBreakTheSecondClass.bind({ var2: 7 })(); 136 | 137 | 138 | // Class 139 | 140 | function PersonFn(name, age) { 141 | this.name = name; 142 | this.age = age; 143 | } 144 | 145 | PersonFn.prototype.speak = function() { 146 | console.log('*woof*'); 147 | } 148 | 149 | class Person { 150 | someValue = 5; 151 | static numberOfCreated = 0; 152 | 153 | constructor(name, age) { 154 | this.name = name; 155 | this.age = age; 156 | 157 | Person.numberOfCreated++; 158 | } 159 | 160 | speak = function () { 161 | console.log('*woof*') 162 | } 163 | } 164 | 165 | const ivan = new Person('Ivan', 20); 166 | // const fnIvan = new PersonFn('Ivan', 20); 167 | 168 | // console.log(ivan.name, ivan.age); 169 | // console.log(fnIvan.name, fnIvan.age); 170 | 171 | 172 | // console.log(ivan.numberOfCreated); 173 | // console.log(Person.numberOfCreated); 174 | 175 | const ivan2 = new Person('Ivan2', 30); 176 | // console.log(Person.numberOfCreated); 177 | 178 | class Employee extends Person { 179 | job; 180 | constructor(name, age, job) { 181 | super(name, age); 182 | this.job = job; 183 | } 184 | } 185 | 186 | const pesho = new Employee('ivan3', 20, 'dev'); 187 | console.log(pesho); 188 | 189 | 190 | // Symbol 191 | 192 | const someSymbol = Symbol('ASD'); 193 | 194 | const someObj = { 195 | 1: 1, 196 | 'a': 'a', 197 | someSymbol: `my key is the string 'someSymbol'`, 198 | [someSymbol]: `my key is the symbol 'someSymbol'` 199 | }; 200 | 201 | console.log(someObj); 202 | 203 | // Different Collections 204 | 205 | // Map -- key/value pair 206 | 207 | const myMap = new Map(); 208 | myMap.set('a', 1); 209 | myMap.set(1, 1); 210 | 211 | myMap.has(1); // ==> true 212 | myMap.has(2); // ==> false 213 | 214 | myMap.get('a'); // ==> 1 215 | myMap.delete('a'); // ==> true, since the element was deleted successfully 216 | myMap.has('a'); // ==> false 217 | myMap.get('a'); // ==> undefined 218 | myMap.delete('a'); // ==> false, since the element was not present in the map 219 | 220 | myMap.set({ prop: 1 }, 1); 221 | 222 | 223 | myMap.clear 224 | // WeakMap 225 | 226 | const myWeakMap = new WeakMap(); 227 | let john = { prop: 1 }; 228 | myWeakMap.set(john, 'doe'); 229 | console.log(myWeakMap.get(john)); 230 | john = null; 231 | console.log(myWeakMap.get(john)); 232 | 233 | john = { prop: 1 }; 234 | myMap.set(john, 'doe'); 235 | console.log(myMap.get(john)); 236 | john = null; 237 | console.log(myMap.get(john)); 238 | 239 | // use cases: 240 | // - additional data 241 | // - caching 242 | // - dropping unnecessary references --> doesn't increase the ref count 243 | 244 | 245 | // Set 246 | const mySet = new Set(); 247 | 248 | john = { name: 'John' }; 249 | mySet.add(1); 250 | mySet.has(1); // ==> true 251 | mySet.add(john); 252 | mySet.add('john').add('doe'); 253 | 254 | console.log(mySet.add('ivan')); 255 | console.log(mySet.add('ivan')); 256 | 257 | mySet.clear(); 258 | 259 | const arr2 = [1,2,3,3,3,4,5]; 260 | const uniqueSet = arr2.reduce((acc, curr) => acc.add(curr), new Set()).values(); 261 | const uniqueArr2 = [...uniqueSet]; // 1, 2, 3, 4, 5; 262 | 263 | // WeakSet 264 | const myWeakSet = new WeakSet(); 265 | 266 | john = { name: 'John' }; 267 | myWeakSet.add(john); 268 | console.log(myWeakSet.has(john)); 269 | john = null; 270 | console.log(myWeakSet.has(john)); 271 | 272 | // String Interpolation 273 | john = { name: 'John', age: 20 }; 274 | console.log('Hi! My name is ' + john.name + ' and I am ' + john.age); 275 | console.log(`Hi! My name is ${john.name} and I am ${john.age}`); -------------------------------------------------------------------------------- /week5/exercise/readme.md: -------------------------------------------------------------------------------- 1 | # Упражнение 5 2 | 3 | ## 09.11.22 4 | 5 | 1. Създайте прост сървър, който по подаден URL извлича съдържанието на дадената страница и ги връща. 6 | [Примерен линк](https://www.reddit.com/.rss) 7 | За зареждане на данните използвайте [`http`](https://nodejs.org/api/http.html) модула. 8 | 9 | 2. Създайте базов сървър, който да слуша за заявки от тип `/load/:prop` и да връща `localJson[prop]`, като `localJson` е локален за сървъра обект. 10 | -------------------------------------------------------------------------------- /week5/exercise/task-1.js: -------------------------------------------------------------------------------- 1 | const https = require("https"); 2 | const http = require('http'); 3 | const url = require("url"); 4 | 5 | const hostname = '127.0.0.1'; 6 | const port = 3000; 7 | 8 | const server = http.createServer((req, res) => { 9 | const parsedUrl = url.parse(req.url, true); 10 | 11 | if (parsedUrl.pathname == "/load") { 12 | const requestedUrl = parsedUrl.query["url"]; 13 | if (typeof requestedUrl !== "string") { 14 | throw new Error("Invalid or empty url."); 15 | } 16 | 17 | const decodedUrl = decodeURIComponent(requestedUrl); 18 | 19 | // Solution #1 20 | https.get(decodedUrl, (externalResponse) => { 21 | res.writeHead(200); 22 | externalResponse.on('data', (chunk) => { 23 | res.write(chunk); 24 | }); 25 | externalResponse.on('end', () => { 26 | res.end(); 27 | }); 28 | }); 29 | 30 | // Solution #2 31 | // https.get(decodedUrl, (externalResponse) => { 32 | // externalResponse.pipe(res); 33 | // }); 34 | } 35 | }); 36 | 37 | server.listen(port, hostname, () => { 38 | console.log(`Server running at http://${hostname}:${port}/`); 39 | }); 40 | -------------------------------------------------------------------------------- /week5/exercise/task-2.js: -------------------------------------------------------------------------------- 1 | 2 | const http = require('http'); 3 | const url = require("url"); 4 | 5 | const hostname = '127.0.0.1'; 6 | const port = 3000; 7 | 8 | const localDb = { 9 | 'someProp': 'test' 10 | }; 11 | 12 | const server = http.createServer((req, res) => { 13 | const { pathname } = url.parse(req.url, true); 14 | 15 | const desiredProp = (pathname.match(/\/load\/([a-zA-Z]+)/) || [null, null])[1]; 16 | if (!desiredProp) { 17 | // problem 18 | } 19 | const result = localDb[desiredProp]; 20 | res.write(result || '') 21 | res.end(); 22 | }); 23 | 24 | server.listen(port, hostname, () => { 25 | console.log(`Server running at http://${hostname}:${port}/`); 26 | }); -------------------------------------------------------------------------------- /week5/lecture/fs.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const utils = require('./utils'); 3 | 4 | // const fileContent = fs.readFileSync('./index.js', 'utf8'); 5 | 6 | // console.log(fileContent); 7 | 8 | // function logCalc(a, b) { 9 | // a = a + b; 10 | // console.log(a); 11 | // return a; 12 | // } 13 | 14 | 15 | // function doSomething(flag) { 16 | // if (flag) { return void logCalc(1, 2); } 17 | // console.log('else'); 18 | // } 19 | 20 | // const res = doSomething(true); 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | function processFiles(content1, content2) { 29 | return content1 + content2; 30 | } 31 | 32 | // const obj = { 33 | // content1: null, 34 | // content2: null, 35 | // next() { 36 | // if (this.content1 === null || this.content2 === null) { return; } 37 | // processFiles(this.content1, this.content2); 38 | // } 39 | // } 40 | 41 | // function firstFileHandler(content) { 42 | // if (err) { 43 | // console.error(err); 44 | // return; 45 | // } 46 | // obj.content1 = content; 47 | // obj.next(); 48 | // } 49 | 50 | // const firstFileHandlerWithError = utils.contentwithErrorHandler(firstFileHandler); 51 | 52 | // fs.readFile('./index.js', 'utf-8', firstFileHandlerWithError); 53 | 54 | // fs.readFile('./index.js', 'utf-8', utils.contentwithErrorHandler(function (content) { 55 | // if (err) { 56 | // console.error(err); 57 | // return; 58 | // } 59 | // obj.content2 = content; 60 | // obj.next(); 61 | // })); 62 | 63 | 64 | 65 | 66 | 67 | fs.readFile('./index.js', 'utf-8', utils.withErrorHandler(function (content1) { 68 | if (err) { 69 | console.error(err); 70 | return; 71 | } 72 | fs.readFile('./index.js', 'utf-8', utils.withErrorHandler(function (content2) { 73 | if (err) { 74 | console.error(err); 75 | return; 76 | } 77 | const result = processFiles(content1, content2); 78 | fs.writeFile('./result', result, utils.withErrorHandler(function () { 79 | if (err) { 80 | console.error(err); 81 | return; 82 | } 83 | 84 | console.log('Success!'); 85 | })); 86 | })); 87 | })); 88 | 89 | function readFirstFile(err, content1) { 90 | if (err) { 91 | console.error(err); 92 | return; 93 | } 94 | fs.readFile('./index.js', 'utf-8', readSecondFile(content1)); 95 | } 96 | 97 | function readSecondFile(content1) { 98 | return function readSecondFileHelper(err, content2) { 99 | if (err) { 100 | console.error(err); 101 | return; 102 | } 103 | const result = processFiles(content1, content2); 104 | fs.writeFile('./result', result, finalStep); 105 | } 106 | } 107 | 108 | function finalStep(err) { 109 | if (err) { 110 | console.error(err); 111 | return; 112 | } 113 | 114 | console.log('Success!'); 115 | } 116 | 117 | fs.readFile('./index.js', 'utf-8', readFirstFile); -------------------------------------------------------------------------------- /week5/lecture/index.js: -------------------------------------------------------------------------------- 1 | // https://www.youtube.com/watch?v=cCOL7MC4Pl0&vl=en - EVENT LOOP EXPAINED 2 | 3 | function foo() { 4 | baz(); 5 | } 6 | 7 | function baz() { 8 | bar(); 9 | } 10 | 11 | function bar() { 12 | setImmediate(function () { 13 | console.log(3); 14 | }); 15 | setTimeout(function () { 16 | console.log(1); 17 | }, 0); 18 | } 19 | 20 | foo(); 21 | console.log(2); 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /week5/lecture/server.js: -------------------------------------------------------------------------------- 1 | const http = require('http'); 2 | const fs = require('fs'); 3 | 4 | const fileStream = fs.createReadStream('./index.js'); 5 | 6 | function handleGetRequest(req, res) { 7 | res.writeHead(200); 8 | fileStream.pipe(res); 9 | // res.write('HELLO WORLD 1'); 10 | 11 | // const interlId = setInterval(() => { 12 | // counter++; 13 | // res.write('HELLO WORLD ' + counter + '\n'); 14 | // if (counter === 5) { 15 | // clearInterval(interlId); 16 | // res.end(); 17 | // } 18 | // }, 3000); 19 | } 20 | 21 | 22 | 23 | function handlePostRequest(req, res) { 24 | 25 | } 26 | 27 | 28 | 29 | const sever = http.createServer(function (req, res) { 30 | const method = req.method.toUpperCase(); 31 | if (method === 'GET') { 32 | return void handleGetRequest(req, res); 33 | } 34 | if (method === 'POST') { 35 | return void handlePostRequest(req, res); 36 | } 37 | }); 38 | 39 | 40 | sever.listen(8080, () => { 41 | console.log('Server is listening :8080'); 42 | }); 43 | -------------------------------------------------------------------------------- /week5/lecture/test.js: -------------------------------------------------------------------------------- 1 | const toUpperCase = String.prototype.toUpperCase; 2 | const toUpperCaseCall = toUpperCase.call; 3 | const boundToUppercaseCall = toUpperCaseCall.bind(toUpperCase); 4 | console.log(['a', 'b', 'abc'].map(boundToUppercaseCall)); 5 | // console.log(['a', 'b', 'abc'].map(el => el.toUpperCase())); 6 | 7 | 8 | // let fn = String.prototype.toUpperCase; 9 | // let fn2 = fn.bind("aaa"); 10 | // console.log(fn2.call(null)); 11 | 12 | 13 | -------------------------------------------------------------------------------- /week5/lecture/utils.js: -------------------------------------------------------------------------------- 1 | // const lib = (function () { 2 | // const _exports = {}; 3 | // function withErrorHandler(fn) { 4 | // return function (err, result) { 5 | // if (err) { return console.error(err); } 6 | // fn(result); 7 | // }; 8 | // } 9 | // _exports.withErrorHandler = withErrorHandler; 10 | 11 | // return _exports; 12 | // })(); 13 | 14 | // lib.withErrorHandler(); 15 | 16 | // A -> B -> C -> A 17 | 18 | function doSomething() { 19 | 20 | } 21 | doSomething(); 22 | 23 | function withErrorHandler(fn) { 24 | return function (err, result) { 25 | if (err) { return console.error(err); } 26 | fn(result); 27 | }; 28 | } 29 | 30 | // module.exports = { 31 | // withErrorHandler 32 | // }; 33 | 34 | // module.exports = { 35 | // withErrorHandler() { 36 | 37 | // } 38 | // } 39 | 40 | module.exports.withErrorHandler = withErrorHandler; 41 | -------------------------------------------------------------------------------- /week6/lecture/custom-copy-cmd-streams.js: -------------------------------------------------------------------------------- 1 | const files = process.argv.slice(2); 2 | const fs = require('fs'); 3 | const path = require('path'); 4 | 5 | for (const file of files) { 6 | const fileExt = path.extname(file); 7 | const readStream = fs.createReadStream('./' + file); 8 | readStream.on('error', function () { 9 | console.error('Someting went wrong!'); 10 | }); 11 | const writeStream = fs.createWriteStream('./' + file.replace(fileExt, '') + ' (copy)' + fileExt); 12 | readStream.pipe(writeStream); 13 | } 14 | 15 | // readStream.on('data', (chunk) => { 16 | // console.log(chunk.toString()); 17 | // }); 18 | 19 | // readStream.pipe(writeStream); 20 | -------------------------------------------------------------------------------- /week6/lecture/event-emitter.js: -------------------------------------------------------------------------------- 1 | // function OldEventEmitter() { 2 | 3 | // } 4 | 5 | // OldEventEmitter.prototype.on = function () { 6 | 7 | // } 8 | 9 | // OldEventEmitter.prototype.emit = function () { 10 | 11 | // } 12 | 13 | // // super 14 | // function MyClass() { 15 | // OldEventEmitter.call(this) 16 | // } 17 | 18 | // // extends 19 | // MyClass.prototype = 20 | // Object.create(OldEventEmitter.prototype); 21 | 22 | 23 | 24 | 25 | 26 | class EventEmitter { 27 | constructor() { 28 | this.eventHandlers = {}; 29 | } 30 | 31 | on(eventName, cb) { 32 | this.eventHandlers[eventName] = 33 | (this.eventHandlers[eventName] || []).concat(cb); 34 | 35 | // const cbs = this.eventHandlers[eventName] || []; 36 | // cbs.push(cb); 37 | // this.eventHandlers[eventName] = cbs; 38 | 39 | return () => { 40 | this.eventHandlers[eventName] = 41 | (this.eventHandlers[eventName] || []).filter(fn => fn !== cb); 42 | }; 43 | } 44 | 45 | once(eventName, cb) { 46 | const unsub = this.on(eventName, (data) => { 47 | cb(data); 48 | unsub(); 49 | }); 50 | } 51 | 52 | emit(eventName, data) { 53 | (this.eventHandlers[eventName] || []) 54 | .forEach(cb => cb(data)); 55 | 56 | // const cbs = this.eventHandlers[eventName] || []; 57 | // cbs.forEach(cb => cb(data)); 58 | } 59 | 60 | 61 | } 62 | 63 | class MyClass extends EventEmitter { 64 | 65 | set name(newValue) { 66 | this._name = newValue; 67 | // setTimeout(() => { 68 | // this.emit('nameChange', this._name); 69 | // }); 70 | process.nextTick(() => { 71 | this.emit('nameChange', this._name); 72 | }); 73 | } 74 | get name() { 75 | return this._name; 76 | } 77 | 78 | constructor(name) { 79 | super(); 80 | this.name = name; 81 | } 82 | 83 | } 84 | 85 | setTimeout(() => { 86 | console.log(1); 87 | }); 88 | const myObj = new MyClass('TEST'); 89 | myObj.once('nameChange', (newName) => { 90 | console.log(newName); 91 | }); 92 | 93 | setTimeout(() => { 94 | myObj.name = 'HELLO!'; 95 | 96 | }, 3000); -------------------------------------------------------------------------------- /week6/lecture/streams.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const { pipeline, Readable, Writable, Transform } = require('stream'); 3 | 4 | // const readStream = fs.createReadStream('./test.txt', { 5 | // highWaterMark: 1, 6 | // }); 7 | // readStream.pause(); 8 | // const writeStream = fs.createWriteStream('./outout.txt'); 9 | 10 | 11 | 12 | function createAsyncStreamReader(readStream, cb, delay = 5000) { 13 | const lineArray = []; 14 | let currenLine = ''; 15 | return function asyncRead() { 16 | const chunk = readStream.read(); 17 | if (chunk === null) { 18 | if (currenLine) { lineArray.push(currenLine); } 19 | return void cb(lineArray); 20 | } 21 | const strChunk = chunk.toString(); 22 | if (strChunk === '\n') { 23 | lineArray.push(currenLine); 24 | currenLine = ''; 25 | } else { 26 | currenLine += strChunk; 27 | } 28 | setTimeout(asyncRead, delay); 29 | } 30 | } 31 | 32 | function onCompleted(data) { 33 | console.log('Completed'); 34 | console.log(data); 35 | } 36 | 37 | // readStream.once('readable', createAsyncStreamReader(readStream, onCompleted, 0)); 38 | 39 | // readStream.on('data', (chunk) => { 40 | // console.log(chunk.toString()); 41 | // }); 42 | 43 | // setTimeout(() => { 44 | // readStream.pause(); 45 | 46 | // setTimeout(() => { 47 | // readStream.read(); 48 | // readStream.read(); 49 | // readStream.read(); 50 | 51 | // // setTimeout(() => { 52 | // // readStream.resume(); 53 | // // }, 1000); 54 | // }, 1000); 55 | // }, 10); 56 | 57 | 58 | // readStream.pipe(trasformStream.pipe(writeStream)) 59 | // pipeline(readStream, trasformStream, writeStream); 60 | 61 | 62 | 63 | // class MySteamClass extends Readable { 64 | // constructor(array) { 65 | // super({ highWaterMark: 1 }); 66 | // this.i = 0; 67 | // this.array = array; 68 | // } 69 | 70 | // _read() { 71 | 72 | // } 73 | // } 74 | 75 | 76 | function fromArray(array) { 77 | const readableStream = new Readable({ highWaterMark: 1 }); 78 | let i = 0; 79 | readableStream._read = function () { 80 | if (i < array.length) { 81 | readableStream.push(array[i++]); 82 | } else { 83 | readableStream.push(null); 84 | } 85 | }; 86 | return readableStream; 87 | } 88 | 89 | function createWritable(onCompleted) { 90 | const writableStream = new Writable({ highWaterMark: 1 }); 91 | let allData = ''; 92 | writableStream.write = function (chunk) { 93 | allData += chunk?.toString(); 94 | }; 95 | writableStream.on('finish', () => { 96 | onCompleted(allData); 97 | }); 98 | return writableStream; 99 | } 100 | 101 | function createTransform() { 102 | return new Transform({ 103 | transform(chunk, encoding, callback) { 104 | const tChunk = chunk.toString().replace('aaa', '***'); 105 | callback(null, tChunk); 106 | } 107 | }); 108 | } 109 | 110 | const tStream = createTransform(); 111 | 112 | 113 | 114 | const myReadStream = fromArray(['aaa', 'aaaa', 'aasadasddsa', 'dsasa', '1', '2', '3']); 115 | myReadStream.pause(); 116 | 117 | // myReadStream.once('readable', createAsyncStreamReader(myReadStream, onCompleted, 10)); 118 | const ws = createWritable(allData => console.log(allData)); 119 | myReadStream.pipe(tStream).pipe(ws); -------------------------------------------------------------------------------- /week6/lecture/test (copy).txt: -------------------------------------------------------------------------------- 1 | dsadskafsa;klsmld as[dsadskafsaasd 2 | adsa 3 | dsadskafsadsd 4 | sd 5 | sad 6 | as dsadskafsaa d 7 | sadas 8 | ds adas dsadskafsadsddas 9 | dsadskafsadsddasd 10 | s dsadskafsad adsa sad sa da] 11 | 12 | dsadskafs 13 | sdsadas 14 | dsadskafs -------------------------------------------------------------------------------- /week6/lecture/test.txt: -------------------------------------------------------------------------------- 1 | dsadskafsa;klsmld as[dsadskafsaasd 2 | adsa 3 | dsadskafsadsd 4 | sd 5 | sad 6 | as dsadskafsaa d 7 | sadas 8 | ds adas dsadskafsadsddas 9 | dsadskafsadsddasd 10 | s dsadskafsad adsa sad sa da] 11 | 12 | dsadskafs 13 | sdsadas 14 | dsadskafs -------------------------------------------------------------------------------- /week7/exercise/filter.ts.stream.js: -------------------------------------------------------------------------------- 1 | // transform stream working with small buffer 2 | // and regular expressions list to filter out 3 | // 4 | // 5 | // Copyright Sofia University 2022 6 | // Copyright G.Penkov / I.Idakiev 7 | // 8 | // License is 9 | // Creative Commons - no commercial, attribution 10 | 11 | const fs = require('fs'), os = require('os'); 12 | const { Readable, Writable, Transform } = require('stream'); 13 | const HWM = 5; // high watermark - how many symbols to read 14 | 15 | const regexen = [ // thse may come from some file also 16 | /(num)+/, 17 | /!/, 18 | /[0-9]+/, 19 | ]; 20 | 21 | const rs = fs.createReadStream('./input.txt', 22 | { encoding: 'utf-8', highWaterMark: HWM }); 23 | const ws = fs.createWriteStream('./output.txt', 24 | { encoding: 'utf-8', highWaterMark: HWM }); 25 | 26 | let buf = ''; 27 | 28 | const tStream = new Transform({ 29 | transform(chunk, encoding, callback) { 30 | const tChunk = chunk.toString(); // chunk is bytes yet 31 | let spl = (buf + tChunk).split(os.EOL); // convert it to chars 32 | buf = spl.pop(); // keep all remaining after newline in buffer 33 | 34 | if (chunk.length < HWM) { // are we at the last chunk? 35 | spl.push(buf); // yes, then process the buffer as last line 36 | } 37 | 38 | spl = spl.map(line => // match each line in the array 39 | regexen.reduce((line, re) => // with each of the regexes 40 | line.replace(re, val => // replace all that matches... 41 | val.replace(/./g, '*') ) // ...with that many *'s 42 | , line) ) // starting with the original line contents 43 | 44 | if (spl.length) { // in case there are lines pending 45 | callback(null, // flush down the drain 46 | spl.join(os.EOL) 47 | + (chunk.length < HWM ? '' : os.EOL)); 48 | } 49 | 50 | callback(null) // or else just continue 51 | } 52 | }); 53 | 54 | rs.pipe(tStream).pipe(ws); 55 | 56 | /* 57 | // note: lines 40-43 can be written also as 58 | for (re of regexen) { // against each of the regexes 59 | line = line.replace(re, val => // all that matches ... 60 | val.replace(/./g, '*')) // ...replace with asterisk 61 | } 62 | return line 63 | */ -------------------------------------------------------------------------------- /week7/exercise/input.txt: -------------------------------------------------------------------------------- 1 | !! this is a sample input 2 | which is to show >> 3 | how this all works ! 4 | and some numbers here - 100 5 | there - 200 -------------------------------------------------------------------------------- /week7/lecture/desctructuring&spread-operator.js: -------------------------------------------------------------------------------- 1 | // ! Spread operator 2 | 3 | // Arrays 4 | const arr1 = [1, 2, 3]; 5 | const arr2 = [5, 6, 7]; 6 | const arr1_2 = [...arr1, 4, ...arr2]; 7 | 8 | // Objects 9 | const ivanPersonData = { name: 'Ivan', age: 27, birthday: '19.11' }; 10 | const ivanProfessionalData = { job: 'student' }; 11 | 12 | const wholeIvan = { 13 | ...ivanPersonData, 14 | ...ivanProfessionalData, 15 | }; 16 | 17 | const wholeIvanUnsuccessfulNameChange = { 18 | name: 'Peter', 19 | ...ivanPersonData, // `name` gets replaced with the value from the spread ==> we are left with 'Ivan' 20 | ...ivanProfessionalData, 21 | }; 22 | 23 | const wholeIvanSuccessfulNameChange = { 24 | ...ivanPersonData, 25 | ...ivanProfessionalData, 26 | name: 'Peter', 27 | }; 28 | 29 | // ! Destructuring 30 | 31 | // const name = wholeIvan.name; 32 | // const age = wholeIvan.age; 33 | const { 34 | name, age, // * extract these from the given object 35 | birthday: bDay, // * extract the given prop to the given variable 36 | ...rest // * get the rest 37 | } = wholeIvan; 38 | console.log(name, age, bDay); // 'Ivan' 27 '19.11' 39 | console.log(rest); // { job: 'student' } 40 | 41 | // let { a, ...rest, c } = { a:1, b:2, c:3 }; // ! Uncaught SyntaxError: Rest element must be last element 42 | 43 | const arr3 = [1, 2, 3, 4, 5, 6, 7]; 44 | // const [a, b, c] = arr3; // 1, 2, 3 45 | // const [,,a, b, c] = arr3; // 3, 4, 5 46 | // const [,, a, b, c, ...arrRest] = arr3; // 3, 4, 5; [6,7] 47 | // const [,, a, b, c, ...arrRest, d] = arr3; // ! Uncaught SyntaxError: Rest element must be last element 48 | 49 | // ! Function 50 | 51 | const fn = (a, b, ...rest) => console.log(a, b, rest) 52 | 53 | fn(1); // 1 undefined [] 54 | fn(1, 2); // 1 2 [] 55 | fn(1, 2, 3); // 1 2 [3] 56 | fn(1, 2, 3, 4); // 1 2 [3, 4] 57 | fn(1, 2, 3, 4, 5); // 1 2 [3, 4, 5] -------------------------------------------------------------------------------- /week7/lecture/index.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FMIjs/advanced-javascript-2022-2023/98f8cabb830dbfd82edd0ef1686b049f15be968d/week7/lecture/index.js -------------------------------------------------------------------------------- /week7/lecture/sample.js: -------------------------------------------------------------------------------- 1 | class EventEmitterWithMapAndSet { 2 | constructor() { 3 | this.eventMap = new Map(); 4 | } 5 | 6 | emit = (eventName, data) => { 7 | const handlers = this.eventMap.get(eventName); 8 | if (!handlers) { return; } 9 | handlers.forEach(handler => handler(data)); 10 | } 11 | on = (eventName, handler) => { 12 | if (!this.eventMap.has(eventName)) { 13 | this.eventMap.set(eventName, new Set()) 14 | } 15 | this.eventMap.get(eventName).add(handler) 16 | } 17 | unsubscribe = (eventName, handler) => { 18 | this.eventMap.get(eventName).delete(handler) 19 | } 20 | } 21 | 22 | class EventEmitter { 23 | constructor() { 24 | this.eventMap = {}; 25 | } 26 | 27 | emit = (eventName, data) => { 28 | const handlers = this.eventMap[eventName]; 29 | if (!handlers) { return; } 30 | handlers.forEach(handler => handler(data)); 31 | } 32 | on = (eventName, handler) => { 33 | this.eventMap[eventName] = [...(this.eventMap[eventName] || []), handler]; 34 | } 35 | unsubscribe = (eventName, handler) => { 36 | if (!this.eventMap[eventName]) { return; } 37 | this.eventMap[eventName] = this.eventMap[eventName].filter(h => h !== handler); 38 | } 39 | } 40 | 41 | class Person extends EventEmitter { 42 | constructor(age) { 43 | super(); 44 | this.age = age; 45 | } 46 | getOlder() { 47 | this.age++; 48 | this.emit('get-older', this.age); 49 | } 50 | } 51 | 52 | const ivan = new Person(23); 53 | const handler = (age) => console.log(`Happy ${age}th birthday! 🎂`); 54 | const handler2 = (age) => console.log(`${age} 🎂`); 55 | ivan.on('get-older', handler); 56 | ivan.getOlder(); // Happy 24th birthday! 🎂 57 | ivan.getOlder(); // Happy 25th birthday! 🎂 58 | 59 | ivan.unsubscribe('get-older', handler) 60 | ivan.getOlder(); // *nothing* 61 | 62 | ivan.on('get-older', handler); 63 | ivan.on('get-older', handler2); 64 | ivan.getOlder(); // Happy 27th birthday! 🎂 && 27 🎂 65 | 66 | ivan.unsubscribe('get-older', handler) 67 | ivan.getOlder(); // 28 🎂 68 | -------------------------------------------------------------------------------- /week7/sample-solution-for-exam-1.js: -------------------------------------------------------------------------------- 1 | class EventEmitterWithMapAndSet { 2 | constructor() { 3 | this.eventMap = new Map(); 4 | } 5 | 6 | emit = (eventName, data) => { 7 | const handlers = this.eventMap.get(eventName); 8 | if (!handlers) { return; } 9 | handlers.forEach(handler => handler(data)); 10 | } 11 | on = (eventName, handler) => { 12 | if (!this.eventMap.has(eventName)) { 13 | this.eventMap.set(eventName, new Set()) 14 | } 15 | this.eventMap.get(eventName).add(handler) 16 | } 17 | unsubscribe = (eventName, handler) => { 18 | this.eventMap.get(eventName).delete(handler) 19 | } 20 | } 21 | 22 | class EventEmitter { 23 | constructor() { 24 | this.eventMap = {}; 25 | } 26 | 27 | emit = (eventName, data) => { 28 | const handlers = this.eventMap[eventName]; 29 | if (!handlers) { return; } 30 | handlers.forEach(handler => handler(data)); 31 | } 32 | on = (eventName, handler) => { 33 | this.eventMap[eventName] = [...(this.eventMap[eventName] || []), handler]; 34 | } 35 | unsubscribe = (eventName, handler) => { 36 | if (!this.eventMap[eventName]) { return; } 37 | this.eventMap[eventName] = this.eventMap[eventName].filter(h => h !== handler); 38 | } 39 | } 40 | 41 | class Person extends EventEmitter { 42 | constructor(age) { 43 | super(); 44 | this.age = age; 45 | } 46 | getOlder() { 47 | this.age++; 48 | this.emit('get-older', this.age); 49 | } 50 | } 51 | 52 | const ivan = new Person(23); 53 | const handler = (age) => console.log(`Happy ${age}th birthday! 🎂`); 54 | const handler2 = (age) => console.log(`${age} 🎂`); 55 | ivan.on('get-older', handler); 56 | ivan.getOlder(); // Happy 24th birthday! 🎂 57 | ivan.getOlder(); // Happy 25th birthday! 🎂 58 | 59 | ivan.unsubscribe('get-older', handler) 60 | ivan.getOlder(); // *nothing* 61 | 62 | ivan.on('get-older', handler); 63 | ivan.on('get-older', handler2); 64 | ivan.getOlder(); // Happy 27th birthday! 🎂 && 27 🎂 65 | 66 | ivan.unsubscribe('get-older', handler) 67 | ivan.getOlder(); // 28 🎂 68 | -------------------------------------------------------------------------------- /week8/exercise/readme.md: -------------------------------------------------------------------------------- 1 | # Упражнение 8 2 | 3 | ## 30.11.22 4 | 5 | 6 | 1. Използвайки [Express](https://expressjs.com/) създайте сървър за базово менажиране на събития. Той да има следните endpoint-и: 7 | - **POST** /event, body: `{ name: string, capacity: number }` -- създава събитие и го връща в отговора; 8 | - **GET** /event/:id -- връща данните за дадено събитие; 9 | - **DELETE** /event/:id -- изтрива данните за дадено събитие; 10 | - **POST** /event/:id/booking, body: `{ firstName: string, lastName: string }` -- запазва дадения потребител в гостите на даденото събитие, ако има свободни места. В отговора да връща оставащите свободни места, ако е успяло да запази, в противен случай да връща грешка 11 | - **GET** /event/:id/booking -- връща данните за гостите на даденото събитие; 12 | - **GET** /event/:id/booking/:bookingId -- връща данните за конкретен гост на даденото събитие; 13 | - **DELETE** /event/:id/booking/:bookingId -- изтива данните за конкретен гост на даденото събитие; 14 | 15 | Като "база данни" може да използвате [Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map). -------------------------------------------------------------------------------- /week8/lecture/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /week8/lecture/index.js: -------------------------------------------------------------------------------- 1 | const http = require('http'); 2 | const port = 8080; 3 | 4 | const urlEncodedStringValueRe = /^"(.*)"$/; 5 | 6 | const users = [ 7 | { username: 'Ivan', id: 1 }, 8 | { username: 'Pesho', id: 2 }, 9 | { username: 'Gosho', id: 3 } 10 | ]; 11 | 12 | const router = { 13 | routes: { 14 | '\/user$': { 15 | POST: (data, res) => { 16 | const { body } = data; 17 | const id = users.length + 1; 18 | const user = { ...body, id }; 19 | users.push(user); 20 | res.write(JSON.stringify(user)); 21 | }, 22 | GET: (data, res) => { 23 | res.write(JSON.stringify(users)); 24 | } 25 | }, 26 | '\/user\/(?[^\/]*)$': { 27 | PUT: (data, res) => { 28 | const { params: { id }, body } = data; 29 | const user = users[id - 1]; 30 | if (!user) { return void res.write(null); } 31 | const updatedUser = { ...user, ...body }; 32 | users[id - 1] = updatedUser; 33 | res.write(JSON.stringify(updatedUser)); 34 | }, 35 | GET: (data, res) => { 36 | const { params: { id } } = data; 37 | if (!id) { return void res.write(null); } 38 | res.write(JSON.stringify(users[id - 1] || null)); 39 | }, 40 | DELETE: (data, res) => { 41 | const { params: { id } } = data; 42 | if (!id) { return void res.write(null); } 43 | const deletedUser = users[id - 1]; 44 | delete users[id - 1]; 45 | res.write(JSON.stringify(deletedUser)); 46 | } 47 | } 48 | }, 49 | handleUrl(url, body, req, res) { 50 | let handlers = null; 51 | let params = {}; 52 | for (const [routeKey, routeHandlers] of Object.entries(this.routes)) { 53 | const routeKeyRe = new RegExp(routeKey); 54 | if (!routeKeyRe.test(url)) { continue; } 55 | handlers = routeHandlers; 56 | const { groups = {} } = routeKeyRe.exec(url); 57 | params = groups || {}; 58 | break; 59 | } 60 | const method = req.method.toUpperCase(); 61 | const methodHanlder = handlers[method] || null; 62 | if (!methodHanlder) { 63 | res.writeHead(404, 'Route not found!'); 64 | res.end(); 65 | return; 66 | } 67 | methodHanlder({ body, params }, res); 68 | res.end(); 69 | } 70 | }; 71 | 72 | function parseData(contentType, dataString) { 73 | if (contentType === 'application/json') { 74 | return JSON.parse(dataString); 75 | } 76 | if (contentType === 'application/x-www-form-urlencoded') { 77 | return dataString.split('&') 78 | .map(str => str.split('=')) 79 | .reduce((acc, [key, value]) => { 80 | value = decodeURI(value); 81 | if (urlEncodedStringValueRe.test(value)) { 82 | acc[key] = value.replace(urlEncodedStringValueRe, '$1'); 83 | } else if (['true', 'false'].includes(value)) { 84 | acc[key] = value === 'true' ? true : false; 85 | } else if (value === 'null') { 86 | acc[key] = null; 87 | } else { 88 | const numberValue = +value; 89 | if (!Number.isNaN(numberValue)) { 90 | acc[key] = numberValue; 91 | } else { 92 | acc[key] = value; 93 | } 94 | } 95 | return acc; 96 | }, {}); 97 | } 98 | return null; 99 | } 100 | 101 | const server = http.createServer((req, res) => { 102 | let reqData = ''; 103 | 104 | req.on('data', (chunk) => { 105 | reqData += chunk; 106 | }); 107 | 108 | req.on('end', () => { 109 | const data = parseData(req.headers['content-type'], reqData); 110 | const url = req.url; 111 | router.handleUrl(url, data, req, res); 112 | }); 113 | }); 114 | 115 | server.listen(port, () => { 116 | console.log(`Server is listening on :${port}`); 117 | }); -------------------------------------------------------------------------------- /week8/lecture/my-express-server.js: -------------------------------------------------------------------------------- 1 | const myExpress = require('./my-express.js'); 2 | const port = 8080; 3 | 4 | const users = [ 5 | { username: 'Ivan', id: 1 }, 6 | { username: 'Pesho', id: 2 }, 7 | { username: 'Gosho', id: 3 } 8 | ]; 9 | 10 | const app = myExpress(); 11 | 12 | app.use(myExpress.jsonParser); 13 | 14 | app.get('\/user$', (req, res) => { 15 | res.write(JSON.stringify(users)); 16 | }); 17 | 18 | app.post('\/user$', (req, res) => { 19 | const { body } = req; 20 | const id = users.length + 1; 21 | const user = { ...body, id }; 22 | users.push(user); 23 | res.write(JSON.stringify(user)); 24 | }); 25 | 26 | 27 | app.get('\/user\/(?[^\/]*)$', (req, res) => { 28 | const { params: { id } } = req; 29 | if (!id) { return void res.write(null); } 30 | res.write(JSON.stringify(users[id - 1] || null)); 31 | }); 32 | 33 | // /user/:id 34 | app.delete('\/user\/(?[^\/]*)$', (req, res) => { 35 | const { params: { id } } = req; 36 | if (!id) { return void res.write(null); } 37 | const deletedUser = users[id - 1]; 38 | delete users[id - 1]; 39 | res.write(JSON.stringify(deletedUser)); 40 | }); 41 | 42 | app.put('\/user\/(?[^\/]*)$', (req, res) => { 43 | const { params: { id }, body } = req; 44 | const user = users[id - 1]; 45 | if (!user) { return void res.write(null); } 46 | const updatedUser = { ...user, ...body }; 47 | users[id - 1] = updatedUser; 48 | res.write(JSON.stringify(updatedUser)); 49 | }); 50 | 51 | 52 | app.listen(port, () => { 53 | console.log(`Server is listening :${port}`); 54 | }); -------------------------------------------------------------------------------- /week8/lecture/my-express.js: -------------------------------------------------------------------------------- 1 | const http = require('http'); 2 | 3 | function jsonParser(req, res, cb) { 4 | if (req.headers['content-type'] !== 'application/json') { 5 | return void cb(); 6 | } 7 | let reqData = ''; 8 | 9 | req.on('data', (chunk) => { reqData += chunk; }); 10 | 11 | req.on('error', (err) => { 12 | cb(err); 13 | }) 14 | req.on('end', () => { 15 | req.body = JSON.parse(reqData); 16 | cb(); 17 | }); 18 | } 19 | 20 | function urlEncodedParser(contentType, dataString) { 21 | if (contentType === 'application/x-www-form-urlencoded') { 22 | return dataString.split('&') 23 | .map(str => str.split('=')) 24 | .reduce((acc, [key, value]) => { 25 | value = decodeURI(value); 26 | if (urlEncodedStringValueRe.test(value)) { 27 | acc[key] = value.replace(urlEncodedStringValueRe, '$1'); 28 | } else if (['true', 'false'].includes(value)) { 29 | acc[key] = value === 'true' ? true : false; 30 | } else if (value === 'null') { 31 | acc[key] = null; 32 | } else { 33 | const numberValue = +value; 34 | if (!Number.isNaN(numberValue)) { 35 | acc[key] = numberValue; 36 | } else { 37 | acc[key] = value; 38 | } 39 | } 40 | return acc; 41 | }, {}); 42 | } 43 | return null; 44 | } 45 | 46 | function myExpress() { 47 | 48 | const middlewares = []; 49 | 50 | const router = { 51 | routes: {}, 52 | handleUrl(url, req, res) { 53 | let handlers = null; 54 | let params = {}; 55 | for (const [routeKey, routeHandlers] of Object.entries(this.routes)) { 56 | const routeKeyRe = new RegExp(routeKey); 57 | if (!routeKeyRe.test(url)) { continue; } 58 | handlers = routeHandlers; 59 | const { groups = {} } = routeKeyRe.exec(url); 60 | params = groups || {}; 61 | break; 62 | } 63 | const method = req.method.toUpperCase(); 64 | const methodHanlder = handlers[method] || null; 65 | if (!methodHanlder) { 66 | res.writeHead(404, 'Route not found!'); 67 | res.end(); 68 | return; 69 | } 70 | req.params = params; 71 | methodHanlder(req, res); 72 | res.end(); 73 | } 74 | }; 75 | 76 | const server = http.createServer((req, res) => { 77 | let counter = 0; 78 | 79 | function mainHandler() { 80 | const url = req.url; 81 | router.handleUrl(url, req, res); 82 | } 83 | 84 | function getNextMiddleware() { 85 | return middlewares[counter++] || mainHandler; 86 | } 87 | 88 | const firstMiddleware = getNextMiddleware(); 89 | const hanlder = (err) => { 90 | if (err) { 91 | res.writeHead(500); 92 | return void res.end(); 93 | } 94 | const nextMiddleware = getNextMiddleware(); 95 | if (nextMiddleware !== null) { 96 | return void nextMiddleware(req, res, hanlder); 97 | } 98 | } 99 | 100 | firstMiddleware(req, res, hanlder); 101 | }); 102 | 103 | return { 104 | use(fn) { 105 | middlewares.push(fn); 106 | }, 107 | put(urlReStr, handlerFn) { 108 | const handlers = router.routes[urlReStr] || {}; 109 | handlers['PUT'] = handlerFn; 110 | router.routes[urlReStr] = handlers; 111 | }, 112 | get(urlReStr, handlerFn) { 113 | const handlers = router.routes[urlReStr] || {}; 114 | handlers['GET'] = handlerFn; 115 | router.routes[urlReStr] = handlers; 116 | }, 117 | post(urlReStr, handlerFn) { 118 | const handlers = router.routes[urlReStr] || {}; 119 | handlers['POST'] = handlerFn; 120 | router.routes[urlReStr] = handlers; 121 | }, 122 | delete(urlReStr, handlerFn) { 123 | const handlers = router.routes[urlReStr] || {}; 124 | handlers['DELETE'] = handlerFn; 125 | router.routes[urlReStr] = handlers; 126 | }, 127 | listen(port, cb) { 128 | server.listen(port, cb); 129 | } 130 | }; 131 | }; 132 | 133 | myExpress.jsonParser = jsonParser; 134 | module.exports = myExpress; -------------------------------------------------------------------------------- /week8/lecture/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lecture", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node server.js" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "express": "^4.18.2" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /week8/lecture/server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | 3 | const app = express(); 4 | 5 | app.use(express.json()); 6 | 7 | app.use((req, res, next) => { 8 | console.log(req.headers); 9 | setTimeout(() => { 10 | next(); 11 | }, 1000); 12 | }); 13 | 14 | app.post('/user', (req, res, next) => { 15 | console.log(req.body); 16 | 17 | }, function (req, res, next) { 18 | res.send('POST'); 19 | }); 20 | 21 | app.get('/', (req, res) => { 22 | res.send('HELLO!'); 23 | }); 24 | 25 | app.listen(8080, () => { console.log('Server is running') }); -------------------------------------------------------------------------------- /week9/example.json: -------------------------------------------------------------------------------- 1 | {"employees":[ 2 | {"name":"Shyam", "email":"shyamjaiswal@gmail.com"}, 3 | {"name":"Bob", "email":"bob32@gmail.com"}, 4 | {"name":"Jai", "email":"jai87@gmail.com"} 5 | ]} -------------------------------------------------------------------------------- /week9/lecture/example.json: -------------------------------------------------------------------------------- 1 | {"employees":[ 2 | {"name":"Shyam", "email":"shyamjaiswal@gmail.com"}, 3 | {"name":"Bob", "email":"bob32@gmail.com"}, 4 | {"name":"Jai", "email":"jai87@gmail.com"} 5 | ]} -------------------------------------------------------------------------------- /week9/lecture/myprom.js: -------------------------------------------------------------------------------- 1 | // Simple implementation of Promise-like clas 2 | // (C) 2022 G.Penkov 3 | // (C) 2022 Sofia University 4 | // 5 | // Creative Commons Share-alike 6 | 7 | class MyPromise { 8 | constructor(fun) { 9 | this.cbs = []; // keep a list of callbacks 10 | fun(val => this.res(val), // call the function provided 11 | err => this.rej(err)); 12 | } 13 | 14 | res(val) { 15 | let cb; 16 | while (cb = this.cbs.shift()) { // process callbacks 17 | val = cb(val); // if the returned value is Promise 18 | if (val instanceof MyPromise) { // defer the processing 19 | return val.then( result => this.res(result)); 20 | } 21 | } 22 | } 23 | 24 | rej = function () { 25 | console.error('failed promise'); 26 | }; 27 | 28 | then = function (cb) { // then will simply register the next 29 | this.cbs.push(cb); // register new callback to be handled 30 | return this 31 | } 32 | 33 | // note the finally and catch methods are missing, 34 | // so don't count on them 35 | } 36 | 37 | let prom = new MyPromise((res, rej) => { 38 | setTimeout(function () { // something asynchronous 39 | res(100); // deffered result 40 | }, 2000) 41 | }) 42 | 43 | prom.then(res => res * 3) // not async 44 | .then(res => Promise.resolve(res)) // immediate async 45 | .then(res => { // not async 46 | console.log(res); 47 | }) 48 | -------------------------------------------------------------------------------- /week9/lecture/nevronka.txt: -------------------------------------------------------------------------------- 1 | Невронка 2 | ЛЛЛЛ • 7 décembre 2019 3 | 4 | интелект в кутия запис дъвче за закуска 5 | палав алгоритъм полиноми сладко хруска 6 | цикъл делта праща след числата да препуска 7 | лекинко регресия към нищото се спуска 8 | 9 | код набива данни във тръбата виртуална 10 | сметката за тока е единствено реална 11 | от далече сякаш - постановка тривиална 12 | носи се в ефира сложна форма интегрална 13 | 14 | логика разпъната в невронна мрежа спяла 15 | някъде из облаците - чистичка и бяла 16 | милиони снимки знае, даже текст разбрала 17 | в дигитален сън за първи път се осъзнала -------------------------------------------------------------------------------- /week9/lecture/promise.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const fsPromises = require('node:fs/promises'); 3 | 4 | const npath = __dirname + '/nevronka.txt' 5 | 6 | // const tobj = setTimeout(t => { 7 | // console.log('(2) now here') 8 | // setTimeout( t => { // the callback hell grows very fast & deep 9 | // console.log("(3) here we are"); 10 | // }, 1000) 11 | // }, 1000) 12 | 13 | // console.log('(1) here first') 14 | 15 | let asyncop = (res, rej) => { 16 | let value = 100; 17 | 18 | console.log("schedule something for async processing"); 19 | setTimeout( t => { // this effectively is a continuation 20 | res("(5) here we are"); 21 | }, 1000) 22 | } 23 | 24 | let promiseObject = new Promise(asyncop) 25 | let readFileAsync = function(fileName) { 26 | return () => new Promise ((res, rej) => { 27 | fs.readFile( 28 | fileName, 29 | { encoding: 'utf8'}, 30 | ( err, data ) => { 31 | if (err) { 32 | return rej (err) } 33 | return res(data) 34 | }) 35 | return 'yeah!' // has absolutely no effect whatsoever 36 | }) 37 | }; 38 | 39 | // const readBadPromise = readFileAsync('NOSUCH.TXT'); 40 | 41 | Promise.any([ 42 | promiseObject, // fsPromises offers aready promise-wrapped APIs 43 | fsPromises.readFile(npath, { encoding: 'utf8'} ) 44 | ]).then( res => { 45 | console.log('here') 46 | }); 47 | 48 | // Promise chain - then and catch. 49 | 50 | Promise.resolve('start!') // immediately resolved 51 | .then( 52 | readFileAsync('example.json')) // function returns function here! 53 | .then( 54 | res => JSON.parse(res)) // exceptions raised here will 55 | .catch( // fail here if JSON is with errors 56 | err => console.log(err)) 57 | .then( 58 | res =>console.log(res)) 59 | 60 | promiseObject 61 | .then( res => 62 | console.log(res)) 63 | // .then( res => // here res would be undefined 64 | // console.log(res)) 65 | .then( readFileAsync(npath) ) 66 | .then( () => Promise.resolve('123') ) 67 | // .then( res => fsPromises.readFile(npath, { encoding: 'utf8'} ) ) 68 | .then( res => { 69 | console.log(res) 70 | } ) 71 | .then( readFileAsync('NOSUCH.TXT') ) 72 | .catch( err => { 73 | console.error('some error occured!') 74 | }) 75 | .finally( () => { 76 | console.log('this runs always. nevermind the then/catch'); 77 | }) 78 | 79 | // async/await examples 80 | 81 | async function fn() { 82 | let res = await readFileAsync(npath)(); 83 | res = await Promise.resolve(); // same as Promise.resolve().then() 84 | return new Promise( (res, rej) => { 85 | setTimeout( () => res(4), 2000) 86 | }) 87 | } 88 | 89 | fn().then(res => { // call the async func and require that it runs 90 | console.log(res) } 91 | ); 92 | 93 | // original promises spec by Kris Kowal 94 | // https://github.com/kriskowal/q 95 | 96 | // additional full-blown promises library 97 | // http://bluebirdjs.com/docs/api-reference.html 98 | 99 | // promises in C++ 100 | // http://www.home.hs-karlsruhe.de/~suma0002/publications/advanced-futures-promises-cpp.pdf 101 | 102 | // sqlLite access with promises 103 | // https://www.npmjs.com/package/promised-sqlite3 -------------------------------------------------------------------------------- /week9/lecture/promises.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FMIjs/advanced-javascript-2022-2023/98f8cabb830dbfd82edd0ef1686b049f15be968d/week9/lecture/promises.png --------------------------------------------------------------------------------