Skip to content

Commit c171835

Browse files
committed
finish this course, congratulations
1 parent 9c37c8b commit c171835

File tree

2 files changed

+300
-3
lines changed

2 files changed

+300
-3
lines changed

Learn/31. Web Components.md

Lines changed: 267 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ There is specific web component lifecycle which the browser follows, when instan
109109

110110
- For this, there is another method we can add in our class which will be executed by the browser once this element (custom component) has been mounted onto the browser's DOM, and that is the `connectedCallback()` method. This is called when our element has been attached to the DOM; and therefore this is a place for DOM initializations. So where we can now start adding content or where we can start accessing the DOM.
111111

112-
- There also is a `disconnectedCallback()` method which will also be executed automatically for us and this method will be called by the browser whenever our element (i.e. our custom web component) is detached from the DOM. This is a great method for some cleanup work; for example, if you were sending a HttpRequest, this would be where you could cancle it.
112+
- There also is a `disconnectedCallback()` method which will also be executed automatically for us and this method will be called by the browser whenever our element (i.e. our custom web component) is detached from the DOM. This is a great method for some cleanup work; for example, if you were sending a HttpRequest, this would be where you could cancel it.
113113

114114
- There is another method, the `attributeChangedCallback()` and that will be important for listening to changes to attributes to our own element. This is important for updating the data and the DOM of our web component when some attributes which can passed to our component changed.
115115

@@ -367,7 +367,7 @@ This web component now requires some content to be added to our HTML file where
367367

368368
We can bring back our removed text by `<slot></slot>` tag and nothing in between:
369369

370-
```js
370+
```html
371371
<!DOCTYPE html>
372372
<html lang="en">
373373

