├── .gitignore ├── Chapter01 ├── Custom Elements │ ├── Add Smiley │ │ └── index.html │ ├── Hello World │ │ └── index.html │ └── Smiley Emoji │ │ └── index.html ├── Modules │ ├── Add Number with Random Number Generator and export │ │ ├── addNumber.js │ │ └── index.html │ ├── Add Number with Random Number Generator │ │ ├── addNumber.js │ │ └── index.html │ ├── Add Number │ │ └── index.html │ ├── Calc and export │ │ ├── calc.js │ │ └── index.html │ ├── RevampedParagraph │ │ ├── index.html │ │ └── revampedParagraph.js │ └── StudentPage │ │ ├── InformationBanner.js │ │ ├── StudentAttendanceTable.js │ │ ├── TimeSlot.js │ │ ├── index.html │ │ └── student.json ├── Shadow DOM │ ├── Hello World with Shadow DOM │ │ └── index.html │ ├── Simple Shadow DOM 2 │ │ └── index.html │ └── Simple Shadow DOM │ │ └── index.html └── Templates │ ├── Hello World Custom Element with Shadow DOM and Template │ └── index.html │ ├── Small Template Example With Shadow DOM │ └── index.html │ └── Small Template Example │ └── index.html ├── Chapter02 ├── attributeChangedCallback │ ├── MyName.js │ └── index.html ├── connectedCallbackButtonExample │ ├── CustomButton.js │ └── index.html ├── connectedCallbackStudentExample │ ├── StudentAttendanceTable.js │ ├── index.html │ └── student.json └── disconnectedCallbackButtonExample │ ├── CustomButton.js │ └── index.html ├── Chapter03 ├── Accessibility for web components │ └── Header Image component │ │ ├── HeaderImage.js │ │ └── index.html └── Styling Web Component │ ├── company header component with shadow dom │ ├── CompanyHeader.js │ ├── icon.png │ ├── index.html │ └── newicon.jpeg │ ├── company header component │ ├── CompanyHeader.js │ ├── icon.png │ ├── index.html │ └── newicon.jpeg │ └── company-login │ ├── CompanyLogin.js │ └── index.html ├── Chapter04 ├── company-header │ ├── CompanyHeader.js │ ├── icon.png │ ├── index.html │ └── newicon.jpeg ├── my-article │ ├── MyArticle.js │ ├── icon.png │ ├── index.html │ └── newicon.jpeg └── profile-info │ ├── Demo │ ├── index.html │ └── john-doe.png │ ├── LICENSE │ ├── ProfileInfo.js │ ├── README.md │ └── package.json ├── Chapter05 ├── attributes and props │ ├── StudentList.js │ └── index.html ├── event handling │ ├── CustomClicker.js │ └── index.html └── online-checker │ ├── OnlineChecker.js │ └── index.html ├── Chapter06 ├── Game Information App │ ├── README.md │ ├── index.html │ ├── package-lock.json │ ├── package.json │ ├── src │ │ ├── components │ │ │ ├── custom-header │ │ │ │ └── index.js │ │ │ ├── gif-cover │ │ │ │ └── index.js │ │ │ ├── my-app │ │ │ │ └── index.js │ │ │ ├── search-bar │ │ │ │ └── index.js │ │ │ ├── search-container │ │ │ │ └── index.js │ │ │ ├── show-random │ │ │ │ └── index.js │ │ │ └── show-trending │ │ │ │ └── index.js │ │ ├── index.js │ │ └── styles.scss │ ├── webpack.config.js │ └── webpack.dev.server.js └── Starter Project │ ├── README.md │ ├── index.html │ ├── package-lock.json │ ├── package.json │ ├── src │ ├── components │ │ └── my-app │ │ │ └── index.js │ ├── index.js │ └── styles.scss │ ├── webpack.config.js │ └── webpack.dev.server.js ├── Chapter07 ├── Polymer │ ├── hello-string.js │ ├── hello-world.js │ ├── index.html │ ├── package-lock.json │ ├── package.json │ ├── second-element.js │ └── student-name.js └── Stencil │ ├── hello-world │ ├── .editorconfig │ ├── .gitignore │ ├── LICENSE │ ├── package-lock.json │ ├── package.json │ ├── readme.md │ ├── src │ │ ├── components.d.ts │ │ ├── components │ │ │ ├── hello-world │ │ │ │ ├── hello-world.css │ │ │ │ └── hello-world.tsx │ │ │ └── my-component │ │ │ │ ├── my-component.css │ │ │ │ ├── my-component.e2e.ts │ │ │ │ ├── my-component.tsx │ │ │ │ └── readme.md │ │ ├── index.html │ │ ├── index.ts │ │ └── utils │ │ │ ├── utils.spec.ts │ │ │ └── utils.ts │ ├── stencil.config.ts │ └── tsconfig.json │ └── student-list │ ├── .editorconfig │ ├── .gitignore │ ├── LICENSE │ ├── package-lock.json │ ├── package.json │ ├── readme.md │ ├── src │ ├── components.d.ts │ ├── components │ │ ├── student-list │ │ │ ├── student-list.css │ │ │ └── student-list.tsx │ │ └── student-name │ │ │ ├── student-name.css │ │ │ └── student-name.tsx │ ├── index.html │ ├── index.ts │ └── utils │ │ ├── utils.spec.ts │ │ └── utils.ts │ ├── stencil.config.ts │ └── tsconfig.json ├── Chapter08 ├── Angular │ └── my-app │ │ ├── .editorconfig │ │ ├── .gitignore │ │ ├── README.md │ │ ├── angular.json │ │ ├── e2e │ │ ├── protractor.conf.js │ │ ├── src │ │ │ ├── app.e2e-spec.ts │ │ │ └── app.po.ts │ │ └── tsconfig.e2e.json │ │ ├── package-lock.json │ │ ├── package.json │ │ ├── src │ │ ├── app │ │ │ ├── app.component.css │ │ │ ├── app.component.html │ │ │ ├── app.component.spec.ts │ │ │ ├── app.component.ts │ │ │ ├── app.module.ts │ │ │ ├── main-body │ │ │ │ ├── main-body.component.css │ │ │ │ ├── main-body.component.html │ │ │ │ ├── main-body.component.spec.ts │ │ │ │ └── main-body.component.ts │ │ │ └── web-components │ │ │ │ └── header-image │ │ │ │ ├── header-image.js │ │ │ │ └── index.html │ │ ├── assets │ │ │ └── .gitkeep │ │ ├── browserslist │ │ ├── environments │ │ │ ├── environment.prod.ts │ │ │ └── environment.ts │ │ ├── favicon.ico │ │ ├── index.html │ │ ├── karma.conf.js │ │ ├── main.ts │ │ ├── polyfills.ts │ │ ├── styles.css │ │ ├── test.ts │ │ ├── tsconfig.app.json │ │ ├── tsconfig.spec.json │ │ └── tslint.json │ │ ├── tsconfig.json │ │ └── tslint.json ├── React │ └── my-app │ │ ├── .gitignore │ │ ├── README.md │ │ ├── package-lock.json │ │ ├── package.json │ │ ├── public │ │ ├── favicon.ico │ │ ├── index.html │ │ └── manifest.json │ │ └── src │ │ ├── App.css │ │ ├── App.js │ │ ├── App.test.js │ │ ├── index.css │ │ ├── index.js │ │ ├── logo.svg │ │ ├── main-body │ │ └── main-body.js │ │ ├── serviceWorker.js │ │ └── web-components │ │ └── header-image │ │ ├── header-image.js │ │ └── index.html ├── Vue │ ├── app.js │ ├── index.html │ ├── main-body.js │ └── web-components │ │ └── header-image │ │ ├── header-image.js │ │ └── index.html └── header-image │ ├── header-image.js │ └── index.html ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | 24 | # nyc test coverage 25 | .nyc_output 26 | 27 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 28 | .grunt 29 | 30 | # Bower dependency directory (https://bower.io/) 31 | bower_components 32 | 33 | # node-waf configuration 34 | .lock-wscript 35 | 36 | # Compiled binary addons (https://nodejs.org/api/addons.html) 37 | build/Release 38 | 39 | # Dependency directories 40 | node_modules/ 41 | jspm_packages/ 42 | 43 | # TypeScript v1 declaration files 44 | typings/ 45 | 46 | # Optional npm cache directory 47 | .npm 48 | 49 | # Optional eslint cache 50 | .eslintcache 51 | 52 | # Optional REPL history 53 | .node_repl_history 54 | 55 | # Output of 'npm pack' 56 | *.tgz 57 | 58 | # Yarn Integrity file 59 | .yarn-integrity 60 | 61 | # dotenv environment variables file 62 | .env 63 | .env.test 64 | 65 | # parcel-bundler cache (https://parceljs.org/) 66 | .cache 67 | 68 | # next.js build output 69 | .next 70 | 71 | # nuxt.js build output 72 | .nuxt 73 | 74 | # vuepress build output 75 | .vuepress/dist 76 | 77 | # Serverless directories 78 | .serverless/ 79 | 80 | # FuseBox cache 81 | .fusebox/ 82 | 83 | # DynamoDB Local files 84 | .dynamodb/ 85 | 86 | # Mac OS Specific 87 | *.DS_Store 88 | 89 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 90 | 91 | # dependencies 92 | */node_modules 93 | */.pnp 94 | *.pnp.js 95 | 96 | # testing 97 | */coverage 98 | 99 | # production 100 | */build 101 | 102 | # misc 103 | *.DS_Store 104 | *.env.local 105 | *.env.development.local 106 | *.env.test.local 107 | *.env.production.local 108 | 109 | npm-debug.log* 110 | *yarn-debug.log* 111 | *yarn-error.log* 112 | -------------------------------------------------------------------------------- /Chapter01/Custom Elements/Add Smiley/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Hello World 8 | 9 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Chapter01/Custom Elements/Hello World/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Hello World 5 | 6 | 7 | 8 | 9 | 10 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /Chapter01/Custom Elements/Smiley Emoji/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Chapter01/Modules/Add Number with Random Number Generator and export/addNumber.js: -------------------------------------------------------------------------------- 1 | export default class AddNumber { 2 | constructor() { 3 | // let's set the inner text of 4 | // this element to a smiley 5 | document.querySelector('p').innerText = randomNumberGenerator(); 6 | } 7 | } 8 | 9 | function randomNumberGenerator() { 10 | return Math.random(); 11 | } 12 | -------------------------------------------------------------------------------- /Chapter01/Modules/Add Number with Random Number Generator and export/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |

Placeholder for Random Number

