14 |
15 |
16 |
--------------------------------------------------------------------------------
/Part 1/UserCard/UserCard.js:
--------------------------------------------------------------------------------
1 | const currentDocument = document.currentScript.ownerDocument;
2 |
3 | class UserCard extends HTMLElement {
4 | constructor() {
5 | // If you define a constructor, always call super() first as it is required by the CE spec.
6 | super();
7 |
8 | // Setup a click listener on
9 | this.addEventListener('click', e => {
10 | this.toggleCard();
11 | });
12 | }
13 |
14 | // Called when element is inserted in DOM
15 | connectedCallback() {
16 | const shadowRoot = this.attachShadow({ mode: 'open' });
17 |
18 | // Select the template and clone it. Finally attach the cloned node to the shadowDOM's root.
19 | // Current document needs to be defined to get DOM access to imported HTML
20 | const template = currentDocument.querySelector('#user-card-template');
21 | const instance = template.content.cloneNode(true);
22 | shadowRoot.appendChild(instance);
23 |
24 | // Extract the attribute user-id from our element.
25 | // Note that we are going to specify our cards like:
26 | //
27 | const userId = this.getAttribute('user-id');
28 |
29 | // Fetch the data for that user Id from the API and call the render method with this data
30 | fetch(`https://jsonplaceholder.typicode.com/users/${userId}`)
31 | .then((response) => response.text())
32 | .then((responseText) => {
33 | this.render(JSON.parse(responseText));
34 | })
35 | .catch((error) => {
36 | console.error(error);
37 | });
38 | }
39 |
40 | render(userData) {
41 | // Fill the respective areas of the card using DOM manipulation APIs
42 | // All of our components elements reside under shadow dom. So we created a this.shadowRoot property
43 | // We use this property to call selectors so that the DOM is searched only under this subtree
44 | this.shadowRoot.querySelector('.card__full-name').innerHTML = userData.name;
45 | this.shadowRoot.querySelector('.card__user-name').innerHTML = userData.username;
46 | this.shadowRoot.querySelector('.card__website').innerHTML = userData.website;
47 | this.shadowRoot.querySelector('.card__address').innerHTML = `
25 |
26 |
--------------------------------------------------------------------------------
/Part 2/components/PeopleController/PeopleList/PeopleList.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | const currentDocument = document.currentScript.ownerDocument;
3 |
4 | function _createPersonListElement(self, person) {
5 | let li = currentDocument.createElement('LI');
6 | li.innerHTML = person.name;
7 | li.className = 'people-list__name'
8 | li.onclick = () => {
9 | let event = new CustomEvent("PersonClicked", {
10 | detail: {
11 | personId: person.id
12 | },
13 | bubbles: true
14 | });
15 | self.dispatchEvent(event);
16 | }
17 | return li;
18 | }
19 |
20 | class PeopleList extends HTMLElement {
21 | constructor() {
22 | // If you define a constructor, always call super() first as it is required by the CE spec.
23 | super();
24 |
25 | // A private property that we'll use to keep track of list
26 | let _list = [];
27 |
28 | // Use defineProperty to define a prop on this object, ie, the component.
29 | // Whenever list is set, call render. This way when the parent component sets some data
30 | // on the child object, we can automatically update the child.
31 | Object.defineProperty(this, 'list', {
32 | get: () => _list,
33 | set: (list) => {
34 | _list = list;
35 | this.render();
36 | }
37 | });
38 | }
39 |
40 | connectedCallback() {
41 | // Create a Shadow DOM using our template
42 | const shadowRoot = this.attachShadow({ mode: 'open' });
43 | const template = currentDocument.querySelector('#people-list-template');
44 | const instance = template.content.cloneNode(true);
45 | shadowRoot.appendChild(instance);
46 | }
47 |
48 | render() {
49 | let ulElement = this.shadowRoot.querySelector('.people-list__list');
50 | ulElement.innerHTML = '';
51 |
52 | this.list.forEach(person => {
53 | let li = _createPersonListElement(this, person);
54 | ulElement.appendChild(li);
55 | });
56 | }
57 | }
58 |
59 | customElements.define('people-list', PeopleList);
60 | })();
--------------------------------------------------------------------------------
/Part 2/components/PeopleController/PersonDetail/PersonDetail.css:
--------------------------------------------------------------------------------
1 | .card__user-card-container {
2 | text-align: center;
3 | border-radius: 5px;
4 | border: 1px solid grey;
5 | font-family: Helvetica;
6 | margin: 3px;
7 | }
8 |
9 | .card__user-card-container:hover {
10 | box-shadow: 3px 3px 3px;
11 | }
12 |
13 | .card__hidden-content {
14 | display: none;
15 | }
16 |
17 | .card__details-btn {
18 | background-color: #dedede;
19 | padding: 6px;
20 | margin-bottom: 8px;
21 | }
--------------------------------------------------------------------------------
/Part 2/components/PeopleController/PersonDetail/PersonDetail.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | (
6 | )
7 |
8 |
Website:
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/Part 2/components/PeopleController/PersonDetail/PersonDetail.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | const currentDocument = document.currentScript.ownerDocument;
3 |
4 | class PersonDetail extends HTMLElement {
5 | constructor() {
6 | // If you define a constructor, always call super() first as it is required by the CE spec.
7 | super();
8 |
9 | // Setup a click listener on
10 | this.addEventListener('click', e => {
11 | this.toggleCard();
12 | });
13 | }
14 |
15 | // Called when element is inserted in DOM
16 | connectedCallback() {
17 | const shadowRoot = this.attachShadow({ mode: 'open' });
18 | const template = currentDocument.querySelector('#person-detail-template');
19 | const instance = template.content.cloneNode(true);
20 | shadowRoot.appendChild(instance);
21 | }
22 |
23 | // Creating an API function so that other components can use this to populate this component
24 | updatePersonDetails(userData) {
25 | this.render(userData);
26 | }
27 |
28 | // Function to populate the card(Can be made private)
29 | render(userData) {
30 | this.shadowRoot.querySelector('.card__full-name').innerHTML = userData.name;
31 | this.shadowRoot.querySelector('.card__user-name').innerHTML = userData.username;
32 | this.shadowRoot.querySelector('.card__website').innerHTML = userData.website;
33 | this.shadowRoot.querySelector('.card__address').innerHTML = `
Address
34 | ${userData.address.suite},
35 | ${userData.address.street},
36 | ${userData.address.city},
37 | Zipcode: ${userData.address.zipcode}`
38 | }
39 |
40 | toggleCard() {
41 | let elem = this.shadowRoot.querySelector('.card__hidden-content');
42 | let btn = this.shadowRoot.querySelector('.card__details-btn');
43 | btn.innerHTML = elem.style.display == 'none' ? 'Less Details' : 'More Details';
44 | elem.style.display = elem.style.display == 'none' ? 'block' : 'none';
45 | }
46 | }
47 |
48 | customElements.define('person-detail', PersonDetail);
49 | })()
--------------------------------------------------------------------------------
/Part 2/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Web Component Part 2
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Web Components tutorials
2 |
3 | This repository contains the code I wrote to go along with [web component posts](https://ayushgp.github.io/html-web-components-using-vanilla-js/). To run the code, you need a static server. I use [static-server](https://github.com/nbluis/static-server) for it. Go to either directory and run:
4 |
5 | ```
6 | $ static-server
7 | ```
8 |
9 | Then navigate to [localhost](http://localhost:9080).
10 |
11 | Links to tutorials:
12 | 1. Part 1: [HTML Web Component using Vanilla JavaScript](https://ayushgp.github.io/html-web-components-using-vanilla-js/)
13 | 2. Part 2: [Making Higher Order Components](https://ayushgp.github.io/html-web-components-using-vanilla-js-part-2/)
14 | 3. Part 3: [Using Attributes to define web components' behavior](https://ayushgp.github.io/html-web-components-using-vanilla-js-part-3/)
15 |
16 | ### Components Created for examples:
17 | #### Part 2:
18 | 
19 |
20 | #### Part 3:
21 | 
22 |
23 | ### About Web Components:
24 | The HTML and DOM standards define four new standards/APIs that are helpful for defining Web Components. These standards are:
25 |
26 | 1. [Custom Elements](https://www.w3.org/TR/custom-elements/): With Custom Elements, web developers can create new HTML tags, beef-up existing HTML tags, or extend the components other developers have authored. This API is the foundation of Web Components.
27 | 2. [HTML Templates](https://www.html5rocks.com/en/tutorials/webcomponents/template/#toc-pillars): It defines a new `` element, which describes a standard DOM-based approach for client-side templating. Templates allow you to declare fragments of markup which are parsed as HTML, go unused at page load, but can be instantiated later on at runtime.
28 | 3. [Shadow DOM](https://dom.spec.whatwg.org/#shadow-trees): Shadow DOM is designed as a tool for building component-based apps. It brings solutions for common problems in web development. It allows you to isolate DOM for the component and scope, and simplify CSS, etc.
29 | 4. [HTML Imports](https://www.html5rocks.com/en/tutorials/webcomponents/imports/): While HTML templates allow you to create new templates, HTML imports allows you to import these templates from different HTML files. Imports help keep code more organized by neatly arranging your components as separate HTML files.
30 |
--------------------------------------------------------------------------------