@@ -504,8 +504,12 @@ class Tooltip extends HTMLElement {
504504
customElements.define("ps-tooltip", Tooltip);
505505
```
506506

507+
**Note**: We can see the all nodes that accessed our slots with `slots.assignedNodes()` method. Consider that we make a array with `const slots = this.shadowRoot.querySelectorAll("slot")` code). You can also use `console.dir(slots[1].assignedNodes())` code (`1` is just a element index).
508+
507509
We used `this.shadowRoot.innerHTML` instead of appending the template content.
508510

511+
**Note**: It means that we can remove the `this.shadowRoot.appendChild(tooltipIcon);` line. Because we appended in above (i.e. `<span> (?)</span>`).
512+
509513
You might wonder: How can we set the HTML content here, inside our constructor? The reason is simple, `innerHTML` is just a property of our element here (of our object) and this is just setting up some HTML code that will be rendered to the DOM, once this element is mounted to the DOM. So unlike `appendChild()`, this does not try to access the DOM at this point; it just prepares some content for the DOM once it later is available and the browser will take care about rendering this when it's able to.
510514

511515
## Styling in Shadow Mode
@@ -1119,3 +1123,264 @@ attributeChangedCallback(name, oldValue, newValue) {
11191123
Now the tooltip will works dynamically.
11201124

11211125
## Working with `disconnectedCallback()`
1126+
1127+
We can cleanup our code with this method. This will be executed when the element is removed from the DOM. Consider:
1128+
1129+
```js
1130+
class Tooltip extends HTMLElement {
1131+
constructor() {
1132+
super();
1133+
this.tooltipContainer;
1134+
this._tooltipText = "This is a dummy text!";
1135+
this.attachShadow({ mode: "open" });
1136+
this.shadowRoot.innerHTML = `
1137+
<style>
1138+
div {
1139+
font-weight: normal;
1140+
background-color: black;
1141+
color: white;
1142+
position: absolute;
1143+
top: 1.5rem;
1144+
z-index: 10;
1145+
padding: 0.15rem;
1146+
border-radius: 3px;
1147+
box-shadow: 1px 1px 6px rgba(0,0,0,0,0.26);
1148+
font-family: sans-serif;
1149+
}
1150+
1151+
::slotted(.highlight) {
1152+
border-bottom: 2px dotted red;
1153+
}
1154+
1155+
:host(.important) {
1156+
background-color: var(--primary-color, #ccc);
1157+
padding: 0.15rem;
1158+
}
1159+
1160+
:host-context(p) {
1161+
font-weight: bold;
1162+
}
1163+
</style>
1164+
<slot>Default value!</slot>
1165+
<span> (?)</span>
1166+
`;
1167+
}
1168+
1169+
connectedCallback() {
1170+
if (this.hasAttribute("text")) {
1171+
this._tooltipText = this.getAttribute("text");
1172+
}
1173+
const tooltipIcon = this.shadowRoot.querySelector("span");
1174+
tooltipIcon.addEventListener("mouseenter", this._showTooltip.bind(this));
1175+
tooltipIcon.addEventListener("mouseleave", this._hideTooltip.bind(this));
1176+
this.style.position = "relative";
1177+
}
1178+
1179+
attributeChangedCallback(name, oldValue, newValue) {
1180+
if (oldValue === newValue) {
1181+
return;
1182+
}
1183+
1184+
if (name === "text") {
1185+
this._tooltipText = newValue;
1186+
}
1187+
}
1188+
1189+
static get observedAttributes() {
1190+
return ["text"];
1191+
}
1192+
1193+
disconnectedCallback() {
1194+
console.log("Disconnected");
1195+
}
1196+
1197+
_showTooltip() {
1198+
this.tooltipContainer = document.createElement("div");
1199+
this.tooltipContainer.textContent = this._tooltipText;
1200+
this.shadowRoot.appendChild(this.tooltipContainer);
1201+
}
1202+
1203+
_hideTooltip() {
1204+
this.shadowRoot.removeChild(this.tooltipContainer);
1205+
}
1206+
}
1207+
1208+
customElements.define("ps-tooltip", Tooltip);
1209+
```
1210+
1211+
Now if we remove the `<ps-tooltip>` from the DOM, the `"Disconnected"` message will rise up.
1212+
1213+
## Refactoring
1214+
1215+
This code is good. But we can make it better, for example:
1216+
1217+
```js
1218+
class Tooltip extends HTMLElement {
1219+
constructor() {
1220+
super();
1221+
this.tooltipVisible = false;
1222+
this.tooltipIcon;
1223+
this._tooltipText = "This is a dummy text!";
1224+
this.attachShadow({ mode: "open" });
1225+
this.shadowRoot.innerHTML = `
1226+
<style>
1227+
div {
1228+
font-weight: normal;
1229+
background-color: black;
1230+
color: white;
1231+
position: absolute;
1232+
top: 1.5rem;
1233+
z-index: 10;
1234+
padding: 0.15rem;
1235+
border-radius: 3px;
1236+
box-shadow: 1px 1px 6px rgba(0,0,0,0,0.26);
1237+
font-family: sans-serif;
1238+
}
1239+
1240+
::slotted(.highlight) {
1241+
border-bottom: 2px dotted red;
1242+
}
1243+
1244+
:host {
1245+
position: relative;
1246+
}
1247+
1248+
:host(.important) {
1249+
background-color: var(--primary-color, #ccc);
1250+
padding: 0.15rem;
1251+
}
1252+
1253+
:host-context(p) {
1254+
font-weight: bold;
1255+
}
1256+
</style>
1257+
<slot>Default value!</slot>
1258+
<span> (?)</span>
1259+
`;
1260+
}
1261+
1262+
connectedCallback() {
1263+
if (this.hasAttribute("text")) {
1264+
this._tooltipText = this.getAttribute("text");
1265+
}
1266+
this.tooltipIcon = this.shadowRoot.querySelector("span");
1267+
this.tooltipIcon.addEventListener(
1268+
"mouseenter",
1269+
this._showTooltip.bind(this)
1270+
);
1271+
this.tooltipIcon.addEventListener(
1272+
"mouseleave",
1273+
this._hideTooltip.bind(this)
1274+
);
1275+
this._render();
1276+
}
1277+
1278+
attributeChangedCallback(name, oldValue, newValue) {
1279+
if (oldValue === newValue) {
1280+
return;
1281+
}
1282+
1283+
if (name === "text") {
1284+
this._tooltipText = newValue;
1285+
}
1286+
}
1287+
1288+
static get observedAttributes() {
1289+
return ["text"];
1290+
}
1291+
1292+
disconnectedCallback() {
1293+
this.tooltipIcon.removeEventListener("mouseenter", this._showTooltip);
1294+
this.tooltipIcon.removeEventListener("mouseleave", this._hideTooltip);
1295+
}
1296+
1297+
_render() {
1298+
let tooltipContainer = this.shadowRoot.querySelector("div");
1299+
if (this.tooltipVisible) {
1300+
tooltipContainer = document.createElement("div");
1301+
tooltipContainer.textContent = this._tooltipText;
1302+
this.shadowRoot.appendChild(tooltipContainer);
1303+
} else {
1304+
if (tooltipContainer) {
1305+
this.shadowRoot.removeChild(tooltipContainer);
1306+
}
1307+
}
1308+
}
1309+
1310+
_showTooltip() {
1311+
this.tooltipVisible = true;
1312+
this._render();
1313+
}
1314+
1315+
_hideTooltip() {
1316+
this.tooltipVisible = false;
1317+
this._render();
1318+
}
1319+
}
1320+
1321+
customElements.define("ps-tooltip", Tooltip);
1322+
```
1323+
1324+
It's awesome, right? We have an understandable code. We also removed `this.style.position = "relative"` line and added it into a `:host` style.
1325+
1326+
## Custom Events
1327+
1328+
We can make custom events it help of `HTMLElement` object:
1329+
1330+
```js
1331+
class Eveeent extends HTMLElement {
1332+
constructor() {
1333+
super();
1334+
}
1335+
1336+
cancel(event) {
1337+
const cancelEvent = new Event("cancel", { bubbles: true, composed: true });
1338+
event.target.dispatchEvent(cancelEvent);
1339+
1340+
const example = document.queryselector("button");
1341+
example.adEventListener("cancel", func);
1342+
}
1343+
}
1344+
```
1345+
1346+
- The **`bubbles`** read-only property of the `Event` interface indicates whether the event bubbles up through the DOM or not.
1347+
1348+
A `Boolean`, which is `true` if the event bubbles up through the DOM.
1349+
1350+
- The read-only **`composed`** property of the `Event` interface returns a `Boolean` which indicates whether or not the event will propagate across the shadow DOM boundary into the standard DOM.
1351+
1352+
A `Boolean` which is `true` if the event will cross from the shadow DOM into the standard DOM after reaching the shadow root. (That is, the first node in the shadow DOM in which the event began to propagate).
1353+
1354+
This is a first way of creating custom elementm there is another and easier way to creating our own events:
1355+
1356+
```js
1357+
class Eveeent extends HTMLElement {
1358+
constructor() {
1359+
super();
1360+
}
1361+
1362+
cancel() {
1363+
const cancelEvent = new Event("cancel");
1364+
this.dispatchEvent(cancelEvent);
1365+
1366+
const example = document.queryselector("button");
1367+
example.adEventListener("cancel", func);
1368+
}
1369+
}
1370+
```
1371+
1372+
It's very easy to understand and useful. We directly use the `HTMLElement` global object for using the `dispatchEvent()` method and we don't need to pass the `bubbles` and `composed` into an object.
1373+
1374+
## CSS Animations
1375+
1376+
You can learn alot about CSS animations with this following link: <https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Transitions/Using_CSS_transitions>
1377+
1378+
## Useful Resources & Links
1379+
1380+
MDN Docs on Web Components: <https://developer.mozilla.org/en-US/docs/Web/Web_Components>
1381+
1382+
More about Templates & Slots: <https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_templates_and_slots>
1383+
1384+
Google Article on Custom Elements: <https://developers.google.com/web/fundamentals/web-components/customelements>
1385+
1386+
Google Article on Shadow DOM: <https://developers.google.com/web/fundamentals/web-components/shadowdom>

practice.js

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5564,10 +5564,42 @@ __proto__: WeakSet
55645564

55655565
// type CalculationResults = CalculationContainer[];
55665566

5567-
55685567
// logAndEcho("Hamid Alavi").split(" ");
55695568

55705569
// const result: Array<CalculationContainer> = [];
55715570

55725571
// const num1Input = document.getElementById("num2")!;
55735572
// const num2Input = document.getElementById("num2")! as HTMLInputElement;
5573+
5574+
// -----------------------------------------------
5575+
5576+
// web component - event
5577+
// class Eveeent extends HTMLElement {
5578+
// constructor() {
5579+
// super();
5580+
// }
5581+
5582+
// cancel(event) {
5583+
// const cancelEvent = new Event("cancel", { bubbles: true, composed: true });
5584+
// event.target.dispatchEvent(cancelEvent);
5585+
5586+
// const example = document.queryselector("button");
5587+
// example.adEventListener("cancel", func);
5588+
// }
5589+
// }
5590+
5591+
// ---
5592+
5593+
// class Eveeent extends HTMLElement {
5594+
// constructor() {
5595+
// super();
5596+
// }
5597+
5598+
// cancel() {
5599+
// const cancelEvent = new Event("cancel");
5600+
// this.dispatchEvent(cancelEvent);
5601+
5602+
// const example = document.queryselector("button");
5603+
// example.adEventListener("cancel", func);
5604+
// }
5605+
// }

0 commit comments

Comments
 (0)