7 | 8 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Chapter01/Modules/Add Number with Random Number Generator/addNumber.js: -------------------------------------------------------------------------------- 1 | class AddNumber { 2 | constructor() { 3 | // let's set the inner text of 4 | // this element to a smiley 5 | document.querySelector('p').innerText = randomNumberGenerator(); 6 | } 7 | } 8 | 9 | function randomNumberGenerator() { 10 | return Math.random(); 11 | } 12 | 13 | new AddNumber(); 14 | -------------------------------------------------------------------------------- /Chapter01/Modules/Add Number with Random Number Generator/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |

Placeholder for Random Number

7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Chapter01/Modules/Add Number/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |

Placeholder for Random Number

7 | 8 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Chapter01/Modules/Calc and export/calc.js: -------------------------------------------------------------------------------- 1 | export function add(a, b) { 2 | return a + b; 3 | } 4 | 5 | export function subtract(a, b) { 6 | return a - b; 7 | } 8 | -------------------------------------------------------------------------------- /Chapter01/Modules/Calc and export/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |

Placeholder for Random Number

7 | 8 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Chapter01/Modules/RevampedParagraph/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Revamped Paragraph 5 | 6 | 9 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 30 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /Chapter01/Modules/RevampedParagraph/revampedParagraph.js: -------------------------------------------------------------------------------- 1 | export default class RevampedParagraph extends HTMLElement { 2 | constructor() { 3 | super(); 4 | 5 | // temaplte ref and content 6 | let templateReference = document.querySelector('#revamped-paragraph-template'); 7 | let template = templateReference.content; 8 | 9 | // adding html from template 10 | this.append(template.cloneNode(true)); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Chapter01/Modules/StudentPage/InformationBanner.js: -------------------------------------------------------------------------------- 1 | export default class InformationBanner extends HTMLElement { 2 | constructor() { 3 | super(); 4 | 5 | // we simply called another method 6 | // inside the class 7 | this.render(); 8 | } 9 | 10 | render() { 11 | 12 | // Get the reference to the template 13 | let templateReference = document.querySelector('#information-banner-template'); 14 | 15 | // Get the content node 16 | let templateContent = templateReference.content; 17 | 18 | let shadowRoot = this.attachShadow({mode: 'open'}); 19 | 20 | // add the template text to the shadow root 21 | shadowRoot.append(templateContent.cloneNode(true)); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Chapter01/Modules/StudentPage/StudentAttendanceTable.js: -------------------------------------------------------------------------------- 1 | export default class StudentAttendanceTable extends HTMLElement { 2 | constructor() { 3 | super(); 4 | 5 | // we simply called another method 6 | // inside the class 7 | this.render(); 8 | } 9 | 10 | render() { 11 | // let put our loading text first 12 | this.innerText = this.getLoadingText(); 13 | 14 | // let's start our fetch call 15 | this.getStudentList(); 16 | } 17 | 18 | getStudentList() { 19 | // lets use fetch api 20 | // https://developer.mozilla.org/en-US/docs/Web 21 | // /API/Fetch_API/Using_Fetch 22 | fetch('./student.json') 23 | .then(response => { 24 | 25 | // converts response to json 26 | return response.json(); 27 | 28 | }) 29 | .then(jsonData => { 30 | this.generateTable(jsonData); 31 | }) 32 | .catch(e => { 33 | 34 | // lets set the error message for 35 | // the user 36 | this.innerText = this.getErrorText(); 37 | 38 | // lets print out the error 39 | // message for the devs 40 | console.log(e); 41 | }); 42 | 43 | } 44 | 45 | generateTable(names) { 46 | // lets loop through names 47 | // with the help of map 48 | let rows = names.map((data, index) => { 49 | return this.getTableRow(index, data.name); 50 | }); 51 | 52 | // creating the table 53 | let table = document.createElement('table'); 54 | table.innerHTML = rows.join(''); 55 | 56 | // setting the table as html for this component 57 | this.appendHTMLToShadowDOM(table); 58 | } 59 | 60 | getTableRow(index, name) { 61 | let tableRow = ` 62 | ${index + 1} 63 | ${name} 64 | 65 | 66 | 67 | `; 68 | 69 | return tableRow; 70 | } 71 | 72 | appendHTMLToShadowDOM(html) { 73 | // clearing out old html 74 | this.innerHTML = ''; 75 | 76 | let shadowRoot = this.attachShadow({mode: 'open'}); 77 | 78 | // add a text node 79 | shadowRoot.append(html); 80 | } 81 | 82 | getLoadingText() { 83 | return `loading..`; 84 | } 85 | 86 | getErrorText() { 87 | return `unable to retrieve student list.`; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /Chapter01/Modules/StudentPage/TimeSlot.js: -------------------------------------------------------------------------------- 1 | export default class TimeSlot extends HTMLElement { 2 | constructor() { 3 | super(); 4 | 5 | // we simply called another method 6 | // inside the class 7 | this.render(); 8 | } 9 | 10 | render() { 11 | 12 | // Get the reference to the template 13 | let templateReference = document.querySelector('#time-slot-template'); 14 | 15 | // Get the content node 16 | let templateContent = templateReference.content; 17 | 18 | let shadowRoot = this.attachShadow({mode: 'open'}); 19 | 20 | // add the template text to the shadow root 21 | shadowRoot.append(templateContent.cloneNode(true)); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Chapter01/Modules/StudentPage/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Student Page 5 | 6 | 9 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 36 | 46 | 47 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /Chapter01/Modules/StudentPage/student.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "Robert De Niro", 4 | "lastScore": 75 5 | }, 6 | { 7 | "name": "Jack Nicholson", 8 | "lastScore": 87 9 | }, 10 | { 11 | "name": "Marlon Brando", 12 | "lastScore": 81 13 | }, 14 | { 15 | "name": "Tom Hanks", 16 | "lastScore": 62 17 | }, 18 | { 19 | "name": "Leonardo DiCaprio", 20 | "lastScore": 92 21 | } 22 | ] 23 | -------------------------------------------------------------------------------- /Chapter01/Shadow DOM/Hello World with Shadow DOM/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Chapter01/Shadow DOM/Simple Shadow DOM 2/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Hello World Shadow DOM 2 5 | 6 | 7 | 8 |
9 | 10 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /Chapter01/Shadow DOM/Simple Shadow DOM/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Hello World Shadow DOM 5 | 6 | 7 | 8 |

This is H1

9 |

10 | 11 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Chapter01/Templates/Hello World Custom Element with Shadow DOM and Template/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 27 | 28 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /Chapter01/Templates/Small Template Example With Shadow DOM/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 16 | 17 | 18 | 37 | 38 | 44 | 45 | 46 | 47 |
48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /Chapter01/Templates/Small Template Example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 16 | 17 | 18 | 27 | 28 | 34 | 35 | 36 | 37 |
38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /Chapter02/attributeChangedCallback/MyName.js: -------------------------------------------------------------------------------- 1 | export default class MyName extends HTMLElement { 2 | constructor() { 3 | super(); 4 | 5 | this.innerText = 'Hello, my name is NO NAME'; 6 | } 7 | 8 | static get observedAttributes() { 9 | return ['fullname']; 10 | } 11 | 12 | attributeChangedCallback(name, oldValue, newValue) { 13 | if (name == 'fullname') { 14 | this.innerText = 'Hello, my name is ' + newValue; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Chapter02/attributeChangedCallback/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Attribute Changed Callback Example 5 | 6 | 9 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Chapter02/connectedCallbackButtonExample/CustomButton.js: -------------------------------------------------------------------------------- 1 | export default class CustomButton extends HTMLElement { 2 | constructor() { 3 | super(); 4 | 5 | // Initializing an initial state 6 | this.timesClicked = 0; 7 | 8 | let template = ` 9 | 10 | ${this.getTimesClicked()} 11 | `; 12 | 13 | this.innerHTML = template; 14 | } 15 | 16 | connectedCallback() { 17 | 18 | // adding event handler to the button 19 | this.querySelector('button') 20 | .addEventListener('click', (e) => { 21 | this.handleClick(e); 22 | }); 23 | } 24 | 25 | handleClick() { 26 | // updating the state 27 | this.timesClicked++; 28 | 29 | this.querySelector('span') 30 | .innerText = this.getTimesClicked(); 31 | } 32 | 33 | getTimesClicked() { 34 | return `Times Clicked: ${this.timesClicked}`; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Chapter02/connectedCallbackButtonExample/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Connected Callback Example 5 | 6 | 9 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Chapter02/connectedCallbackStudentExample/StudentAttendanceTable.js: -------------------------------------------------------------------------------- 1 | export default class StudentAttendanceTable extends HTMLElement { 2 | constructor() { 3 | super(); 4 | 5 | this.innerText = this.getLoadingText(); 6 | } 7 | 8 | connectedCallback() { 9 | // let's start our fetch call 10 | this.getStudentList(); 11 | } 12 | 13 | getStudentList() { 14 | // lets use fetch api 15 | // https://developer.mozilla.org/en-US/docs/Web 16 | // /API/Fetch_API/Using_Fetch 17 | fetch('./student.json') 18 | .then(response => { 19 | 20 | // converts response to json 21 | return response.json(); 22 | 23 | }) 24 | .then(jsonData => { 25 | this.generateTable(jsonData); 26 | }) 27 | .catch(e => { 28 | 29 | // lets set the error message for 30 | // the user 31 | this.innerText = this.getErrorText(); 32 | 33 | // lets print out the error 34 | // message for the devs 35 | console.log(e); 36 | }); 37 | 38 | } 39 | 40 | generateTable(names) { 41 | // lets loop through names 42 | // with the help of map 43 | let rows = names.map((data, index) => { 44 | return this.getTableRow(index, data.name); 45 | }); 46 | 47 | // creating the table 48 | let table = document.createElement('table'); 49 | table.innerHTML = rows.join(''); 50 | 51 | // setting the table as html for this component 52 | this.appendHTMLToShadowDOM(table); 53 | } 54 | 55 | getTableRow(index, name) { 56 | let tableRow = ` 57 | ${index + 1} 58 | ${name} 59 | 60 | 61 | 62 | `; 63 | 64 | return tableRow; 65 | } 66 | 67 | appendHTMLToShadowDOM(html) { 68 | // clearing out old html 69 | this.innerHTML = ''; 70 | 71 | let shadowRoot = this.attachShadow({mode: 'open'}); 72 | 73 | // add a text node 74 | shadowRoot.append(html); 75 | } 76 | 77 | getLoadingText() { 78 | return `loading..`; 79 | } 80 | 81 | getErrorText() { 82 | return `unable to retrieve student list.`; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /Chapter02/connectedCallbackStudentExample/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Connected Callback Example 5 | 6 | 9 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Chapter02/connectedCallbackStudentExample/student.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "Robert De Niro", 4 | "lastScore": 75 5 | }, 6 | { 7 | "name": "Jack Nicholson", 8 | "lastScore": 87 9 | }, 10 | { 11 | "name": "Marlon Brando", 12 | "lastScore": 81 13 | }, 14 | { 15 | "name": "Tom Hanks", 16 | "lastScore": 62 17 | }, 18 | { 19 | "name": "Leonardo DiCaprio", 20 | "lastScore": 92 21 | } 22 | ] 23 | -------------------------------------------------------------------------------- /Chapter02/disconnectedCallbackButtonExample/CustomButton.js: -------------------------------------------------------------------------------- 1 | export default class CustomButton extends HTMLElement { 2 | constructor() { 3 | super(); 4 | 5 | // Initializing an initial state 6 | this.timesClicked = 0; 7 | 8 | let template = ` 9 | 10 | ${this.getTimesClicked()} 11 | `; 12 | 13 | this.innerHTML = template; 14 | } 15 | 16 | connectedCallback() { 17 | 18 | // adding event handler to the button 19 | this.querySelector('button') 20 | .addEventListener('click', this.handleClick.bind(this)); 21 | } 22 | 23 | disconnectedCallback() { 24 | console.log('We are inside disconnectedCallback'); 25 | 26 | // adding event handler to the button 27 | this.querySelector('button') 28 | .removeEventListener('click', this.handleClick); 29 | } 30 | 31 | handleClick() { 32 | // updating the state 33 | this.timesClicked++; 34 | 35 | this.querySelector('span') 36 | .innerText = this.getTimesClicked(); 37 | } 38 | 39 | getTimesClicked() { 40 | return `Times Clicked: ${this.timesClicked}`; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Chapter02/disconnectedCallbackButtonExample/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Disconnected Callback Example 5 | 6 | 9 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Chapter03/Accessibility for web components/Header Image component/HeaderImage.js: -------------------------------------------------------------------------------- 1 | export default class HeaderImage extends HTMLElement { 2 | constructor() { 3 | 4 | // We are not even going to touch this. 5 | super(); 6 | this.altText = ''; 7 | 8 | // lets create our shadow root 9 | this.shadowObj = this.attachShadow({mode: 'open'}); 10 | 11 | // Then lets render the template 12 | this.render(); 13 | } 14 | 15 | render() { 16 | this.shadowObj.innerHTML = this.getTemplate(); 17 | } 18 | 19 | getTemplate() { 20 | return ` 21 | ${this.getAttribute('alt')} 23 | ${this.handleErrors()} 24 | 29 | `; 30 | } 31 | 32 | handleErrors() { 33 | if(!this.getAttribute('alt')) { 34 | return ` 35 |
Missing Alt Text
36 | 41 | `; 42 | } 43 | 44 | return ``; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Chapter03/Accessibility for web components/Header Image component/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Header Image 5 | 6 | 9 | 16 | 17 | 18 | 19 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Chapter03/Styling Web Component/company header component with shadow dom/CompanyHeader.js: -------------------------------------------------------------------------------- 1 | export default class CompanyHeader extends HTMLElement { 2 | constructor() { 3 | 4 | // We are not even going to touch this. 5 | super(); 6 | 7 | // Lets provide a default icon 8 | this.icon = 'newicon.jpeg'; 9 | 10 | // lets create our shadow root 11 | this.shadowObj = this.attachShadow({mode: 'open'}); 12 | 13 | // Then lets render the template 14 | this.render(); 15 | } 16 | 17 | render() { 18 | 19 | this.shadowObj.innerHTML = this.getTemplate(); 20 | } 21 | 22 | // Lets get icon and page-name from attributes 23 | static get observedAttributes() { 24 | return ['icon', 'page-name']; 25 | } 26 | 27 | attributeChangedCallback(name, oldValue, newValue) { 28 | if (name == 'icon') { 29 | this.icon = newValue; 30 | } 31 | 32 | if (name == 'page-name') { 33 | this.pageName = newValue; 34 | } 35 | 36 | // Lets re-render after getting the new attributes 37 | this.render(); 38 | } 39 | 40 | getTemplate() { 41 | return ` 42 | 43 | 44 | 45 |

${this.pageName}

46 |
47 | Home 48 | About Us 49 |
50 | 73 | `; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Chapter03/Styling Web Component/company header component with shadow dom/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Getting-Started-with-Web-Components/3f9ae36ca523e2239315cabf948b935b80ddfce5/Chapter03/Styling Web Component/company header component with shadow dom/icon.png -------------------------------------------------------------------------------- /Chapter03/Styling Web Component/company header component with shadow dom/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Custom header 5 | 6 | 9 | 16 | 17 | 18 | 19 | 20 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Chapter03/Styling Web Component/company header component with shadow dom/newicon.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Getting-Started-with-Web-Components/3f9ae36ca523e2239315cabf948b935b80ddfce5/Chapter03/Styling Web Component/company header component with shadow dom/newicon.jpeg -------------------------------------------------------------------------------- /Chapter03/Styling Web Component/company header component/CompanyHeader.js: -------------------------------------------------------------------------------- 1 | export default class CompanyHeader extends HTMLElement { 2 | constructor() { 3 | 4 | // We are not even going to touch this. 5 | super(); 6 | 7 | // Lets provide a default icon 8 | this.icon = 'newicon.jpeg'; 9 | 10 | // Then lets render the template 11 | this.render(); 12 | } 13 | 14 | render() { 15 | this.innerHTML = this.getTemplate(); 16 | } 17 | 18 | // Lets get icon and page-name from attributes 19 | static get observedAttributes() { 20 | return ['icon', 'page-name']; 21 | } 22 | 23 | attributeChangedCallback(name, oldValue, newValue) { 24 | if (name == 'icon') { 25 | this.icon = newValue; 26 | } 27 | 28 | if (name == 'page-name') { 29 | this.pageName = newValue; 30 | } 31 | 32 | // Lets re-render after getting the new attributes 33 | this.render(); 34 | } 35 | 36 | getTemplate() { 37 | return ` 38 | 39 | 40 | 41 |

${this.pageName}

42 |
43 | Home 44 | About Us 45 |
46 | `; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Chapter03/Styling Web Component/company header component/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Getting-Started-with-Web-Components/3f9ae36ca523e2239315cabf948b935b80ddfce5/Chapter03/Styling Web Component/company header component/icon.png -------------------------------------------------------------------------------- /Chapter03/Styling Web Component/company header component/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Custom header 5 | 6 | 9 | 16 | 17 | 40 | 41 | 42 | 43 | 44 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /Chapter03/Styling Web Component/company header component/newicon.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Getting-Started-with-Web-Components/3f9ae36ca523e2239315cabf948b935b80ddfce5/Chapter03/Styling Web Component/company header component/newicon.jpeg -------------------------------------------------------------------------------- /Chapter03/Styling Web Component/company-login/CompanyLogin.js: -------------------------------------------------------------------------------- 1 | export default class CompanyLogin extends HTMLElement { 2 | constructor() { 3 | 4 | // We are not even going to touch this. 5 | super(); 6 | 7 | // Lets provide a default icon 8 | this.username = ''; 9 | this.password = ''; 10 | 11 | // lets create our shadow root 12 | this.shadowObj = this.attachShadow({mode: 'open'}); 13 | 14 | // Then lets render the template 15 | this.render(); 16 | } 17 | 18 | connectedCallback() { 19 | this.shadowObj.querySelector('button') 20 | .addEventListener('click', (e) => this.handleLogin(e)); 21 | } 22 | 23 | render() { 24 | this.shadowObj.innerHTML = this.getTemplate(); 25 | } 26 | 27 | getTemplate() { 28 | return ` 29 | 30 | 31 | 32 | 65 | `; 66 | } 67 | 68 | handleLogin(e) { 69 | this.username = this.shadowObj.querySelector('[name=username]').value; 70 | this.password = this.shadowObj.querySelector('[name=password]').value; 71 | 72 | // Do what ever you want with these values 73 | console.log(this.username, this.password); 74 | 75 | // We will do things as per our requirement 76 | let loginSuccess = Math.random(); 77 | if(loginSuccess > 0.5) { 78 | this.classList.remove('login-failure'); 79 | this.classList.add('login-success'); 80 | 81 | } else { 82 | this.classList.remove('login-success'); 83 | this.classList.add('login-failure'); 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /Chapter03/Styling Web Component/company-login/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Company Login 5 | 6 | 9 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Chapter04/company-header/CompanyHeader.js: -------------------------------------------------------------------------------- 1 | export default class CompanyHeader extends HTMLElement { 2 | constructor() { 3 | 4 | // We are not even going to touch this. 5 | super(); 6 | 7 | // lets create our shadow root 8 | this.shadowObj = this.attachShadow({mode: 'open'}); 9 | 10 | // Then lets render the template 11 | this.render(); 12 | } 13 | 14 | render() { 15 | 16 | this.shadowObj.innerHTML = this.getTemplate(); 17 | } 18 | 19 | getTemplate() { 20 | return ` 21 | 22 | 23 | 24 |

${this.getAttribute('page-name')}

25 |
26 | 27 |
28 | ${this.getStyle()} 29 | `; 30 | } 31 | 32 | getStyle() { 33 | return ` 34 | 52 | `; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Chapter04/company-header/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Getting-Started-with-Web-Components/3f9ae36ca523e2239315cabf948b935b80ddfce5/Chapter04/company-header/icon.png -------------------------------------------------------------------------------- /Chapter04/company-header/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Custom header 5 | 6 | 9 | 16 | 17 | 31 | 32 | 33 | 34 | 37 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /Chapter04/company-header/newicon.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Getting-Started-with-Web-Components/3f9ae36ca523e2239315cabf948b935b80ddfce5/Chapter04/company-header/newicon.jpeg -------------------------------------------------------------------------------- /Chapter04/my-article/MyArticle.js: -------------------------------------------------------------------------------- 1 | export default class MyArticle extends HTMLElement { 2 | constructor() { 3 | 4 | // We are not even going to touch this. 5 | super(); 6 | 7 | // lets create our shadow root 8 | this.shadowObj = this.attachShadow({mode: 'open'}); 9 | 10 | // Then lets render the template 11 | this.render(); 12 | } 13 | 14 | render() { 15 | 16 | this.shadowObj.innerHTML = this.getTemplate(); 17 | } 18 | 19 | getTemplate() { 20 | return ` 21 |

22 | 23 |

24 |
25 | 26 |
27 |
28 | 29 |
30 | ${this.getStyle()} 31 | `; 32 | } 33 | 34 | getStyle() { 35 | return ` 36 | 65 | `; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Chapter04/my-article/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Getting-Started-with-Web-Components/3f9ae36ca523e2239315cabf948b935b80ddfce5/Chapter04/my-article/icon.png -------------------------------------------------------------------------------- /Chapter04/my-article/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | My Article 5 | 6 | 9 | 16 | 17 | 18 | 19 | 20 | A random article 21 | Prateek Jadhwani 22 |
23 |

This is a demo paragraph

24 |

This is another demo paragraph

25 |
26 |
27 | 28 | 29 | -------------------------------------------------------------------------------- /Chapter04/my-article/newicon.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Getting-Started-with-Web-Components/3f9ae36ca523e2239315cabf948b935b80ddfce5/Chapter04/my-article/newicon.jpeg -------------------------------------------------------------------------------- /Chapter04/profile-info/Demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Profile Information 5 | 6 | 9 | 16 | 17 | 18 | 19 | 20 | 26 | 27 | 28 | 34 | 35 | 36 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /Chapter04/profile-info/Demo/john-doe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Getting-Started-with-Web-Components/3f9ae36ca523e2239315cabf948b935b80ddfce5/Chapter04/profile-info/Demo/john-doe.png -------------------------------------------------------------------------------- /Chapter04/profile-info/ProfileInfo.js: -------------------------------------------------------------------------------- 1 | export default class ProfileInfo extends HTMLElement { 2 | constructor() { 3 | 4 | // We are not even going to touch this. 5 | super(); 6 | 7 | // lets create our shadow root 8 | this.shadowObj = this.attachShadow({mode: 'open'}); 9 | 10 | // Then lets render the template 11 | this.render(); 12 | } 13 | 14 | render() { 15 | this.shadowObj.innerHTML = this.getTemplate(); 16 | 17 | this.updateCardBackground(); 18 | } 19 | 20 | getTemplate() { 21 | return ` 22 |
23 | 25 |
26 |
27 | ${this.getAttribute('name')} 28 |
29 |
30 | ${this.getAttribute('designation')} 31 |
32 |
33 | ${this.getAttribute('id-number')} 34 |
35 |
36 |
37 | ${this.getStyles()} 38 | `; 39 | } 40 | 41 | getStyles() { 42 | return ` 43 | 97 | `; 98 | } 99 | 100 | updateCardBackground() { 101 | this.classList.add(`profile-info__emp-type-${this.getAttribute('employee-type')}`); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /Chapter04/profile-info/README.md: -------------------------------------------------------------------------------- 1 | # profile-info 2 | 3 | [![Published on webcomponents.org](https://img.shields.io/badge/webcomponents.org-published-blue.svg)](https://www.webcomponents.org/element/prateekjadhwani/profile-info) 4 | 5 | A web component that shows information about an employee in the form of a profile card. 6 | 7 | ## Demo 8 | 9 | 31 | ```html 32 | 38 | 39 | ``` 40 | -------------------------------------------------------------------------------- /Chapter04/profile-info/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "profile-info", 3 | "version": "0.0.4", 4 | "description": "A webcomponent that shows information about an employee in the form of a profile card.", 5 | "main": "ProfileInfo.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [ 10 | "webcomponent", 11 | "component", 12 | "profile", 13 | "info", 14 | "employee" 15 | ], 16 | "author": "Prateek Jadhwani", 17 | "license": "ISC", 18 | "repository": "https://github.com/prateekjadhwani/profile-info" 19 | } 20 | -------------------------------------------------------------------------------- /Chapter05/attributes and props/StudentList.js: -------------------------------------------------------------------------------- 1 | export default class StudentList extends HTMLElement { 2 | constructor() { 3 | 4 | // We are not even going to touch this. 5 | super(); 6 | 7 | // Initially, the list is empty 8 | this._list = []; 9 | 10 | // lets create our shadow root 11 | this.shadowObj = this.attachShadow({mode: 'open'}); 12 | this.render(); 13 | } 14 | 15 | connectedCallback() { 16 | 17 | // what should happen when the button is clicked 18 | this.shadowObj.querySelector('.js-addButton') 19 | .addEventListener("click", (e) => { 20 | this.handleAdd(e); 21 | }); 22 | } 23 | 24 | set students (value) { 25 | 26 | this._list = value; 27 | this.renderList(); 28 | } 29 | 30 | get students (){ 31 | return this._list; 32 | } 33 | 34 | render() { 35 | this.shadowObj.innerHTML = this.getTemplate(); 36 | } 37 | 38 | renderList() { 39 | this.shadowObj.querySelector('.student-list__student-list').innerHTML 40 | = this.getStudents(); 41 | } 42 | 43 | handleAdd() { 44 | let value = this.shadowObj.querySelector('input[name=student-name]').value; 45 | this._list.push(value); 46 | this.setAttribute("students", this._list); 47 | this.renderList(); 48 | } 49 | 50 | getTemplate() { 51 | return ` 52 |
53 | 56 | 57 |
58 |
59 |
Student List
60 |
61 | ${this.getStudents()} 62 |
63 |
64 | ${this.getStyle()} 65 | `; 66 | } 67 | 68 | getStyle() { 69 | return ` 70 | 109 | `; 110 | } 111 | 112 | getStudents() { 113 | return this._list.map((item, num) => { 114 | return `
${num + 1}. ${item}
`; 115 | }).join(''); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /Chapter05/attributes and props/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Student List 5 | 6 | 9 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /Chapter05/event handling/CustomClicker.js: -------------------------------------------------------------------------------- 1 | export default class CustomClicker extends HTMLElement { 2 | constructor() { 3 | 4 | // We are not even going to touch this. 5 | super(); 6 | 7 | // Initially, the list is empty 8 | this._num = 0; 9 | 10 | // lets create our shadow root 11 | this.shadowObj = this.attachShadow({mode: 'open'}); 12 | this.render(); 13 | } 14 | 15 | connectedCallback() { 16 | 17 | // what should happen when the button is clicked 18 | this.shadowObj.querySelector('.js-button') 19 | .addEventListener("click", (e) => { 20 | this.handleClick(e); 21 | }); 22 | } 23 | 24 | render() { 25 | this.shadowObj.innerHTML = this.getTemplate(); 26 | } 27 | 28 | handleClick() { 29 | this._num++; 30 | this.shadowObj.querySelector('.custom-clicker__num').innerHTML = this.getTimesClicked(); 31 | 32 | this.dispatchEvent(new CustomEvent('change', { 33 | detail: { 34 | num: this._num, 35 | }, 36 | bubbles: true, 37 | })); 38 | } 39 | 40 | getTemplate() { 41 | return ` 42 |
43 |
${this.getTimesClicked()}
44 | 45 |
46 | ${this.getStyle()} 47 | `; 48 | } 49 | 50 | getStyle() { 51 | return ` 52 | 64 | `; 65 | } 66 | 67 | getTimesClicked() { 68 | return `${this._num} times clicked.`; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Chapter05/event handling/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Custom Clicker 5 | 6 | 9 | 16 | 17 | 18 | 19 | 20 | 21 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /Chapter05/online-checker/OnlineChecker.js: -------------------------------------------------------------------------------- 1 | export default class OnlineChecker extends HTMLElement { 2 | constructor() { 3 | 4 | // We are not even going to touch this. 5 | super(); 6 | 7 | this._isOnline = false; 8 | 9 | // lets create our shadow root 10 | this.shadowObj = this.attachShadow({mode: 'open'}); 11 | } 12 | 13 | connectedCallback() { 14 | this.isOnline = navigator.onLine; 15 | this.render(); 16 | } 17 | 18 | set isOnline (value) { 19 | if(value !== this._isOnline) { 20 | this._isOnline = value; 21 | this.render(); 22 | } 23 | } 24 | 25 | get isOnline (){ 26 | return this._isOnline; 27 | } 28 | 29 | render() { 30 | this.shadowObj.innerHTML = this.getTemplate(); 31 | } 32 | 33 | getTemplate() { 34 | return ` 35 | 36 | ${this._isOnline ? 'Online' : 'Offline'} 37 | ${this.getStyle()} 38 | `; 39 | } 40 | 41 | getStyle() { 42 | return ` 43 | 63 | `; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Chapter05/online-checker/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Online Checker 5 | 6 | 9 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /Chapter06/Game Information App/README.md: -------------------------------------------------------------------------------- 1 | # Starter Project 2 | 3 | ## How to start 4 | 5 | ### First Run / Setup 6 | 7 | ```shell 8 | cd Chapter\ 6/Starter\ Project/ 9 | npm install 10 | ``` 11 | ### How To Run 12 | 13 | ```shell 14 | npm start 15 | ``` 16 | Then open your browser to http://localhost:3000 17 | -------------------------------------------------------------------------------- /Chapter06/Game Information App/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | My App 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /Chapter06/Game Information App/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "starter-project", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "scripts": { 6 | "build": "node webpack -p --optimize-minimize", 7 | "start": "node webpack.dev.server" 8 | }, 9 | "devDependencies": { 10 | "babel-core": "^6.25.0", 11 | "babel-loader": "^7.1.1", 12 | "babel-plugin-transform-runtime": "^6.23.0", 13 | "babel-preset-env": "^1.5.2", 14 | "babel-preset-es2015": "^6.24.1", 15 | "babel-preset-flow": "^6.23.0", 16 | "babel-runtime": "^6.23.0", 17 | "css-loader": "^0.28.4", 18 | "node-sass": "^4.5.3", 19 | "sass-loader": "^6.0.6", 20 | "style-loader": "^0.18.2", 21 | "webpack": "^3.0.0", 22 | "webpack-cli": "^3.3.2", 23 | "webpack-dev-server": "^2.5.0" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Chapter06/Game Information App/src/components/custom-header/index.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | export default class CustomHeader extends HTMLElement { 4 | constructor() { 5 | 6 | // We are not even going to touch this. 7 | super(); 8 | 9 | // lets create our shadow root 10 | this.shadowObj = this.attachShadow({mode: 'open'}); 11 | 12 | this.render(); 13 | } 14 | 15 | connectedCallback() { 16 | this.shadowObj.querySelectorAll('.custom-header__li a') 17 | .forEach((aTag, index) => { 18 | aTag.addEventListener('click', (e) => { 19 | this.handleClick(index); 20 | }); 21 | }); 22 | } 23 | 24 | render() { 25 | this.shadowObj.innerHTML = this.getTemplate(); 26 | } 27 | 28 | handleClick(index) { 29 | this.dispatchEvent(new CustomEvent('custom-header-clicked', { 30 | detail: { 31 | data: index + 1, 32 | }, 33 | bubbles: true, 34 | })); 35 | } 36 | 37 | getTemplate() { 38 | return ` 39 | 50 | ${this.getStyle()} 51 | `; 52 | } 53 | 54 | getStyle() { 55 | return ` 56 | 83 | `; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /Chapter06/Game Information App/src/components/gif-cover/index.js: -------------------------------------------------------------------------------- 1 | export default class GifCover extends HTMLElement { 2 | constructor() { 3 | 4 | // We are not even going to touch this. 5 | super(); 6 | 7 | // lets get the url from attribute 8 | this.url = this.getAttribute('url'); 9 | 10 | // lets create our shadow root 11 | this.shadowObj = this.attachShadow({mode: 'open'}); 12 | 13 | this.render(); 14 | } 15 | 16 | render() { 17 | this.shadowObj.innerHTML = this.getTemplate(); 18 | } 19 | 20 | getTemplate() { 21 | return ` 22 |
23 | 25 |
26 | ${this.getStyle()} 27 | `; 28 | } 29 | 30 | getStyle() { 31 | return ` 32 | 40 | `; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Chapter06/Game Information App/src/components/my-app/index.js: -------------------------------------------------------------------------------- 1 | import CustomHeader from '../custom-header'; 2 | import SearchContainer from '../search-container'; 3 | import ShowTrending from '../show-trending'; 4 | import ShowRandom from '../show-random'; 5 | 6 | export default class MyApp extends HTMLElement { 7 | constructor() { 8 | 9 | // We are not even going to touch this. 10 | super(); 11 | 12 | // to show what section 13 | this.shownSection = 1; 14 | 15 | // lets create our shadow root 16 | this.shadowObj = this.attachShadow({mode: 'open'}); 17 | 18 | // lets register other elements 19 | this.registerOtherComponents(); 20 | 21 | this.handleURL(); 22 | } 23 | 24 | registerOtherComponents() { 25 | if (typeof customElements.get('custom-header') === 'undefined') { 26 | customElements.define('custom-header', CustomHeader); 27 | } 28 | 29 | if (typeof customElements.get('search-container') === 'undefined') { 30 | customElements.define('search-container', SearchContainer); 31 | } 32 | 33 | if (typeof customElements.get('show-trending') === 'undefined') { 34 | customElements.define('show-trending', ShowTrending); 35 | } 36 | 37 | if (typeof customElements.get('show-random') === 'undefined') { 38 | customElements.define('show-random', ShowRandom); 39 | } 40 | } 41 | 42 | connectedCallback() { 43 | this.shadowObj.querySelector('custom-header') 44 | .addEventListener('custom-header-clicked', (e) => { 45 | let newShownSection = e.detail.data; 46 | if(newShownSection !== this.shownSection) { 47 | this.shownSection = newShownSection; 48 | this.reRenderAppSection(); 49 | } 50 | }) 51 | } 52 | 53 | reRenderAppSection() { 54 | this.shadowObj.querySelector('.app-section').innerHTML = this.getSection(this.shownSection); 55 | } 56 | 57 | render() { 58 | this.shadowObj.innerHTML = this.getTemplate(); 59 | } 60 | 61 | getTemplate() { 62 | return ` 63 | 64 |
65 | ${this.getSection(this.shownSection)} 66 |
67 | ${this.getStyle()} 68 | `; 69 | } 70 | 71 | getSection(section) { 72 | switch(section) { 73 | case 1: 74 | return ` 75 | 76 | `; 77 | case 2: 78 | return ` 79 | 80 | `; 81 | case 3: 82 | return ` 83 | 84 | `; 85 | } 86 | } 87 | 88 | getStyle() { 89 | return ` 90 | 95 | `; 96 | } 97 | 98 | handleURL() { 99 | switch(window.location.hash) { 100 | case '#search': 101 | this.shownSection = 1; 102 | break; 103 | case '#trending': 104 | this.shownSection = 2; 105 | break; 106 | case '#random': 107 | this.shownSection = 3; 108 | break; 109 | default: 110 | this.shownSection = 1; 111 | break; 112 | } 113 | 114 | this.render(); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /Chapter06/Game Information App/src/components/search-bar/index.js: -------------------------------------------------------------------------------- 1 | export default class SearchBar extends HTMLElement { 2 | constructor() { 3 | 4 | // We are not even going to touch this. 5 | super(); 6 | 7 | this.key = 'fmuxd9jT3qfcEmMXnXDBGxA4NvWgNA6S'; 8 | this.searchUrl = 'https://api.giphy.com/v1/gifs/search'; 9 | this.showlimit = 20; 10 | 11 | // lets create our shadow root 12 | this.shadowObj = this.attachShadow({mode: 'open'}); 13 | 14 | this.render(); 15 | } 16 | 17 | connectedCallback() { 18 | this.shadowObj.querySelector('button') 19 | .addEventListener('click', (e) => { 20 | this.handleSearch(); 21 | }); 22 | } 23 | 24 | handleSearch() { 25 | let value = this.shadowObj.querySelector('input').value; 26 | 27 | fetch(`${this.searchUrl}?api_key=${this.key}&q=${value}&limit=${this.showlimit}`) 28 | .then(response => response.json()) 29 | .then((jsonResponse) => { 30 | this.dispatchDataInEvent(jsonResponse.data); 31 | }); 32 | 33 | } 34 | 35 | dispatchDataInEvent(data) { 36 | this.dispatchEvent(new CustomEvent('search-complete', { 37 | detail: { 38 | data: data, 39 | }, 40 | bubbles: true, 41 | })); 42 | } 43 | 44 | render() { 45 | this.shadowObj.innerHTML = this.getTemplate(); 46 | } 47 | 48 | getTemplate() { 49 | return ` 50 |
51 | 54 | 55 |
56 | ${this.getStyle()} 57 | `; 58 | } 59 | 60 | getStyle() { 61 | return ` 62 | 91 | `; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /Chapter06/Game Information App/src/components/search-container/index.js: -------------------------------------------------------------------------------- 1 | import SearchBar from '../search-bar'; 2 | import GifCover from '../gif-cover'; 3 | 4 | export default class SearchContainer extends HTMLElement { 5 | constructor() { 6 | 7 | // We are not even going to touch this. 8 | super(); 9 | 10 | // lets create our shadow root 11 | this.shadowObj = this.attachShadow({mode: 'open'}); 12 | 13 | this.registerOtherComponents(); 14 | this.render(); 15 | } 16 | 17 | connectedCallback() { 18 | this.shadowObj.querySelector('search-bar') 19 | .addEventListener('search-complete', (e) => { 20 | this.handleSearchData(e.detail.data); 21 | }); 22 | } 23 | 24 | handleSearchData(data) { 25 | data = data.map((val, index) => { 26 | return ` 27 | 28 | `; 29 | }).join(''); 30 | this.shadowObj.querySelector('.search-container__images') 31 | .innerHTML = data; 32 | } 33 | 34 | 35 | 36 | registerOtherComponents() { 37 | if (typeof customElements.get('search-bar') === 'undefined') { 38 | customElements.define('search-bar', SearchBar); 39 | } 40 | 41 | if (typeof customElements.get('gif-cover') === 'undefined') { 42 | customElements.define('gif-cover', GifCover); 43 | } 44 | } 45 | 46 | render() { 47 | this.shadowObj.innerHTML = this.getTemplate(); 48 | } 49 | 50 | getTemplate() { 51 | return ` 52 |
53 | 54 |
55 |

Try Searching for a tag in the search bar

56 |
57 |
58 | ${this.getStyle()} 59 | `; 60 | } 61 | 62 | getStyle() { 63 | return ` 64 | 84 | `; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /Chapter06/Game Information App/src/components/show-random/index.js: -------------------------------------------------------------------------------- 1 | import GifCover from '../gif-cover'; 2 | 3 | export default class ShowRandom extends HTMLElement { 4 | constructor() { 5 | 6 | // We are not even going to touch this. 7 | super(); 8 | 9 | // the key required for api 10 | this.key = 'fmuxd9jT3qfcEmMXnXDBGxA4NvWgNA6S'; 11 | 12 | // the url used to get the random gif 13 | this.url = 'https://api.giphy.com/v1/gifs/random'; 14 | 15 | // lets create our shadow root 16 | this.shadowObj = this.attachShadow({mode: 'open'}); 17 | 18 | this.registerOtherComponents(); 19 | this.render(); 20 | } 21 | 22 | registerOtherComponents() { 23 | // lets register other components used 24 | if (typeof customElements.get('gif-cover') === 'undefined') { 25 | customElements.define('gif-cover', GifCover); 26 | } 27 | } 28 | 29 | connectedCallback() { 30 | this.handleRandom(); 31 | 32 | this.shadowObj.querySelector('button') 33 | .addEventListener('click', (e) => { 34 | this.handleRandom(); 35 | }); 36 | } 37 | 38 | handleRandom() { 39 | fetch(`${this.url}?api_key=${this.key}`) 40 | .then(response => response.json()) 41 | .then((jsonResponse) => { 42 | this.handleTrendingData(jsonResponse.data); 43 | }); 44 | 45 | } 46 | 47 | handleTrendingData(data) { 48 | 49 | this.shadowObj.querySelector('.show-random__images') 50 | .innerHTML = ` 51 | 52 | `; 53 | } 54 | 55 | render() { 56 | this.shadowObj.innerHTML = this.getTemplate(); 57 | } 58 | 59 | getTemplate() { 60 | return ` 61 |
62 |
63 | 64 |
65 | ${this.getStyle()} 66 | `; 67 | } 68 | 69 | getStyle() { 70 | return ` 71 | 100 | `; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /Chapter06/Game Information App/src/components/show-trending/index.js: -------------------------------------------------------------------------------- 1 | import GifCover from '../gif-cover'; 2 | 3 | export default class ShowTrending extends HTMLElement { 4 | constructor() { 5 | 6 | // We are not even going to touch this. 7 | super(); 8 | 9 | this.key = 'fmuxd9jT3qfcEmMXnXDBGxA4NvWgNA6S'; 10 | this.url = 'https://api.giphy.com/v1/gifs/trending'; 11 | this.showlimit = 20; 12 | 13 | // lets create our shadow root 14 | this.shadowObj = this.attachShadow({mode: 'open'}); 15 | 16 | this.registerOtherComponents(); 17 | this.render(); 18 | } 19 | 20 | registerOtherComponents() { 21 | if (typeof customElements.get('gif-cover') === 'undefined') { 22 | customElements.define('gif-cover', GifCover); 23 | } 24 | } 25 | 26 | connectedCallback() { 27 | this.makeApiCall(); 28 | } 29 | 30 | makeApiCall() { 31 | fetch(`${this.url}?api_key=${this.key}&limit=${this.showlimit}`) 32 | .then(response => response.json()) 33 | .then((jsonResponse) => { 34 | this.handleTrendingData(jsonResponse.data); 35 | }); 36 | 37 | } 38 | 39 | handleTrendingData(data) { 40 | 41 | data = data.map((val, index) => { 42 | return ` 43 | 44 | `; 45 | }).join(''); 46 | 47 | this.shadowObj.querySelector('.show-trending__images') 48 | .innerHTML = data; 49 | } 50 | 51 | render() { 52 | this.shadowObj.innerHTML = this.getTemplate(); 53 | } 54 | 55 | getTemplate() { 56 | return ` 57 | 61 | ${this.getStyle()} 62 | `; 63 | } 64 | 65 | getStyle() { 66 | return ` 67 | 87 | `; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /Chapter06/Game Information App/src/index.js: -------------------------------------------------------------------------------- 1 | import './styles.scss'; 2 | 3 | import MyApp from './components/my-app'; 4 | customElements.define('my-app', MyApp); 5 | -------------------------------------------------------------------------------- /Chapter06/Game Information App/src/styles.scss: -------------------------------------------------------------------------------- 1 | html, body { 2 | margin: 0; 3 | } 4 | body { 5 | background: #f6f6ef; 6 | } 7 | -------------------------------------------------------------------------------- /Chapter06/Game Information App/webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | 3 | module.exports = { 4 | entry: './src/index.js', 5 | module: { 6 | rules: [ 7 | { test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader' }, 8 | { 9 | test: /\.scss$/, 10 | use: [ 11 | { 12 | loader: 'style-loader' 13 | }, 14 | { 15 | loader: 'css-loader' 16 | }, 17 | { 18 | loader: 'sass-loader' 19 | } 20 | ] 21 | } 22 | ] 23 | }, 24 | output: { 25 | filename: 'bundle.js', 26 | path: path.resolve(__dirname, './dist') 27 | 28 | }, 29 | performance: { 30 | hints: false 31 | }, 32 | devServer: { 33 | publicPath: path.resolve(__dirname, './dist'), 34 | writeToDisk: true 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /Chapter06/Game Information App/webpack.dev.server.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var WebpackDevServer = require('webpack-dev-server'); 3 | var config = require('./webpack.config'); 4 | 5 | new WebpackDevServer(webpack(config), { 6 | headers: { 'Access-Control-Allow-Origin': '*' } 7 | }).listen('8000'); 8 | -------------------------------------------------------------------------------- /Chapter06/Starter Project/README.md: -------------------------------------------------------------------------------- 1 | # Starter Project 2 | 3 | ## How to start 4 | 5 | ### First Run / Setup 6 | 7 | ```shell 8 | cd Chapter\ 6/Starter\ Project/ 9 | npm install 10 | ``` 11 | ### How To Run 12 | 13 | ```shell 14 | npm start 15 | ``` 16 | Then open your browser to http://localhost:3000 17 | -------------------------------------------------------------------------------- /Chapter06/Starter Project/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | My App 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Chapter06/Starter Project/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "starter-project", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "scripts": { 6 | "build": "webpack -p --optimize-minimize", 7 | "start": "node webpack.dev.server" 8 | }, 9 | "dependencies": {}, 10 | "devDependencies": { 11 | "babel-core": "^6.25.0", 12 | "babel-loader": "^7.1.1", 13 | "babel-plugin-transform-runtime": "^6.23.0", 14 | "babel-preset-env": "^1.5.2", 15 | "babel-preset-es2015": "^6.24.1", 16 | "babel-preset-flow": "^6.23.0", 17 | "babel-runtime": "^6.23.0", 18 | "css-loader": "^0.28.4", 19 | "node-sass": "^4.5.3", 20 | "sass-loader": "^6.0.6", 21 | "style-loader": "^0.18.2", 22 | "webpack": "^3.0.0", 23 | "webpack-dev-server": "^2.5.0" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Chapter06/Starter Project/src/components/my-app/index.js: -------------------------------------------------------------------------------- 1 | export default class MyApp extends HTMLElement { 2 | constructor() { 3 | 4 | // We are not even going to touch this. 5 | super(); 6 | 7 | // lets create our shadow root 8 | this.shadowObj = this.attachShadow({mode: 'open'}); 9 | this.render(); 10 | } 11 | 12 | render() { 13 | this.shadowObj.innerHTML = this.getTemplate(); 14 | } 15 | 16 | getTemplate() { 17 | return ` 18 |
19 | My App 20 |
21 | ${this.getStyle()} 22 | `; 23 | } 24 | 25 | getStyle() { 26 | return ` 27 | 32 | `; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Chapter06/Starter Project/src/index.js: -------------------------------------------------------------------------------- 1 | import './styles.scss'; 2 | 3 | import MyApp from './components/my-app'; 4 | customElements.define('my-app', MyApp); 5 | 6 | console.log("Ready..."); 7 | -------------------------------------------------------------------------------- /Chapter06/Starter Project/src/styles.scss: -------------------------------------------------------------------------------- 1 | html, body { 2 | margin: 0; 3 | } 4 | body { 5 | background: #f6f6ef; 6 | } 7 | -------------------------------------------------------------------------------- /Chapter06/Starter Project/webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | 3 | module.exports = { 4 | entry: './src/index.js', 5 | module: { 6 | rules: [ 7 | { test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader' }, 8 | { 9 | test: /\.scss$/, 10 | use: [ 11 | { 12 | loader: 'style-loader' 13 | }, 14 | { 15 | loader: 'css-loader' 16 | }, 17 | { 18 | loader: 'sass-loader' 19 | } 20 | ] 21 | } 22 | ] 23 | }, 24 | output: { 25 | filename: 'bundle.js', 26 | path: path.resolve(__dirname, 'dist') 27 | }, 28 | performance: { 29 | hints: false 30 | } 31 | }; 32 | -------------------------------------------------------------------------------- /Chapter06/Starter Project/webpack.dev.server.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var WebpackDevServer = require('webpack-dev-server'); 3 | var config = require('./webpack.config'); 4 | 5 | new WebpackDevServer(webpack(config), { 6 | headers: { 'Access-Control-Allow-Origin': '*' } 7 | }).listen('3000'); 8 | -------------------------------------------------------------------------------- /Chapter07/Polymer/hello-string.js: -------------------------------------------------------------------------------- 1 | import { PolymerElement, html } from '@polymer/polymer/polymer-element.js'; 2 | 3 | class HelloString extends PolymerElement { 4 | constructor() { 5 | super(); 6 | } 7 | 8 | static get properties() { 9 | return { 10 | name: { 11 | type: String, 12 | value: 'No Name Provided Yet' 13 | } 14 | }; 15 | } 16 | 17 | static get template() { 18 | return html` 19 |

Hello, [[name]]

20 | `; 21 | } 22 | } 23 | 24 | customElements.define('hello-string', HelloString); 25 | -------------------------------------------------------------------------------- /Chapter07/Polymer/hello-world.js: -------------------------------------------------------------------------------- 1 | import { PolymerElement, html } from '@polymer/polymer/polymer-element.js'; 2 | import './second-element.js'; 3 | 4 | class HelloWorld extends PolymerElement { 5 | constructor() { 6 | super(); 7 | } 8 | static get template() { 9 | return html` 10 |

Hello World

11 | 12 | `; 13 | } 14 | } 15 | 16 | customElements.define('hello-world', HelloWorld); 17 | -------------------------------------------------------------------------------- /Chapter07/Polymer/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Chapter07/Polymer/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "polymer", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@polymer/iron-flex-layout": { 8 | "version": "3.0.1", 9 | "resolved": "https://registry.npmjs.org/@polymer/iron-flex-layout/-/iron-flex-layout-3.0.1.tgz", 10 | "integrity": "sha512-7gB869czArF+HZcPTVSgvA7tXYFze9EKckvM95NB7SqYF+NnsQyhoXgKnpFwGyo95lUjUW9TFDLUwDXnCYFtkw==", 11 | "requires": { 12 | "@polymer/polymer": "^3.0.0" 13 | } 14 | }, 15 | "@polymer/iron-icon": { 16 | "version": "3.0.1", 17 | "resolved": "https://registry.npmjs.org/@polymer/iron-icon/-/iron-icon-3.0.1.tgz", 18 | "integrity": "sha512-QLPwirk+UPZNaLnMew9VludXA4CWUCenRewgEcGYwdzVgDPCDbXxy6vRJjmweZobMQv/oVLppT2JZtJFnPxX6g==", 19 | "requires": { 20 | "@polymer/iron-flex-layout": "^3.0.0-pre.26", 21 | "@polymer/iron-meta": "^3.0.0-pre.26", 22 | "@polymer/polymer": "^3.0.0" 23 | } 24 | }, 25 | "@polymer/iron-icons": { 26 | "version": "3.0.1", 27 | "resolved": "https://registry.npmjs.org/@polymer/iron-icons/-/iron-icons-3.0.1.tgz", 28 | "integrity": "sha512-xtEI8erH2GIBiF3QxEMyW81XuVjguu6Le5WjEEpX67qd9z7jjmc4T/ke3zRUlnDydex9p8ytcwVpMIKcyvjYAQ==", 29 | "requires": { 30 | "@polymer/iron-icon": "^3.0.0-pre.26", 31 | "@polymer/iron-iconset-svg": "^3.0.0-pre.26", 32 | "@polymer/polymer": "^3.0.0" 33 | } 34 | }, 35 | "@polymer/iron-iconset-svg": { 36 | "version": "3.0.1", 37 | "resolved": "https://registry.npmjs.org/@polymer/iron-iconset-svg/-/iron-iconset-svg-3.0.1.tgz", 38 | "integrity": "sha512-XNwURbNHRw6u2fJe05O5fMYye6GSgDlDqCO+q6K1zAnKIrpgZwf2vTkBd5uCcZwsN0FyCB3mvNZx4jkh85dRDw==", 39 | "requires": { 40 | "@polymer/iron-meta": "^3.0.0-pre.26", 41 | "@polymer/polymer": "^3.0.0" 42 | } 43 | }, 44 | "@polymer/iron-meta": { 45 | "version": "3.0.1", 46 | "resolved": "https://registry.npmjs.org/@polymer/iron-meta/-/iron-meta-3.0.1.tgz", 47 | "integrity": "sha512-pWguPugiLYmWFV9UWxLWzZ6gm4wBwQdDy4VULKwdHCqR7OP7u98h+XDdGZsSlDPv6qoryV/e3tGHlTIT0mbzJA==", 48 | "requires": { 49 | "@polymer/polymer": "^3.0.0" 50 | } 51 | }, 52 | "@polymer/polymer": { 53 | "version": "3.2.0", 54 | "resolved": "https://registry.npmjs.org/@polymer/polymer/-/polymer-3.2.0.tgz", 55 | "integrity": "sha512-L6uV1oM6T6xbwbVx6t3biG5T2VSSB03LxnIrUd9M2pr6RkHVPFHJ37pC5MUwBAEhkGFJif7eks7fdMMSGZTeEQ==", 56 | "requires": { 57 | "@webcomponents/shadycss": "^1.8.0" 58 | } 59 | }, 60 | "@webcomponents/shadycss": { 61 | "version": "1.9.1", 62 | "resolved": "https://registry.npmjs.org/@webcomponents/shadycss/-/shadycss-1.9.1.tgz", 63 | "integrity": "sha512-IaZOnWOKXHghqk/WfPNDRIgDBi3RsVPY2IFAw6tYiL9UBGvQRy5R6uC+Fk7qTZsReTJ0xh5MTT8yAcb3MUR4mQ==" 64 | }, 65 | "@webcomponents/webcomponentsjs": { 66 | "version": "1.3.3", 67 | "resolved": "https://registry.npmjs.org/@webcomponents/webcomponentsjs/-/webcomponentsjs-1.3.3.tgz", 68 | "integrity": "sha512-eLH04VBMpuZGzBIhOnUjECcQPEPcmfhWEijW9u1B5I+2PPYdWf3vWUExdDxu4Y3GljRSTCOlWnGtS9tpzmXMyQ==" 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Chapter07/Polymer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "polymer", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "@polymer/polymer": "^3.0.0-pre.12", 13 | "@webcomponents/webcomponentsjs": "^1.0.20" 14 | }, 15 | "resolutions": { 16 | "@webcomponents/webcomponentsjs": "1.0.20" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Chapter07/Polymer/second-element.js: -------------------------------------------------------------------------------- 1 | import { PolymerElement, html } from '@polymer/polymer/polymer-element.js'; 2 | 3 | class SecondElement extends PolymerElement { 4 | constructor() { 5 | super(); 6 | } 7 | static get template() { 8 | return html` 9 | 14 |

This is the second element

15 | `; 16 | } 17 | } 18 | 19 | customElements.define('second-element', SecondElement); 20 | -------------------------------------------------------------------------------- /Chapter07/Polymer/student-name.js: -------------------------------------------------------------------------------- 1 | import { PolymerElement, html } from '@polymer/polymer/polymer-element.js'; 2 | import './hello-string.js'; 3 | 4 | class StudentName extends PolymerElement { 5 | constructor() { 6 | super(); 7 | } 8 | 9 | static get properties() { 10 | return { 11 | name: { 12 | type: String, 13 | value: 'John Doe' 14 | } 15 | }; 16 | } 17 | 18 | static get template() { 19 | return html` 20 | 21 | `; 22 | } 23 | } 24 | 25 | customElements.define('student-name', StudentName); 26 | -------------------------------------------------------------------------------- /Chapter07/Stencil/hello-world/.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | indent_style = space 8 | indent_size = 2 9 | end_of_line = lf 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | 13 | [*.md] 14 | insert_final_newline = false 15 | trim_trailing_whitespace = false 16 | -------------------------------------------------------------------------------- /Chapter07/Stencil/hello-world/.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | www/ 3 | 4 | *~ 5 | *.sw[mnpcod] 6 | *.log 7 | *.lock 8 | *.tmp 9 | *.tmp.* 10 | log.txt 11 | *.sublime-project 12 | *.sublime-workspace 13 | 14 | .stencil/ 15 | .idea/ 16 | .vscode/ 17 | .sass-cache/ 18 | .versions/ 19 | node_modules/ 20 | $RECYCLE.BIN/ 21 | 22 | .DS_Store 23 | Thumbs.db 24 | UserInterfaceState.xcuserstate 25 | .env 26 | -------------------------------------------------------------------------------- /Chapter07/Stencil/hello-world/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Chapter07/Stencil/hello-world/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hello-world", 3 | "version": "0.0.1", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@stencil/core": { 8 | "version": "1.0.0-alpha.32", 9 | "resolved": "https://registry.npmjs.org/@stencil/core/-/core-1.0.0-alpha.32.tgz", 10 | "integrity": "sha512-438FzH5gKkQMET2qXryuzEcVserxTMT6y9Hwg7SFK8w/W137uXNGW+ziVve8q0YC7yJpeBCej+FzseQdrFPLmQ==", 11 | "dev": true, 12 | "requires": { 13 | "typescript": "3.4.5" 14 | } 15 | }, 16 | "typescript": { 17 | "version": "3.4.5", 18 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.4.5.tgz", 19 | "integrity": "sha512-YycBxUb49UUhdNMU5aJ7z5Ej2XGmaIBL0x34vZ82fn3hGvD+bgrMrVDpatgz2f7YxUMJxMkbWxJZeAvDxVe7Vw==", 20 | "dev": true 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Chapter07/Stencil/hello-world/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hello-world", 3 | "version": "0.0.1", 4 | "description": "Stencil Component Starter", 5 | "main": "dist/index.js", 6 | "module": "dist/index.mjs", 7 | "es2015": "dist/esm/index.mjs", 8 | "es2017": "dist/esm/index.mjs", 9 | "types": "dist/types/index.d.ts", 10 | "collection": "dist/collection/collection-manifest.json", 11 | "collection:main": "dist/collection/index.js", 12 | "files": [ 13 | "dist/" 14 | ], 15 | "scripts": { 16 | "build": "stencil build --docs", 17 | "start": "stencil build --dev --watch --serve", 18 | "test": "stencil test --spec --e2e", 19 | "test.watch": "stencil test --spec --e2e --watchAll" 20 | }, 21 | "devDependencies": { 22 | "@stencil/core": "one" 23 | }, 24 | "license": "MIT" 25 | } 26 | -------------------------------------------------------------------------------- /Chapter07/Stencil/hello-world/readme.md: -------------------------------------------------------------------------------- 1 | ![Built With Stencil](https://img.shields.io/badge/-Built%20With%20Stencil-16161d.svg?logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDE5LjIuMSwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPgo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IgoJIHZpZXdCb3g9IjAgMCA1MTIgNTEyIiBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCA1MTIgNTEyOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI%2BCjxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI%2BCgkuc3Qwe2ZpbGw6I0ZGRkZGRjt9Cjwvc3R5bGU%2BCjxwYXRoIGNsYXNzPSJzdDAiIGQ9Ik00MjQuNywzNzMuOWMwLDM3LjYtNTUuMSw2OC42LTkyLjcsNjguNkgxODAuNGMtMzcuOSwwLTkyLjctMzAuNy05Mi43LTY4LjZ2LTMuNmgzMzYuOVYzNzMuOXoiLz4KPHBhdGggY2xhc3M9InN0MCIgZD0iTTQyNC43LDI5Mi4xSDE4MC40Yy0zNy42LDAtOTIuNy0zMS05Mi43LTY4LjZ2LTMuNkgzMzJjMzcuNiwwLDkyLjcsMzEsOTIuNyw2OC42VjI5Mi4xeiIvPgo8cGF0aCBjbGFzcz0ic3QwIiBkPSJNNDI0LjcsMTQxLjdIODcuN3YtMy42YzAtMzcuNiw1NC44LTY4LjYsOTIuNy02OC42SDMzMmMzNy45LDAsOTIuNywzMC43LDkyLjcsNjguNlYxNDEuN3oiLz4KPC9zdmc%2BCg%3D%3D&colorA=16161d&style=flat-square) 2 | 3 | # Stencil Component Starter 4 | 5 | This is a starter project for building a standalone Web Component using Stencil. 6 | 7 | Stencil is also great for building entire apps. For that, use the [stencil-app-starter](https://github.com/ionic-team/stencil-app-starter) instead. 8 | 9 | # Stencil 10 | 11 | Stencil is a compiler for building fast web apps using Web Components. 12 | 13 | Stencil combines the best concepts of the most popular frontend frameworks into a compile-time rather than run-time tool. Stencil takes TypeScript, JSX, a tiny virtual DOM layer, efficient one-way data binding, an asynchronous rendering pipeline (similar to React Fiber), and lazy-loading out of the box, and generates 100% standards-based Web Components that run in any browser supporting the Custom Elements v1 spec. 14 | 15 | Stencil components are just Web Components, so they work in any major framework or with no framework at all. 16 | 17 | ## Getting Started 18 | 19 | To start building a new web component using Stencil, clone this repo to a new directory: 20 | 21 | ```bash 22 | git clone https://github.com/ionic-team/stencil-component-starter.git my-component 23 | cd my-component 24 | git remote rm origin 25 | ``` 26 | 27 | and run: 28 | 29 | ```bash 30 | npm install 31 | npm start 32 | ``` 33 | 34 | To build the component for production, run: 35 | 36 | ```bash 37 | npm run build 38 | ``` 39 | 40 | To run the unit tests for the components, run: 41 | 42 | ```bash 43 | npm test 44 | ``` 45 | 46 | Need help? Check out our docs [here](https://stenciljs.com/docs/my-first-component). 47 | 48 | 49 | ## Naming Components 50 | 51 | When creating new component tags, we recommend _not_ using `stencil` in the component name (ex: ``). This is because the generated component has little to nothing to do with Stencil; it's just a web component! 52 | 53 | Instead, use a prefix that fits your company or any name for a group of related components. For example, all of the Ionic generated web components use the prefix `ion`. 54 | 55 | 56 | ## Using this component 57 | 58 | ### Script tag 59 | 60 | - [Publish to NPM](https://docs.npmjs.com/getting-started/publishing-npm-packages) 61 | - Put a script tag similar to this `` in the head of your index.html 62 | - Then you can use the element anywhere in your template, JSX, html etc 63 | 64 | ### Node Modules 65 | - Run `npm install my-component --save` 66 | - Put a script tag similar to this `` in the head of your index.html 67 | - Then you can use the element anywhere in your template, JSX, html etc 68 | 69 | ### In a stencil-starter app 70 | - Run `npm install my-component --save` 71 | - Add an import to the npm packages `import my-component;` 72 | - Then you can use the element anywhere in your template, JSX, html etc 73 | -------------------------------------------------------------------------------- /Chapter07/Stencil/hello-world/src/components.d.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | /** 3 | * This is an autogenerated file created by the Stencil compiler. 4 | * It contains typing information for all components that exist in this project. 5 | */ 6 | 7 | 8 | import { HTMLStencilElement, JSXBase } from '@stencil/core/internal'; 9 | import { JSX } from '@stencil/core'; 10 | 11 | 12 | export namespace Components { 13 | interface HelloWorld {} 14 | interface MyComponent { 15 | /** 16 | * The first name 17 | */ 18 | 'first': string; 19 | /** 20 | * The last name 21 | */ 22 | 'last': string; 23 | /** 24 | * The middle name 25 | */ 26 | 'middle': string; 27 | } 28 | } 29 | 30 | declare namespace LocalJSX { 31 | interface HelloWorld extends JSXBase.HTMLAttributes {} 32 | interface MyComponent extends JSXBase.HTMLAttributes { 33 | /** 34 | * The first name 35 | */ 36 | 'first'?: string; 37 | /** 38 | * The last name 39 | */ 40 | 'last'?: string; 41 | /** 42 | * The middle name 43 | */ 44 | 'middle'?: string; 45 | } 46 | 47 | interface ElementInterfaces { 48 | 'HelloWorld': Components.HelloWorld; 49 | 'MyComponent': Components.MyComponent; 50 | } 51 | 52 | interface IntrinsicElements { 53 | 'HelloWorld': LocalJSX.HelloWorld; 54 | 'MyComponent': LocalJSX.MyComponent; 55 | } 56 | } 57 | export { LocalJSX as JSX }; 58 | 59 | declare module "@stencil/core" { 60 | export namespace JSX { 61 | interface ElementInterfaces extends LocalJSX.ElementInterfaces {} 62 | interface IntrinsicElements extends LocalJSX.IntrinsicElements {} 63 | } 64 | } 65 | 66 | declare global { 67 | 68 | 69 | interface HTMLHelloWorldElement extends Components.HelloWorld, HTMLStencilElement {} 70 | var HTMLHelloWorldElement: { 71 | prototype: HTMLHelloWorldElement; 72 | new (): HTMLHelloWorldElement; 73 | }; 74 | 75 | interface HTMLMyComponentElement extends Components.MyComponent, HTMLStencilElement {} 76 | var HTMLMyComponentElement: { 77 | prototype: HTMLMyComponentElement; 78 | new (): HTMLMyComponentElement; 79 | }; 80 | interface HTMLElementTagNameMap { 81 | 'hello-world': HTMLHelloWorldElement 82 | 'my-component': HTMLMyComponentElement 83 | } 84 | 85 | interface ElementTagNameMap { 86 | 'hello-world': HTMLHelloWorldElement; 87 | 'my-component': HTMLMyComponentElement; 88 | } 89 | } 90 | 91 | -------------------------------------------------------------------------------- /Chapter07/Stencil/hello-world/src/components/hello-world/hello-world.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Getting-Started-with-Web-Components/3f9ae36ca523e2239315cabf948b935b80ddfce5/Chapter07/Stencil/hello-world/src/components/hello-world/hello-world.css -------------------------------------------------------------------------------- /Chapter07/Stencil/hello-world/src/components/hello-world/hello-world.tsx: -------------------------------------------------------------------------------- 1 | import { Component, h } from '@stencil/core'; 2 | 3 | @Component({ 4 | tag: 'hello-world', 5 | styleUrl: 'hello-world.css', 6 | shadow: true 7 | }) 8 | 9 | export class HelloWorld { 10 | render() { 11 | return ( 12 |
Hello World
13 | ); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Chapter07/Stencil/hello-world/src/components/my-component/my-component.css: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /Chapter07/Stencil/hello-world/src/components/my-component/my-component.e2e.ts: -------------------------------------------------------------------------------- 1 | import { newE2EPage } from '@stencil/core/testing'; 2 | 3 | describe('my-component', () => { 4 | it('renders', async () => { 5 | const page = await newE2EPage(); 6 | 7 | await page.setContent(''); 8 | const element = await page.find('my-component'); 9 | expect(element).toHaveClass('hydrated'); 10 | }); 11 | 12 | it('renders changes to the name data', async () => { 13 | const page = await newE2EPage(); 14 | 15 | await page.setContent(''); 16 | const component = await page.find('my-component'); 17 | const element = await page.find('my-component >>> div'); 18 | expect(element.textContent).toEqual(`Hello, World! I'm `); 19 | 20 | component.setProperty('first', 'James'); 21 | await page.waitForChanges(); 22 | expect(element.textContent).toEqual(`Hello, World! I'm James`); 23 | 24 | component.setProperty('last', 'Quincy'); 25 | await page.waitForChanges(); 26 | expect(element.textContent).toEqual(`Hello, World! I'm James Quincy`); 27 | 28 | component.setProperty('middle', 'Earl'); 29 | await page.waitForChanges(); 30 | expect(element.textContent).toEqual(`Hello, World! I'm James Earl Quincy`); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /Chapter07/Stencil/hello-world/src/components/my-component/my-component.tsx: -------------------------------------------------------------------------------- 1 | import { Component, Prop, h } from '@stencil/core'; 2 | import { format } from '../../utils/utils'; 3 | 4 | @Component({ 5 | tag: 'my-component', 6 | styleUrl: 'my-component.css', 7 | shadow: true 8 | }) 9 | export class MyComponent { 10 | /** 11 | * The first name 12 | */ 13 | @Prop() first: string; 14 | 15 | /** 16 | * The middle name 17 | */ 18 | @Prop() middle: string; 19 | 20 | /** 21 | * The last name 22 | */ 23 | @Prop() last: string; 24 | 25 | private getText(): string { 26 | return format(this.first, this.middle, this.last); 27 | } 28 | 29 | render() { 30 | return
Hello, World! I'm {this.getText()}
; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Chapter07/Stencil/hello-world/src/components/my-component/readme.md: -------------------------------------------------------------------------------- 1 | # my-component 2 | 3 | 4 | 5 | 6 | 7 | 8 | ## Properties 9 | 10 | | Property | Attribute | Description | Type | Default | 11 | | -------- | --------- | --------------- | -------- | ----------- | 12 | | `first` | `first` | The first name | `string` | `undefined` | 13 | | `last` | `last` | The last name | `string` | `undefined` | 14 | | `middle` | `middle` | The middle name | `string` | `undefined` | 15 | 16 | 17 | ---------------------------------------------- 18 | 19 | *Built with [StencilJS](https://stenciljs.com/)* 20 | -------------------------------------------------------------------------------- /Chapter07/Stencil/hello-world/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Stencil Component Starter 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /Chapter07/Stencil/hello-world/src/index.ts: -------------------------------------------------------------------------------- 1 | export { Components } from './components'; 2 | -------------------------------------------------------------------------------- /Chapter07/Stencil/hello-world/src/utils/utils.spec.ts: -------------------------------------------------------------------------------- 1 | import { format } from './utils'; 2 | 3 | describe('format', () => { 4 | it('returns empty string for no names defined', () => { 5 | expect(format(undefined, undefined, undefined)).toEqual(''); 6 | }); 7 | 8 | it('formats just first names', () => { 9 | expect(format('Joseph', undefined, undefined)).toEqual('Joseph'); 10 | }); 11 | 12 | it('formats first and last names', () => { 13 | expect(format('Joseph', undefined, 'Publique')).toEqual('Joseph Publique'); 14 | }); 15 | 16 | it('formats first, middle and last names', () => { 17 | expect(format('Joseph', 'Quincy', 'Publique')).toEqual( 18 | 'Joseph Quincy Publique' 19 | ); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /Chapter07/Stencil/hello-world/src/utils/utils.ts: -------------------------------------------------------------------------------- 1 | 2 | export function format(first: string, middle: string, last: string): string { 3 | return ( 4 | (first || '') + 5 | (middle ? ` ${middle}` : '') + 6 | (last ? ` ${last}` : '') 7 | ); 8 | } 9 | -------------------------------------------------------------------------------- /Chapter07/Stencil/hello-world/stencil.config.ts: -------------------------------------------------------------------------------- 1 | import { Config } from '@stencil/core'; 2 | 3 | export const config: Config = { 4 | namespace: 'mycomponent', 5 | outputTargets:[ 6 | { type: 'dist' }, 7 | { type: 'docs' }, 8 | { 9 | type: 'www', 10 | serviceWorker: null // disable service workers 11 | } 12 | ] 13 | }; 14 | -------------------------------------------------------------------------------- /Chapter07/Stencil/hello-world/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowSyntheticDefaultImports": true, 4 | "allowUnreachableCode": false, 5 | "declaration": false, 6 | "experimentalDecorators": true, 7 | "lib": [ 8 | "dom", 9 | "es2017" 10 | ], 11 | "moduleResolution": "node", 12 | "module": "esnext", 13 | "target": "es2017", 14 | "noUnusedLocals": true, 15 | "noUnusedParameters": true, 16 | "jsx": "react", 17 | "jsxFactory": "h" 18 | }, 19 | "include": [ 20 | "src", 21 | "types/jsx.d.ts" 22 | ], 23 | "exclude": [ 24 | "node_modules" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /Chapter07/Stencil/student-list/.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | indent_style = space 8 | indent_size = 2 9 | end_of_line = lf 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | 13 | [*.md] 14 | insert_final_newline = false 15 | trim_trailing_whitespace = false 16 | -------------------------------------------------------------------------------- /Chapter07/Stencil/student-list/.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | www/ 3 | 4 | *~ 5 | *.sw[mnpcod] 6 | *.log 7 | *.lock 8 | *.tmp 9 | *.tmp.* 10 | log.txt 11 | *.sublime-project 12 | *.sublime-workspace 13 | 14 | .stencil/ 15 | .idea/ 16 | .vscode/ 17 | .sass-cache/ 18 | .versions/ 19 | node_modules/ 20 | $RECYCLE.BIN/ 21 | 22 | .DS_Store 23 | Thumbs.db 24 | UserInterfaceState.xcuserstate 25 | .env 26 | -------------------------------------------------------------------------------- /Chapter07/Stencil/student-list/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Chapter07/Stencil/student-list/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "student-list", 3 | "version": "0.0.1", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@stencil/core": { 8 | "version": "1.0.0-alpha.32", 9 | "resolved": "https://registry.npmjs.org/@stencil/core/-/core-1.0.0-alpha.32.tgz", 10 | "integrity": "sha512-438FzH5gKkQMET2qXryuzEcVserxTMT6y9Hwg7SFK8w/W137uXNGW+ziVve8q0YC7yJpeBCej+FzseQdrFPLmQ==", 11 | "dev": true, 12 | "requires": { 13 | "typescript": "3.4.5" 14 | } 15 | }, 16 | "typescript": { 17 | "version": "3.4.5", 18 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.4.5.tgz", 19 | "integrity": "sha512-YycBxUb49UUhdNMU5aJ7z5Ej2XGmaIBL0x34vZ82fn3hGvD+bgrMrVDpatgz2f7YxUMJxMkbWxJZeAvDxVe7Vw==", 20 | "dev": true 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Chapter07/Stencil/student-list/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "student-list", 3 | "version": "0.0.1", 4 | "description": "Stencil Component Starter", 5 | "main": "dist/index.js", 6 | "module": "dist/index.mjs", 7 | "es2015": "dist/esm/index.mjs", 8 | "es2017": "dist/esm/index.mjs", 9 | "types": "dist/types/index.d.ts", 10 | "collection": "dist/collection/collection-manifest.json", 11 | "collection:main": "dist/collection/index.js", 12 | "files": [ 13 | "dist/" 14 | ], 15 | "scripts": { 16 | "build": "stencil build --docs", 17 | "start": "stencil build --dev --watch --serve", 18 | "test": "stencil test --spec --e2e", 19 | "test.watch": "stencil test --spec --e2e --watchAll" 20 | }, 21 | "devDependencies": { 22 | "@stencil/core": "one" 23 | }, 24 | "license": "MIT" 25 | } 26 | -------------------------------------------------------------------------------- /Chapter07/Stencil/student-list/readme.md: -------------------------------------------------------------------------------- 1 | ![Built With Stencil](https://img.shields.io/badge/-Built%20With%20Stencil-16161d.svg?logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDE5LjIuMSwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPgo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IgoJIHZpZXdCb3g9IjAgMCA1MTIgNTEyIiBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCA1MTIgNTEyOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI%2BCjxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI%2BCgkuc3Qwe2ZpbGw6I0ZGRkZGRjt9Cjwvc3R5bGU%2BCjxwYXRoIGNsYXNzPSJzdDAiIGQ9Ik00MjQuNywzNzMuOWMwLDM3LjYtNTUuMSw2OC42LTkyLjcsNjguNkgxODAuNGMtMzcuOSwwLTkyLjctMzAuNy05Mi43LTY4LjZ2LTMuNmgzMzYuOVYzNzMuOXoiLz4KPHBhdGggY2xhc3M9InN0MCIgZD0iTTQyNC43LDI5Mi4xSDE4MC40Yy0zNy42LDAtOTIuNy0zMS05Mi43LTY4LjZ2LTMuNkgzMzJjMzcuNiwwLDkyLjcsMzEsOTIuNyw2OC42VjI5Mi4xeiIvPgo8cGF0aCBjbGFzcz0ic3QwIiBkPSJNNDI0LjcsMTQxLjdIODcuN3YtMy42YzAtMzcuNiw1NC44LTY4LjYsOTIuNy02OC42SDMzMmMzNy45LDAsOTIuNywzMC43LDkyLjcsNjguNlYxNDEuN3oiLz4KPC9zdmc%2BCg%3D%3D&colorA=16161d&style=flat-square) 2 | 3 | # Stencil Component Starter 4 | 5 | This is a starter project for building a standalone Web Component using Stencil. 6 | 7 | Stencil is also great for building entire apps. For that, use the [stencil-app-starter](https://github.com/ionic-team/stencil-app-starter) instead. 8 | 9 | # Stencil 10 | 11 | Stencil is a compiler for building fast web apps using Web Components. 12 | 13 | Stencil combines the best concepts of the most popular frontend frameworks into a compile-time rather than run-time tool. Stencil takes TypeScript, JSX, a tiny virtual DOM layer, efficient one-way data binding, an asynchronous rendering pipeline (similar to React Fiber), and lazy-loading out of the box, and generates 100% standards-based Web Components that run in any browser supporting the Custom Elements v1 spec. 14 | 15 | Stencil components are just Web Components, so they work in any major framework or with no framework at all. 16 | 17 | ## Getting Started 18 | 19 | To start building a new web component using Stencil, clone this repo to a new directory: 20 | 21 | ```bash 22 | git clone https://github.com/ionic-team/stencil-component-starter.git my-component 23 | cd my-component 24 | git remote rm origin 25 | ``` 26 | 27 | and run: 28 | 29 | ```bash 30 | npm install 31 | npm start 32 | ``` 33 | 34 | To build the component for production, run: 35 | 36 | ```bash 37 | npm run build 38 | ``` 39 | 40 | To run the unit tests for the components, run: 41 | 42 | ```bash 43 | npm test 44 | ``` 45 | 46 | Need help? Check out our docs [here](https://stenciljs.com/docs/my-first-component). 47 | 48 | 49 | ## Naming Components 50 | 51 | When creating new component tags, we recommend _not_ using `stencil` in the component name (ex: ``). This is because the generated component has little to nothing to do with Stencil; it's just a web component! 52 | 53 | Instead, use a prefix that fits your company or any name for a group of related components. For example, all of the Ionic generated web components use the prefix `ion`. 54 | 55 | 56 | ## Using this component 57 | 58 | ### Script tag 59 | 60 | - [Publish to NPM](https://docs.npmjs.com/getting-started/publishing-npm-packages) 61 | - Put a script tag similar to this `` in the head of your index.html 62 | - Then you can use the element anywhere in your template, JSX, html etc 63 | 64 | ### Node Modules 65 | - Run `npm install my-component --save` 66 | - Put a script tag similar to this `` in the head of your index.html 67 | - Then you can use the element anywhere in your template, JSX, html etc 68 | 69 | ### In a stencil-starter app 70 | - Run `npm install my-component --save` 71 | - Add an import to the npm packages `import my-component;` 72 | - Then you can use the element anywhere in your template, JSX, html etc 73 | -------------------------------------------------------------------------------- /Chapter07/Stencil/student-list/src/components.d.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | /** 3 | * This is an autogenerated file created by the Stencil compiler. 4 | * It contains typing information for all components that exist in this project. 5 | */ 6 | 7 | 8 | import { HTMLStencilElement, JSXBase } from '@stencil/core/internal'; 9 | import { JSX } from '@stencil/core'; 10 | 11 | 12 | export namespace Components { 13 | interface StudentList {} 14 | interface StudentName { 15 | 'first': string; 16 | 'last': string; 17 | } 18 | } 19 | 20 | declare namespace LocalJSX { 21 | interface StudentList extends JSXBase.HTMLAttributes {} 22 | interface StudentName extends JSXBase.HTMLAttributes { 23 | 'first'?: string; 24 | 'last'?: string; 25 | } 26 | 27 | interface ElementInterfaces { 28 | 'StudentList': Components.StudentList; 29 | 'StudentName': Components.StudentName; 30 | } 31 | 32 | interface IntrinsicElements { 33 | 'StudentList': LocalJSX.StudentList; 34 | 'StudentName': LocalJSX.StudentName; 35 | } 36 | } 37 | export { LocalJSX as JSX }; 38 | 39 | declare module "@stencil/core" { 40 | export namespace JSX { 41 | interface ElementInterfaces extends LocalJSX.ElementInterfaces {} 42 | interface IntrinsicElements extends LocalJSX.IntrinsicElements {} 43 | } 44 | } 45 | 46 | declare global { 47 | 48 | 49 | interface HTMLStudentListElement extends Components.StudentList, HTMLStencilElement {} 50 | var HTMLStudentListElement: { 51 | prototype: HTMLStudentListElement; 52 | new (): HTMLStudentListElement; 53 | }; 54 | 55 | interface HTMLStudentNameElement extends Components.StudentName, HTMLStencilElement {} 56 | var HTMLStudentNameElement: { 57 | prototype: HTMLStudentNameElement; 58 | new (): HTMLStudentNameElement; 59 | }; 60 | interface HTMLElementTagNameMap { 61 | 'student-list': HTMLStudentListElement 62 | 'student-name': HTMLStudentNameElement 63 | } 64 | 65 | interface ElementTagNameMap { 66 | 'student-list': HTMLStudentListElement; 67 | 'student-name': HTMLStudentNameElement; 68 | } 69 | } 70 | 71 | -------------------------------------------------------------------------------- /Chapter07/Stencil/student-list/src/components/student-list/student-list.css: -------------------------------------------------------------------------------- 1 | 2 | .student-list__student { 3 | border: 1px solid #CECECE; 4 | display: block; 5 | padding: 10px; 6 | } 7 | -------------------------------------------------------------------------------- /Chapter07/Stencil/student-list/src/components/student-list/student-list.tsx: -------------------------------------------------------------------------------- 1 | import { Component, h } from '@stencil/core'; 2 | 3 | @Component({ 4 | tag: 'student-list', 5 | styleUrl: 'student-list.css', 6 | shadow: true 7 | }) 8 | 9 | export class StudentList { 10 | render() { 11 | return
12 |
Student List is as follows:
13 | 14 | 15 |
; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Chapter07/Stencil/student-list/src/components/student-name/student-name.css: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /Chapter07/Stencil/student-list/src/components/student-name/student-name.tsx: -------------------------------------------------------------------------------- 1 | import { Component, Prop, h } from '@stencil/core'; 2 | 3 | @Component({ 4 | tag: 'student-name', 5 | styleUrl: 'student-name.css', 6 | shadow: true 7 | }) 8 | export class StudentName { 9 | @Prop({reflectToAttr: true}) first: string; 10 | @Prop() last: string; 11 | 12 | private getFullName(): string { 13 | return `${this.first} ${this.last}`; 14 | } 15 | 16 | render() { 17 | return
Student Name: {this.getFullName()}
; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Chapter07/Stencil/student-list/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Stencil Component Starter 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /Chapter07/Stencil/student-list/src/index.ts: -------------------------------------------------------------------------------- 1 | export { Components } from './components'; 2 | -------------------------------------------------------------------------------- /Chapter07/Stencil/student-list/src/utils/utils.spec.ts: -------------------------------------------------------------------------------- 1 | import { format } from './utils'; 2 | 3 | describe('format', () => { 4 | it('returns empty string for no names defined', () => { 5 | expect(format(undefined, undefined, undefined)).toEqual(''); 6 | }); 7 | 8 | it('formats just first names', () => { 9 | expect(format('Joseph', undefined, undefined)).toEqual('Joseph'); 10 | }); 11 | 12 | it('formats first and last names', () => { 13 | expect(format('Joseph', undefined, 'Publique')).toEqual('Joseph Publique'); 14 | }); 15 | 16 | it('formats first, middle and last names', () => { 17 | expect(format('Joseph', 'Quincy', 'Publique')).toEqual( 18 | 'Joseph Quincy Publique' 19 | ); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /Chapter07/Stencil/student-list/src/utils/utils.ts: -------------------------------------------------------------------------------- 1 | 2 | export function format(first: string, middle: string, last: string): string { 3 | return ( 4 | (first || '') + 5 | (middle ? ` ${middle}` : '') + 6 | (last ? ` ${last}` : '') 7 | ); 8 | } 9 | -------------------------------------------------------------------------------- /Chapter07/Stencil/student-list/stencil.config.ts: -------------------------------------------------------------------------------- 1 | import { Config } from '@stencil/core'; 2 | 3 | export const config: Config = { 4 | namespace: 'mycomponent', 5 | outputTargets:[ 6 | { type: 'dist' }, 7 | { type: 'docs' }, 8 | { 9 | type: 'www', 10 | serviceWorker: null // disable service workers 11 | } 12 | ] 13 | }; 14 | -------------------------------------------------------------------------------- /Chapter07/Stencil/student-list/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowSyntheticDefaultImports": true, 4 | "allowUnreachableCode": false, 5 | "declaration": false, 6 | "experimentalDecorators": true, 7 | "lib": [ 8 | "dom", 9 | "es2017" 10 | ], 11 | "moduleResolution": "node", 12 | "module": "esnext", 13 | "target": "es2017", 14 | "noUnusedLocals": true, 15 | "noUnusedParameters": true, 16 | "jsx": "react", 17 | "jsxFactory": "h" 18 | }, 19 | "include": [ 20 | "src", 21 | "types/jsx.d.ts" 22 | ], 23 | "exclude": [ 24 | "node_modules" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /Chapter08/Angular/my-app/.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /Chapter08/Angular/my-app/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | # Only exists if Bazel was run 8 | /bazel-out 9 | 10 | # dependencies 11 | /node_modules 12 | 13 | # profiling files 14 | chrome-profiler-events.json 15 | speed-measure-plugin.json 16 | 17 | # IDEs and editors 18 | /.idea 19 | .project 20 | .classpath 21 | .c9/ 22 | *.launch 23 | .settings/ 24 | *.sublime-workspace 25 | 26 | # IDE - VSCode 27 | .vscode/* 28 | !.vscode/settings.json 29 | !.vscode/tasks.json 30 | !.vscode/launch.json 31 | !.vscode/extensions.json 32 | .history/* 33 | 34 | # misc 35 | /.sass-cache 36 | /connect.lock 37 | /coverage 38 | /libpeerconnection.log 39 | npm-debug.log 40 | yarn-error.log 41 | testem.log 42 | /typings 43 | 44 | # System Files 45 | .DS_Store 46 | Thumbs.db 47 | -------------------------------------------------------------------------------- /Chapter08/Angular/my-app/README.md: -------------------------------------------------------------------------------- 1 | # MyApp 2 | 3 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 7.3.9. 4 | 5 | ## Development server 6 | 7 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. 8 | 9 | ## Code scaffolding 10 | 11 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. 12 | 13 | ## Build 14 | 15 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build. 16 | 17 | ## Running unit tests 18 | 19 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). 20 | 21 | ## Running end-to-end tests 22 | 23 | Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). 24 | 25 | ## Further help 26 | 27 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md). 28 | -------------------------------------------------------------------------------- /Chapter08/Angular/my-app/angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "my-app": { 7 | "root": "", 8 | "sourceRoot": "src", 9 | "projectType": "application", 10 | "prefix": "app", 11 | "schematics": {}, 12 | "architect": { 13 | "build": { 14 | "builder": "@angular-devkit/build-angular:browser", 15 | "options": { 16 | "outputPath": "dist/my-app", 17 | "index": "src/index.html", 18 | "main": "src/main.ts", 19 | "polyfills": "src/polyfills.ts", 20 | "tsConfig": "src/tsconfig.app.json", 21 | "assets": [ 22 | "src/favicon.ico", 23 | "src/assets" 24 | ], 25 | "styles": [ 26 | "src/styles.css" 27 | ], 28 | "scripts": [], 29 | "es5BrowserSupport": true 30 | }, 31 | "configurations": { 32 | "production": { 33 | "fileReplacements": [ 34 | { 35 | "replace": "src/environments/environment.ts", 36 | "with": "src/environments/environment.prod.ts" 37 | } 38 | ], 39 | "optimization": true, 40 | "outputHashing": "all", 41 | "sourceMap": false, 42 | "extractCss": true, 43 | "namedChunks": false, 44 | "aot": true, 45 | "extractLicenses": true, 46 | "vendorChunk": false, 47 | "buildOptimizer": true, 48 | "budgets": [ 49 | { 50 | "type": "initial", 51 | "maximumWarning": "2mb", 52 | "maximumError": "5mb" 53 | } 54 | ] 55 | } 56 | } 57 | }, 58 | "serve": { 59 | "builder": "@angular-devkit/build-angular:dev-server", 60 | "options": { 61 | "browserTarget": "my-app:build" 62 | }, 63 | "configurations": { 64 | "production": { 65 | "browserTarget": "my-app:build:production" 66 | } 67 | } 68 | }, 69 | "extract-i18n": { 70 | "builder": "@angular-devkit/build-angular:extract-i18n", 71 | "options": { 72 | "browserTarget": "my-app:build" 73 | } 74 | }, 75 | "test": { 76 | "builder": "@angular-devkit/build-angular:karma", 77 | "options": { 78 | "main": "src/test.ts", 79 | "polyfills": "src/polyfills.ts", 80 | "tsConfig": "src/tsconfig.spec.json", 81 | "karmaConfig": "src/karma.conf.js", 82 | "styles": [ 83 | "src/styles.css" 84 | ], 85 | "scripts": [], 86 | "assets": [ 87 | "src/favicon.ico", 88 | "src/assets" 89 | ] 90 | } 91 | }, 92 | "lint": { 93 | "builder": "@angular-devkit/build-angular:tslint", 94 | "options": { 95 | "tsConfig": [ 96 | "src/tsconfig.app.json", 97 | "src/tsconfig.spec.json" 98 | ], 99 | "exclude": [ 100 | "**/node_modules/**" 101 | ] 102 | } 103 | } 104 | } 105 | }, 106 | "my-app-e2e": { 107 | "root": "e2e/", 108 | "projectType": "application", 109 | "prefix": "", 110 | "architect": { 111 | "e2e": { 112 | "builder": "@angular-devkit/build-angular:protractor", 113 | "options": { 114 | "protractorConfig": "e2e/protractor.conf.js", 115 | "devServerTarget": "my-app:serve" 116 | }, 117 | "configurations": { 118 | "production": { 119 | "devServerTarget": "my-app:serve:production" 120 | } 121 | } 122 | }, 123 | "lint": { 124 | "builder": "@angular-devkit/build-angular:tslint", 125 | "options": { 126 | "tsConfig": "e2e/tsconfig.e2e.json", 127 | "exclude": [ 128 | "**/node_modules/**" 129 | ] 130 | } 131 | } 132 | } 133 | } 134 | }, 135 | "defaultProject": "my-app" 136 | } -------------------------------------------------------------------------------- /Chapter08/Angular/my-app/e2e/protractor.conf.js: -------------------------------------------------------------------------------- 1 | // Protractor configuration file, see link for more information 2 | // https://github.com/angular/protractor/blob/master/lib/config.ts 3 | 4 | const { SpecReporter } = require('jasmine-spec-reporter'); 5 | 6 | exports.config = { 7 | allScriptsTimeout: 11000, 8 | specs: [ 9 | './src/**/*.e2e-spec.ts' 10 | ], 11 | capabilities: { 12 | 'browserName': 'chrome' 13 | }, 14 | directConnect: true, 15 | baseUrl: 'http://localhost:4200/', 16 | framework: 'jasmine', 17 | jasmineNodeOpts: { 18 | showColors: true, 19 | defaultTimeoutInterval: 30000, 20 | print: function() {} 21 | }, 22 | onPrepare() { 23 | require('ts-node').register({ 24 | project: require('path').join(__dirname, './tsconfig.e2e.json') 25 | }); 26 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); 27 | } 28 | }; -------------------------------------------------------------------------------- /Chapter08/Angular/my-app/e2e/src/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { AppPage } from './app.po'; 2 | import { browser, logging } from 'protractor'; 3 | 4 | describe('workspace-project App', () => { 5 | let page: AppPage; 6 | 7 | beforeEach(() => { 8 | page = new AppPage(); 9 | }); 10 | 11 | it('should display welcome message', () => { 12 | page.navigateTo(); 13 | expect(page.getTitleText()).toEqual('Welcome to my-app!'); 14 | }); 15 | 16 | afterEach(async () => { 17 | // Assert that there are no errors emitted from the browser 18 | const logs = await browser.manage().logs().get(logging.Type.BROWSER); 19 | expect(logs).not.toContain(jasmine.objectContaining({ 20 | level: logging.Level.SEVERE, 21 | } as logging.Entry)); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /Chapter08/Angular/my-app/e2e/src/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class AppPage { 4 | navigateTo() { 5 | return browser.get(browser.baseUrl) as Promise; 6 | } 7 | 8 | getTitleText() { 9 | return element(by.css('app-root h1')).getText() as Promise; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Chapter08/Angular/my-app/e2e/tsconfig.e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "module": "commonjs", 6 | "target": "es5", 7 | "types": [ 8 | "jasmine", 9 | "jasminewd2", 10 | "node" 11 | ] 12 | } 13 | } -------------------------------------------------------------------------------- /Chapter08/Angular/my-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "my-app", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "ng": "ng", 6 | "start": "ng serve", 7 | "build": "ng build", 8 | "test": "ng test", 9 | "lint": "ng lint", 10 | "e2e": "ng e2e" 11 | }, 12 | "private": true, 13 | "dependencies": { 14 | "@angular/animations": "~7.2.0", 15 | "@angular/common": "~7.2.0", 16 | "@angular/compiler": "~7.2.0", 17 | "@angular/core": "~7.2.0", 18 | "@angular/forms": "~7.2.0", 19 | "@angular/platform-browser": "~7.2.0", 20 | "@angular/platform-browser-dynamic": "~7.2.0", 21 | "@angular/router": "~7.2.0", 22 | "core-js": "^2.5.4", 23 | "rxjs": "~6.3.3", 24 | "tslib": "^1.9.0", 25 | "zone.js": "~0.8.26" 26 | }, 27 | "devDependencies": { 28 | "@angular-devkit/build-angular": "~0.13.0", 29 | "@angular/cli": "~7.3.9", 30 | "@angular/compiler-cli": "~7.2.0", 31 | "@angular/language-service": "~7.2.0", 32 | "@types/node": "~8.9.4", 33 | "@types/jasmine": "~2.8.8", 34 | "@types/jasminewd2": "~2.0.3", 35 | "codelyzer": "~4.5.0", 36 | "jasmine-core": "~2.99.1", 37 | "jasmine-spec-reporter": "~4.2.1", 38 | "karma": "~4.0.0", 39 | "karma-chrome-launcher": "~2.2.0", 40 | "karma-coverage-istanbul-reporter": "~2.0.1", 41 | "karma-jasmine": "~1.1.2", 42 | "karma-jasmine-html-reporter": "^0.2.2", 43 | "protractor": "~5.4.0", 44 | "ts-node": "~7.0.0", 45 | "tslint": "~5.11.0", 46 | "typescript": "~3.2.2" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Chapter08/Angular/my-app/src/app/app.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Getting-Started-with-Web-Components/3f9ae36ca523e2239315cabf948b935b80ddfce5/Chapter08/Angular/my-app/src/app/app.component.css -------------------------------------------------------------------------------- /Chapter08/Angular/my-app/src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |

4 | Welcome to {{ title }}! 5 |

6 | Angular Logo 7 |
8 |

Here are some links to help you start:

9 | 20 | 21 | -------------------------------------------------------------------------------- /Chapter08/Angular/my-app/src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, async } from '@angular/core/testing'; 2 | import { AppComponent } from './app.component'; 3 | 4 | describe('AppComponent', () => { 5 | beforeEach(async(() => { 6 | TestBed.configureTestingModule({ 7 | declarations: [ 8 | AppComponent 9 | ], 10 | }).compileComponents(); 11 | })); 12 | 13 | it('should create the app', () => { 14 | const fixture = TestBed.createComponent(AppComponent); 15 | const app = fixture.debugElement.componentInstance; 16 | expect(app).toBeTruthy(); 17 | }); 18 | 19 | it(`should have as title 'my-app'`, () => { 20 | const fixture = TestBed.createComponent(AppComponent); 21 | const app = fixture.debugElement.componentInstance; 22 | expect(app.title).toEqual('my-app'); 23 | }); 24 | 25 | it('should render title in a h1 tag', () => { 26 | const fixture = TestBed.createComponent(AppComponent); 27 | fixture.detectChanges(); 28 | const compiled = fixture.debugElement.nativeElement; 29 | expect(compiled.querySelector('h1').textContent).toContain('Welcome to my-app!'); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /Chapter08/Angular/my-app/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-root', 5 | templateUrl: './app.component.html', 6 | styleUrls: ['./app.component.css'] 7 | }) 8 | export class AppComponent { 9 | title = 'my-app'; 10 | } 11 | -------------------------------------------------------------------------------- /Chapter08/Angular/my-app/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { BrowserModule } from '@angular/platform-browser'; 2 | import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; 3 | 4 | import { AppComponent } from './app.component'; 5 | import { MainBodyComponent } from './main-body/main-body.component'; 6 | 7 | @NgModule({ 8 | declarations: [ 9 | AppComponent, 10 | MainBodyComponent 11 | ], 12 | imports: [ 13 | BrowserModule 14 | ], 15 | providers: [], 16 | bootstrap: [AppComponent], 17 | schemas: [ 18 | CUSTOM_ELEMENTS_SCHEMA 19 | ] 20 | }) 21 | export class AppModule { } 22 | -------------------------------------------------------------------------------- /Chapter08/Angular/my-app/src/app/main-body/main-body.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Getting-Started-with-Web-Components/3f9ae36ca523e2239315cabf948b935b80ddfce5/Chapter08/Angular/my-app/src/app/main-body/main-body.component.css -------------------------------------------------------------------------------- /Chapter08/Angular/my-app/src/app/main-body/main-body.component.html: -------------------------------------------------------------------------------- 1 |

2 | main-body works! 3 |

4 | 5 | -------------------------------------------------------------------------------- /Chapter08/Angular/my-app/src/app/main-body/main-body.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { MainBodyComponent } from './main-body.component'; 4 | 5 | describe('MainBodyComponent', () => { 6 | let component: MainBodyComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ MainBodyComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(MainBodyComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /Chapter08/Angular/my-app/src/app/main-body/main-body.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import HeaderImage from '../web-components/header-image/header-image.js'; 3 | 4 | @Component({ 5 | selector: 'app-main-body', 6 | templateUrl: './main-body.component.html', 7 | styleUrls: ['./main-body.component.css'] 8 | }) 9 | export class MainBodyComponent implements OnInit { 10 | src: string ; 11 | alttext: string; 12 | 13 | constructor() { 14 | this.src = 'https://www.freewebheaders.com/wordpress/wp-content/gallery/clouds-sky/clouds-sky-header-2069-1024x300.jpg'; 15 | this.alttext = 'Blue Clouds'; 16 | } 17 | 18 | ngOnInit() { 19 | customElements.define('header-image', HeaderImage); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /Chapter08/Angular/my-app/src/app/web-components/header-image/header-image.js: -------------------------------------------------------------------------------- 1 | export default class HeaderImage extends HTMLElement { 2 | constructor() { 3 | 4 | // We are not even going to touch this. 5 | super(); 6 | this.src = ''; 7 | this.alt = ''; 8 | 9 | // lets create our shadow root 10 | this.shadowObj = this.attachShadow({mode: 'open'}); 11 | 12 | // Then lets render the template 13 | this.render(); 14 | } 15 | 16 | static get observedAttributes() { 17 | return ['src', 'alttext']; 18 | } 19 | 20 | attributeChangedCallback(name, oldValue, newValue) { 21 | if (name == 'src') { 22 | this.src = newValue; 23 | this.render(); 24 | } 25 | if (name == 'alttext') { 26 | this.alt = newValue; 27 | this.render(); 28 | } 29 | 30 | 31 | } 32 | 33 | render() { 34 | this.shadowObj.innerHTML = this.getTemplate(); 35 | } 36 | 37 | getTemplate() { 38 | return ` 39 | ${this.alt} 41 | ${this.handleErrors()} 42 | 47 | `; 48 | } 49 | 50 | handleErrors() { 51 | if(!this.alt) { 52 | return ` 53 |
Missing Alt Text
54 | 59 | `; 60 | } 61 | 62 | return ``; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Chapter08/Angular/my-app/src/app/web-components/header-image/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Header Image 5 | 6 | 9 | 16 | 17 | 18 | 19 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Chapter08/Angular/my-app/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Getting-Started-with-Web-Components/3f9ae36ca523e2239315cabf948b935b80ddfce5/Chapter08/Angular/my-app/src/assets/.gitkeep -------------------------------------------------------------------------------- /Chapter08/Angular/my-app/src/browserslist: -------------------------------------------------------------------------------- 1 | # This file is currently used by autoprefixer to adjust CSS to support the below specified browsers 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | # 5 | # For IE 9-11 support, please remove 'not' from the last line of the file and adjust as needed 6 | 7 | > 0.5% 8 | last 2 versions 9 | Firefox ESR 10 | not dead 11 | not IE 9-11 -------------------------------------------------------------------------------- /Chapter08/Angular/my-app/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /Chapter08/Angular/my-app/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false 7 | }; 8 | 9 | /* 10 | * For easier debugging in development mode, you can import the following file 11 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 12 | * 13 | * This import should be commented out in production mode because it will have a negative impact 14 | * on performance if an error is thrown. 15 | */ 16 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI. 17 | -------------------------------------------------------------------------------- /Chapter08/Angular/my-app/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Getting-Started-with-Web-Components/3f9ae36ca523e2239315cabf948b935b80ddfce5/Chapter08/Angular/my-app/src/favicon.ico -------------------------------------------------------------------------------- /Chapter08/Angular/my-app/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | MyApp 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Chapter08/Angular/my-app/src/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | dir: require('path').join(__dirname, '../coverage/my-app'), 20 | reports: ['html', 'lcovonly', 'text-summary'], 21 | fixWebpackSourcePaths: true 22 | }, 23 | reporters: ['progress', 'kjhtml'], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | browsers: ['Chrome'], 29 | singleRun: false, 30 | restartOnFileChange: true 31 | }); 32 | }; 33 | -------------------------------------------------------------------------------- /Chapter08/Angular/my-app/src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic().bootstrapModule(AppModule) 12 | .catch(err => console.error(err)); 13 | -------------------------------------------------------------------------------- /Chapter08/Angular/my-app/src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/guide/browser-support 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 22 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 23 | 24 | /** 25 | * Web Animations `@angular/platform-browser/animations` 26 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. 27 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). 28 | */ 29 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 30 | 31 | /** 32 | * By default, zone.js will patch all possible macroTask and DomEvents 33 | * user can disable parts of macroTask/DomEvents patch by setting following flags 34 | * because those flags need to be set before `zone.js` being loaded, and webpack 35 | * will put import in the top of bundle, so user need to create a separate file 36 | * in this directory (for example: zone-flags.ts), and put the following flags 37 | * into that file, and then add the following code before importing zone.js. 38 | * import './zone-flags.ts'; 39 | * 40 | * The flags allowed in zone-flags.ts are listed here. 41 | * 42 | * The following flags will work for all browsers. 43 | * 44 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 45 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 46 | * (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 47 | * 48 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 49 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 50 | * 51 | * (window as any).__Zone_enable_cross_context_check = true; 52 | * 53 | */ 54 | 55 | /*************************************************************************************************** 56 | * Zone JS is required by default for Angular itself. 57 | */ 58 | import 'zone.js/dist/zone'; // Included with Angular CLI. 59 | 60 | 61 | /*************************************************************************************************** 62 | * APPLICATION IMPORTS 63 | */ 64 | -------------------------------------------------------------------------------- /Chapter08/Angular/my-app/src/styles.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | -------------------------------------------------------------------------------- /Chapter08/Angular/my-app/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/dist/zone-testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | declare const require: any; 11 | 12 | // First, initialize the Angular testing environment. 13 | getTestBed().initTestEnvironment( 14 | BrowserDynamicTestingModule, 15 | platformBrowserDynamicTesting() 16 | ); 17 | // Then we find all the tests. 18 | const context = require.context('./', true, /\.spec\.ts$/); 19 | // And load the modules. 20 | context.keys().map(context); 21 | -------------------------------------------------------------------------------- /Chapter08/Angular/my-app/src/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "types": [] 6 | }, 7 | "exclude": [ 8 | "test.ts", 9 | "**/*.spec.ts" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /Chapter08/Angular/my-app/src/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node" 8 | ] 9 | }, 10 | "files": [ 11 | "test.ts", 12 | "polyfills.ts" 13 | ], 14 | "include": [ 15 | "**/*.spec.ts", 16 | "**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /Chapter08/Angular/my-app/src/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tslint.json", 3 | "rules": { 4 | "directive-selector": [ 5 | true, 6 | "attribute", 7 | "app", 8 | "camelCase" 9 | ], 10 | "component-selector": [ 11 | true, 12 | "element", 13 | "app", 14 | "kebab-case" 15 | ] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Chapter08/Angular/my-app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./dist/out-tsc", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "module": "es2015", 9 | "moduleResolution": "node", 10 | "emitDecoratorMetadata": true, 11 | "experimentalDecorators": true, 12 | "importHelpers": true, 13 | "target": "es5", 14 | "typeRoots": [ 15 | "node_modules/@types" 16 | ], 17 | "lib": [ 18 | "es2018", 19 | "dom" 20 | ] 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Chapter08/Angular/my-app/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint:recommended", 3 | "rulesDirectory": [ 4 | "codelyzer" 5 | ], 6 | "rules": { 7 | "array-type": false, 8 | "arrow-parens": false, 9 | "deprecation": { 10 | "severity": "warn" 11 | }, 12 | "import-blacklist": [ 13 | true, 14 | "rxjs/Rx" 15 | ], 16 | "interface-name": false, 17 | "max-classes-per-file": false, 18 | "max-line-length": [ 19 | true, 20 | 140 21 | ], 22 | "member-access": false, 23 | "member-ordering": [ 24 | true, 25 | { 26 | "order": [ 27 | "static-field", 28 | "instance-field", 29 | "static-method", 30 | "instance-method" 31 | ] 32 | } 33 | ], 34 | "no-consecutive-blank-lines": false, 35 | "no-console": [ 36 | true, 37 | "debug", 38 | "info", 39 | "time", 40 | "timeEnd", 41 | "trace" 42 | ], 43 | "no-empty": false, 44 | "no-inferrable-types": [ 45 | true, 46 | "ignore-params" 47 | ], 48 | "no-non-null-assertion": true, 49 | "no-redundant-jsdoc": true, 50 | "no-switch-case-fall-through": true, 51 | "no-use-before-declare": true, 52 | "no-var-requires": false, 53 | "object-literal-key-quotes": [ 54 | true, 55 | "as-needed" 56 | ], 57 | "object-literal-sort-keys": false, 58 | "ordered-imports": false, 59 | "quotemark": [ 60 | true, 61 | "single" 62 | ], 63 | "trailing-comma": false, 64 | "no-output-on-prefix": true, 65 | "use-input-property-decorator": true, 66 | "use-output-property-decorator": true, 67 | "use-host-property-decorator": true, 68 | "no-input-rename": true, 69 | "no-output-rename": true, 70 | "use-life-cycle-interface": true, 71 | "use-pipe-transform-interface": true, 72 | "component-class-suffix": true, 73 | "directive-class-suffix": true 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Chapter08/React/my-app/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /Chapter08/React/my-app/README.md: -------------------------------------------------------------------------------- 1 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 2 | 3 | ## Available Scripts 4 | 5 | In the project directory, you can run: 6 | 7 | ### `npm start` 8 | 9 | Runs the app in the development mode.
10 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 11 | 12 | The page will reload if you make edits.
13 | You will also see any lint errors in the console. 14 | 15 | ### `npm test` 16 | 17 | Launches the test runner in the interactive watch mode.
18 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 19 | 20 | ### `npm run build` 21 | 22 | Builds the app for production to the `build` folder.
23 | It correctly bundles React in production mode and optimizes the build for the best performance. 24 | 25 | The build is minified and the filenames include the hashes.
26 | Your app is ready to be deployed! 27 | 28 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 29 | 30 | ### `npm run eject` 31 | 32 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 33 | 34 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 35 | 36 | Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 37 | 38 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 39 | 40 | ## Learn More 41 | 42 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 43 | 44 | To learn React, check out the [React documentation](https://reactjs.org/). 45 | 46 | ### Code Splitting 47 | 48 | This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting 49 | 50 | ### Analyzing the Bundle Size 51 | 52 | This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size 53 | 54 | ### Making a Progressive Web App 55 | 56 | This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app 57 | 58 | ### Advanced Configuration 59 | 60 | This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration 61 | 62 | ### Deployment 63 | 64 | This section has moved here: https://facebook.github.io/create-react-app/docs/deployment 65 | 66 | ### `npm run build` fails to minify 67 | 68 | This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify 69 | -------------------------------------------------------------------------------- /Chapter08/React/my-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "my-app", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "react": "^16.8.6", 7 | "react-dom": "^16.8.6", 8 | "react-scripts": "3.0.1" 9 | }, 10 | "scripts": { 11 | "start": "react-scripts start", 12 | "build": "react-scripts build", 13 | "test": "react-scripts test", 14 | "eject": "react-scripts eject" 15 | }, 16 | "eslintConfig": { 17 | "extends": "react-app" 18 | }, 19 | "browserslist": { 20 | "production": [ 21 | ">0.2%", 22 | "not dead", 23 | "not op_mini all" 24 | ], 25 | "development": [ 26 | "last 1 chrome version", 27 | "last 1 firefox version", 28 | "last 1 safari version" 29 | ] 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Chapter08/React/my-app/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Getting-Started-with-Web-Components/3f9ae36ca523e2239315cabf948b935b80ddfce5/Chapter08/React/my-app/public/favicon.ico -------------------------------------------------------------------------------- /Chapter08/React/my-app/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 22 | React App 23 | 24 | 25 | 26 |
27 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /Chapter08/React/my-app/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /Chapter08/React/my-app/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | animation: App-logo-spin infinite 20s linear; 7 | height: 40vmin; 8 | pointer-events: none; 9 | } 10 | 11 | .App-header { 12 | background-color: #282c34; 13 | min-height: 100vh; 14 | display: flex; 15 | flex-direction: column; 16 | align-items: center; 17 | justify-content: center; 18 | font-size: calc(10px + 2vmin); 19 | color: white; 20 | } 21 | 22 | .App-link { 23 | color: #61dafb; 24 | } 25 | 26 | @keyframes App-logo-spin { 27 | from { 28 | transform: rotate(0deg); 29 | } 30 | to { 31 | transform: rotate(360deg); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Chapter08/React/my-app/src/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import logo from './logo.svg'; 3 | import './App.css'; 4 | import MainBody from './main-body/main-body.js'; 5 | 6 | function App() { 7 | return ( 8 |
9 |
10 | logo 11 |

12 | Edit src/App.js and save to reload. 13 |

14 | 20 | Learn React 21 | 22 | 23 |
24 |
25 | ); 26 | } 27 | 28 | export default App; 29 | -------------------------------------------------------------------------------- /Chapter08/React/my-app/src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /Chapter08/React/my-app/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", 4 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /Chapter08/React/my-app/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import * as serviceWorker from './serviceWorker'; 6 | 7 | ReactDOM.render(, document.getElementById('root')); 8 | 9 | // If you want your app to work offline and load faster, you can change 10 | // unregister() to register() below. Note this comes with some pitfalls. 11 | // Learn more about service workers: https://bit.ly/CRA-PWA 12 | serviceWorker.unregister(); 13 | -------------------------------------------------------------------------------- /Chapter08/React/my-app/src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Chapter08/React/my-app/src/main-body/main-body.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import HeaderImage from '../web-components/header-image/header-image.js'; 3 | 4 | export default class MainBody extends Component { 5 | 6 | constructor() { 7 | super(); 8 | this.state = { 9 | src: 'https://www.freewebheaders.com/wordpress/wp-content/gallery/clouds-sky/clouds-sky-header-2069-1024x300.jpg', 10 | altText: 'Blue Clouds' 11 | } 12 | } 13 | 14 | componentDidMount() { 15 | customElements.define('header-image', HeaderImage); 16 | } 17 | render() { 18 | return ( 19 |
20 |

This is the main body

21 | 23 | 24 |
25 | ); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Chapter08/React/my-app/src/serviceWorker.js: -------------------------------------------------------------------------------- 1 | // This optional code is used to register a service worker. 2 | // register() is not called by default. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on subsequent visits to a page, after all the 7 | // existing tabs open on the page have been closed, since previously cached 8 | // resources are updated in the background. 9 | 10 | // To learn more about the benefits of this model and instructions on how to 11 | // opt-in, read https://bit.ly/CRA-PWA 12 | 13 | const isLocalhost = Boolean( 14 | window.location.hostname === 'localhost' || 15 | // [::1] is the IPv6 localhost address. 16 | window.location.hostname === '[::1]' || 17 | // 127.0.0.1/8 is considered localhost for IPv4. 18 | window.location.hostname.match( 19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 20 | ) 21 | ); 22 | 23 | export function register(config) { 24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 25 | // The URL constructor is available in all browsers that support SW. 26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); 27 | if (publicUrl.origin !== window.location.origin) { 28 | // Our service worker won't work if PUBLIC_URL is on a different origin 29 | // from what our page is served on. This might happen if a CDN is used to 30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 31 | return; 32 | } 33 | 34 | window.addEventListener('load', () => { 35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 36 | 37 | if (isLocalhost) { 38 | // This is running on localhost. Let's check if a service worker still exists or not. 39 | checkValidServiceWorker(swUrl, config); 40 | 41 | // Add some additional logging to localhost, pointing developers to the 42 | // service worker/PWA documentation. 43 | navigator.serviceWorker.ready.then(() => { 44 | console.log( 45 | 'This web app is being served cache-first by a service ' + 46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA' 47 | ); 48 | }); 49 | } else { 50 | // Is not localhost. Just register service worker 51 | registerValidSW(swUrl, config); 52 | } 53 | }); 54 | } 55 | } 56 | 57 | function registerValidSW(swUrl, config) { 58 | navigator.serviceWorker 59 | .register(swUrl) 60 | .then(registration => { 61 | registration.onupdatefound = () => { 62 | const installingWorker = registration.installing; 63 | if (installingWorker == null) { 64 | return; 65 | } 66 | installingWorker.onstatechange = () => { 67 | if (installingWorker.state === 'installed') { 68 | if (navigator.serviceWorker.controller) { 69 | // At this point, the updated precached content has been fetched, 70 | // but the previous service worker will still serve the older 71 | // content until all client tabs are closed. 72 | console.log( 73 | 'New content is available and will be used when all ' + 74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' 75 | ); 76 | 77 | // Execute callback 78 | if (config && config.onUpdate) { 79 | config.onUpdate(registration); 80 | } 81 | } else { 82 | // At this point, everything has been precached. 83 | // It's the perfect time to display a 84 | // "Content is cached for offline use." message. 85 | console.log('Content is cached for offline use.'); 86 | 87 | // Execute callback 88 | if (config && config.onSuccess) { 89 | config.onSuccess(registration); 90 | } 91 | } 92 | } 93 | }; 94 | }; 95 | }) 96 | .catch(error => { 97 | console.error('Error during service worker registration:', error); 98 | }); 99 | } 100 | 101 | function checkValidServiceWorker(swUrl, config) { 102 | // Check if the service worker can be found. If it can't reload the page. 103 | fetch(swUrl) 104 | .then(response => { 105 | // Ensure service worker exists, and that we really are getting a JS file. 106 | const contentType = response.headers.get('content-type'); 107 | if ( 108 | response.status === 404 || 109 | (contentType != null && contentType.indexOf('javascript') === -1) 110 | ) { 111 | // No service worker found. Probably a different app. Reload the page. 112 | navigator.serviceWorker.ready.then(registration => { 113 | registration.unregister().then(() => { 114 | window.location.reload(); 115 | }); 116 | }); 117 | } else { 118 | // Service worker found. Proceed as normal. 119 | registerValidSW(swUrl, config); 120 | } 121 | }) 122 | .catch(() => { 123 | console.log( 124 | 'No internet connection found. App is running in offline mode.' 125 | ); 126 | }); 127 | } 128 | 129 | export function unregister() { 130 | if ('serviceWorker' in navigator) { 131 | navigator.serviceWorker.ready.then(registration => { 132 | registration.unregister(); 133 | }); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /Chapter08/React/my-app/src/web-components/header-image/header-image.js: -------------------------------------------------------------------------------- 1 | export default class HeaderImage extends HTMLElement { 2 | constructor() { 3 | 4 | // We are not even going to touch this. 5 | super(); 6 | this.src = ''; 7 | this.alt = ''; 8 | 9 | // lets create our shadow root 10 | this.shadowObj = this.attachShadow({mode: 'open'}); 11 | 12 | // Then lets render the template 13 | this.render(); 14 | } 15 | 16 | static get observedAttributes() { 17 | return ['src', 'alttext']; 18 | } 19 | 20 | attributeChangedCallback(name, oldValue, newValue) { 21 | if (name == 'src') { 22 | this.src = newValue; 23 | this.render(); 24 | } 25 | if (name == 'alttext') { 26 | this.alt = newValue; 27 | this.render(); 28 | } 29 | } 30 | 31 | render() { 32 | this.shadowObj.innerHTML = this.getTemplate(); 33 | } 34 | 35 | getTemplate() { 36 | return ` 37 | ${this.alt} 39 | ${this.handleErrors()} 40 | 45 | `; 46 | } 47 | 48 | handleErrors() { 49 | if(!this.alt) { 50 | return ` 51 |
Missing Alt Text
52 | 57 | `; 58 | } 59 | 60 | return ``; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Chapter08/React/my-app/src/web-components/header-image/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Header Image 5 | 6 | 9 | 16 | 17 | 18 | 19 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Chapter08/Vue/app.js: -------------------------------------------------------------------------------- 1 | import './main-body.js'; 2 | 3 | new Vue({ 4 | el: '#vue-container' 5 | }); 6 | -------------------------------------------------------------------------------- /Chapter08/Vue/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 11 | 12 |
13 | 14 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Chapter08/Vue/main-body.js: -------------------------------------------------------------------------------- 1 | import HeaderImage from '../web-components/header-image/header-image.js'; 2 | 3 | Vue.component('main-body', { 4 | props: ['src', 'alt'], 5 | template: ` 6 |

This is the main body

7 | 8 | `, 9 | created: function() { 10 | customElements.define('header-image', HeaderImage); 11 | } 12 | }) 13 | -------------------------------------------------------------------------------- /Chapter08/Vue/web-components/header-image/header-image.js: -------------------------------------------------------------------------------- 1 | export default class HeaderImage extends HTMLElement { 2 | constructor() { 3 | 4 | // We are not even going to touch this. 5 | super(); 6 | this.src = ''; 7 | this.alt = ''; 8 | 9 | // lets create our shadow root 10 | this.shadowObj = this.attachShadow({mode: 'open'}); 11 | 12 | // Then lets render the template 13 | this.render(); 14 | } 15 | 16 | static get observedAttributes() { 17 | return ['src', 'alttext']; 18 | } 19 | 20 | attributeChangedCallback(name, oldValue, newValue) { 21 | if (name == 'src') { 22 | this.src = newValue; 23 | this.render(); 24 | } 25 | if (name == 'alttext') { 26 | this.alt = newValue; 27 | this.render(); 28 | } 29 | 30 | 31 | } 32 | 33 | render() { 34 | this.shadowObj.innerHTML = this.getTemplate(); 35 | } 36 | 37 | getTemplate() { 38 | return ` 39 | ${this.alt} 41 | ${this.handleErrors()} 42 | 47 | `; 48 | } 49 | 50 | handleErrors() { 51 | if(!this.alt) { 52 | return ` 53 |
Missing Alt Text
54 | 59 | `; 60 | } 61 | 62 | return ``; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Chapter08/Vue/web-components/header-image/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Header Image 5 | 6 | 9 | 16 | 17 | 18 | 19 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Chapter08/header-image/header-image.js: -------------------------------------------------------------------------------- 1 | export default class HeaderImage extends HTMLElement { 2 | constructor() { 3 | 4 | // We are not even going to touch this. 5 | super(); 6 | this.src = ''; 7 | this.alt = ''; 8 | 9 | // lets create our shadow root 10 | this.shadowObj = this.attachShadow({mode: 'open'}); 11 | 12 | // Then lets render the template 13 | this.render(); 14 | } 15 | 16 | static get observedAttributes() { 17 | return ['src', 'alttext']; 18 | } 19 | 20 | attributeChangedCallback(name, oldValue, newValue) { 21 | if (name == 'src') { 22 | this.src = newValue; 23 | this.render(); 24 | } 25 | if (name == 'alttext') { 26 | this.alt = newValue; 27 | this.render(); 28 | } 29 | 30 | 31 | } 32 | 33 | render() { 34 | this.shadowObj.innerHTML = this.getTemplate(); 35 | } 36 | 37 | getTemplate() { 38 | return ` 39 | ${this.alt} 41 | ${this.handleErrors()} 42 | 47 | `; 48 | } 49 | 50 | handleErrors() { 51 | if(!this.alt) { 52 | return ` 53 |
Missing Alt Text
54 | 59 | `; 60 | } 61 | 62 | return ``; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Chapter08/header-image/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Header Image 5 | 6 | 9 | 16 | 17 | 18 | 19 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Packt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | # Getting Started with Web Components 5 | 6 | Getting Started with Web Components 7 | 8 | This is the code repository for [Getting Started with Web Components](https://www.packtpub.com/web-development/getting-started-web-components?utm_source=github&utm_medium=repository&utm_campaign=9781838649234), published by Packt. 9 | 10 | **Build modular and reusable components for modern web applications using HTML and JavaScript** 11 | 12 | ## What is this book about? 13 | Web Components are a set of APIs that help you build reusable UI modules that can operate in any modern browser using just Vanilla JavaScript. The power of Web Components lies in their ability to build frontend web applications with or without web frameworks. 14 | 15 | This book covers the following exciting features: 16 | * Understand Web Component design, specifications, and life cycle 17 | * Create single-page applications using Web Components 18 | * Enable reusability and customization for your UI components 19 | * Implement Web Components in your web apps using Polymer and Stencil libraries 20 | * Build powerful frontend components from scratch and deploy them on web 21 | 22 | If you feel this book is for you, get your [copy](https://www.amazon.com/dp/1838649239) today! 23 | 24 | https://www.packtpub.com/ 26 | 27 | 28 | ## Instructions and Navigations 29 | All of the code is organized into folders. For example, Chapter02. 30 | 31 | The code will look like the following: 32 | ``` 33 | class myClass { 34 | constructor() { 35 | // do stuff 36 | } 37 | } 38 | ``` 39 | 40 | **Following is what you need for this book:** 41 | This book is for web developers who want to build reusable components for their modern web applications regardless of their web framework level of experience. The books assume working knowledge of HTML, CSS, and JavaScript. 42 | 43 | With the following software and hardware list you can run all code files present in the book (Chapter 1-8). 44 | 45 | ### Software and Hardware List 46 | 47 | | Chapter | Software required | OS required | 48 | | -------- | ------------------------------------------ | -----------------------------------| 49 | | 1 | Web Browser such as Chrome/Firefox/Safari | Windows, Mac OS X, and Linux (Any) | 50 | | 1 | Terminal / Command Prompt | Windows, Mac OS X, and Linux (Any) | 51 | | 1 | Code Editor such as Atom | Windows, Mac OS X, and Linux (Any) | 52 | | 1 | Python | Windows, Mac OS X, and Linux (Any) | 53 | | 6 | NodeJS | Windows, Mac OS X, and Linux (Any) | 54 | 55 | We also provide a PDF file that has color images of the screenshots/diagrams used in this book. [Click here to download it](https://static.packt-cdn.com/downloads/9781838649234_ColorImages.pdf). 56 | 57 | ### Related products 58 | * Learn React with TypeScript 3 [[Packt]](https://www.packtpub.com/web-development/learn-react-typescript-3?utm_source=github&utm_medium=repository&utm_campaign=9781789610253) [[Amazon]](https://www.amazon.com/dp/1789610257) 59 | 60 | * Learn WebAssembly [[Packt]](https://www.packtpub.com/web-development/learn-webassembly?utm_source=github&utm_medium=repository&utm_campaign=9781788997379) [[Amazon]](https://www.amazon.com/dp/1788997379) 61 | 62 | ## Get to Know the Author 63 | **Prateek Jadhwani** 64 | is a developer specializing in frontend technologies, living and working in the US. His experience includes 10 years of working as a frontend developer for many high-profile clients and many early-adoption side projects. Prateek had his first exposure to Web Components in 2014, and instantly fell in love with component-driven methodologies. Since then, he has evangelized Web Components and its related libraries to people at work and outside of work. Prateek loves all things open source and enjoys writing good JavaScript. A love for programming in general and a thirst for knowledge provide the motivation he carries through his work. 65 | 66 | 67 | ### Suggestions and Feedback 68 | [Click here](https://docs.google.com/forms/d/e/1FAIpQLSdy7dATC6QmEL81FIUuymZ0Wy9vH1jHkvpY57OiMeKGqib_Ow/viewform) if you have any feedback or suggestions. 69 | 70 | ### Download a free PDF 71 | 72 | If you have already purchased a print or Kindle version of this book, you can get a DRM-free PDF version at no cost.
Simply click on the link to claim your free PDF.
73 |

https://packt.link/free-ebook/9781838649234

--------------------------------------------------------------------------------