Skip to content

Commit 6b97878

Browse files
authored
Merge pull request Aman0509#2 from Aman0509/working-with-events
Working with events
2 parents 9e510da + 9fa4c04 commit 6b97878

File tree

9 files changed

+1416
-0
lines changed

9 files changed

+1416
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
- [Classes and Object Oriented Programming (OOP)](Classes-and-Object-Oriented-Programming/README.md)
1313
- [Constructor Functions and Prototypes](Constructor-Functions-and-Prototypes/README.md)
1414
- [Advanced DOM APIs](Advanced-DOM-APIs/README.md)
15+
- [Working with Events](Working-with-Events/README.md)
1516

1617
## References
1718

Working-with-Events/README.md

Lines changed: 761 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// console.log("Starting some analysis...");
2+
3+
// Demonstration of setInterval()
4+
5+
const intervalId = setInterval(() => {
6+
console.log("Starting some analysis...")
7+
}, 2000);
8+
9+
document.getElementById('stop-analytics').addEventListener(
10+
'click', () => {
11+
clearInterval(intervalId);
12+
// clearTimeout() will also work
13+
}
14+
)
Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
class DOMHelper {
2+
static clearEventListeners(element){
3+
const clonedElement = element.cloneNode(true);
4+
element.replaceWith(clonedElement);
5+
return clonedElement;
6+
}
7+
8+
static moveElement(elementId, newDestinationSelector) {
9+
const element = document.getElementById(elementId);
10+
const destinationElement = document.querySelector(newDestinationSelector);
11+
destinationElement.append(element);
12+
element.scrollIntoView({behavior: 'smooth'});
13+
}
14+
}
15+
16+
class Component {
17+
constructor(hostElementId, insertBefore=false) {
18+
if (hostElementId) {
19+
this.hostElement = document.getElementById(hostElementId);
20+
} else {
21+
this.hostElement = document.body;
22+
}
23+
this.insertBefore = insertBefore;
24+
}
25+
26+
detach() {
27+
if (this.element) {
28+
this.element.remove();
29+
// this.element.parentElement.removeChild(this.element);
30+
}
31+
}
32+
33+
attach() {
34+
this.hostElement.insertAdjacentElement(this.insertBefore ? 'afterbegin' : 'beforeend', this.element);
35+
}
36+
}
37+
38+
class Tooltip extends Component {
39+
constructor(closeNotifierFunction, toolTipText, hostElementId) {
40+
super(hostElementId);
41+
this.closeNotifier = closeNotifierFunction;
42+
this.toolTipText = toolTipText;
43+
this.create();
44+
}
45+
46+
closeToolTip = () => {
47+
this.detach();
48+
this.closeNotifier();
49+
};
50+
51+
create() {
52+
const toolTipElement = document.createElement('div');
53+
toolTipElement.className = 'card';
54+
// toolTipElement.textContent = this.toolTipText;
55+
const tootTipTemplate = document.getElementById('tooltip');
56+
const tootTipBody = document.importNode(tootTipTemplate.content, true);
57+
tootTipBody.querySelector('p').textContent = this.toolTipText;
58+
toolTipElement.append(tootTipBody);
59+
60+
const hostElementLeft = this.hostElement.offsetLeft;
61+
const hostElementTop = this.hostElement.offsetTop;
62+
const hostElementHeight = this.hostElement.offsetHeight;
63+
const parentElementScrolling = this.hostElement.parentElement.scrollTop;
64+
65+
const x = hostElementLeft + 20;
66+
const y = hostElementTop + hostElementHeight - parentElementScrolling - 10;
67+
68+
toolTipElement.style.position = 'absolute';
69+
toolTipElement.style.left = x + 'px';
70+
toolTipElement.style.top = y + 'px';
71+
72+
toolTipElement.addEventListener('click', this.closeToolTip);
73+
this.element = toolTipElement;
74+
}
75+
}
76+
77+
class ProjectItem {
78+
hasActiveTooltip = false;
79+
80+
constructor(id, updateProjectListFunction, type) {
81+
this.id = id;
82+
this.updateProjectListHandler = updateProjectListFunction;
83+
this.connectMoreInfoButton();
84+
this.connectSwitchButton(type);
85+
this.connectDrag();
86+
}
87+
88+
showMoreInfoHandler() {
89+
if (this.hasActiveTooltip) {
90+
return;
91+
}
92+
const projectElement = document.getElementById(this.id);
93+
const toolTipText = projectElement.dataset.extraInfo;
94+
const tooltip = new Tooltip(() => {
95+
this.hasActiveTooltip = false;
96+
}, toolTipText, this.id);
97+
tooltip.attach();
98+
this.hasActiveTooltip = true;
99+
}
100+
101+
connectDrag(){
102+
const item = document.getElementById(this.id)
103+
item.addEventListener('dragstart', event => {
104+
event.dataTransfer.setData('text/plain', this.id);
105+
event.dataTransfer.effectAllowed='move';
106+
});
107+
item.addEventListener('dragend', event => {
108+
console.log(event);
109+
});
110+
}
111+
112+
connectMoreInfoButton(){
113+
const prjItemElement = document.getElementById(this.id);
114+
const moreInfoBtn = prjItemElement.querySelector('button:first-of-type');
115+
moreInfoBtn.addEventListener('click', this.showMoreInfoHandler.bind(this));
116+
}
117+
118+
connectSwitchButton(type) {
119+
const prjItemElement = document.getElementById(this.id);
120+
let switchBtn = prjItemElement.querySelector('button:last-of-type');
121+
switchBtn = DOMHelper.clearEventListeners(switchBtn);
122+
switchBtn.textContent = type === 'active' ? 'Finish' : 'Activate';
123+
switchBtn.addEventListener('click', this.updateProjectListHandler.bind(null, this.id));
124+
}
125+
126+
update(updateProjectListFn, type) {
127+
this.updateProjectListHandler = updateProjectListFn;
128+
this.connectSwitchButton(type);
129+
}
130+
}
131+
132+
class ProjectList {
133+
projects = [];
134+
135+
constructor(type) {
136+
this.type = type;
137+
const prjItems = document.querySelectorAll(`#${type}-projects li`);
138+
for (const prjItem of prjItems) {
139+
this.projects.push(new ProjectItem(prjItem.id, this.switchProject.bind(this), this.type));
140+
}
141+
this.connectDroppable();
142+
}
143+
144+
connectDroppable(){
145+
const list = document.querySelector(`#${this.type}-projects ul`);
146+
147+
list.addEventListener('dragenter', event => {
148+
if (event.dataTransfer.types[0]==='text/plain'){
149+
list.parentElement.classList.add('droppable');
150+
event.preventDefault();
151+
}
152+
});
153+
154+
list.addEventListener('dragover', event => {
155+
if (event.dataTransfer.types[0]==='text/plain'){
156+
event.preventDefault();
157+
}
158+
});
159+
160+
list.addEventListener('dragleave', event => {
161+
if (event.relatedTarget.closest(`#${this.type}-projects ul`) !== list) {
162+
list.parentElement.classList.remove('droppable');
163+
}
164+
});
165+
166+
list.addEventListener('drop', event => {
167+
const prjId = event.dataTransfer.getData('text/plain');
168+
if (this.projects.find(p => p.id === prjId)) {
169+
return;
170+
}
171+
document.getElementById(prjId).querySelector('button:last-of-type').click();
172+
list.parentElement.classList.remove('droppable');
173+
});
174+
175+
}
176+
177+
setSwitchHandlerFunction(switchHandlerFunction) {
178+
this.switchHandler = switchHandlerFunction;
179+
}
180+
181+
addProject(project) {
182+
this.projects.push(project);
183+
DOMHelper.moveElement(project.id, `#${this.type}-projects ul`);
184+
project.update(this.switchProject.bind(this), this.type);
185+
}
186+
187+
switchProject(projectId) {
188+
// adding project to another bucket
189+
this.switchHandler(this.projects.find(p => p.id === projectId));
190+
191+
// remove project
192+
// One way to do it
193+
// const projectIndex = this.projects.findIndex(p => p.id === projectId);
194+
// this.projects.splice(projectIndex, 1);
195+
196+
// Another way of removing the project
197+
this.projects = this.projects.filter(p => p.id !== projectId);
198+
}
199+
}
200+
201+
class App {
202+
static init(){
203+
const activeProjectsList = new ProjectList('active');
204+
const finishedProjectsList = new ProjectList('finished');
205+
activeProjectsList.setSwitchHandlerFunction(finishedProjectsList.addProject.bind(finishedProjectsList));
206+
finishedProjectsList.setSwitchHandlerFunction(activeProjectsList.addProject.bind(activeProjectsList));
207+
208+
/*
209+
This is an example of how JavaScript can execute code asynchronously via setTimeout() and setInterval(), a topic we haven't covered yet. When you set a timer in JavaScript, it won't pause the execution of your entire script. Instead, the browser registers the timer in the background and manages it, so your script can continue to run normally without interruption. When the timer expires, the browser will execute the registered function. It's important to understand that this process doesn't pause script execution, and the browser takes care of managing the timer in the background. This ensures that your script can continue to run without any pauses or disruptions.
210+
*/
211+
// document.getElementById('start-analytics').addEventListener('click', this.startAnalytics);
212+
const timerId = setTimeout(this.startAnalytics, 3000);
213+
214+
// If you click on `Stop Analytics` button before 3 seconds, the action will not be happened
215+
document.getElementById('stop-analytics').addEventListener('click', () => {clearTimeout(timerId);})
216+
}
217+
218+
// This is just a random function to demonstrate about adding a JS dynamically on some event
219+
static startAnalytics() {
220+
const analyticsScript = document.createElement('script');
221+
analyticsScript.src = 'assets/scripts/analytics.js';
222+
analyticsScript.defer = true;
223+
document.head.append(analyticsScript);
224+
}
225+
}
226+
227+
App.init();
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
const button = document.querySelector('button');
2+
3+
/* Using the `on` property */
4+
5+
// button.onclick() = function() {
6+
7+
// }
8+
9+
// const buttonClickHandler = () => {
10+
// alert('Button was clicked!');
11+
// }
12+
13+
const buttonClickHandler = event => {
14+
console.log(event);
15+
// event.target.disabled=true;
16+
}
17+
18+
const anotherButtonClickHandler= () => {
19+
console.log('This was clicked!');
20+
}
21+
22+
// Disadvantage of `on` property method is that you can assign multiple functions to an event
23+
// button.onclick = buttonClickHandler;
24+
// button.onclick = anotherButtonClickHandler;
25+
26+
// Recommended Approach
27+
// button.addEventListener('click', anotherButtonClickHandler);
28+
29+
// Suppose, we are removing event listener after 2 seconds
30+
// setTimeout(() => {
31+
// button.removeEventListener('click', anotherButtonClickHandler)
32+
// }, 3000);
33+
34+
// buttons.forEach(btn => {
35+
// btn.addEventListener('click', buttonClickHandler);
36+
// });
37+
38+
// buttons.forEach(btn => {
39+
// btn.addEventListener('mouseenter', buttonClickHandler);
40+
// });
41+
42+
// window.addEventListener('scroll', event => {
43+
// console.log(event);
44+
// });
45+
46+
const form = document.querySelector('form');
47+
48+
// with `preventDefault()`, we will stop the default behavior of submit event
49+
form.addEventListener('submit', event => {
50+
event.preventDefault();
51+
console.log(event);
52+
});
53+
54+
const div = document.querySelector('div');
55+
56+
// Bubbling Phase
57+
div.addEventListener('click', event => {
58+
console.log('CLICKED DIV');
59+
console.log(event);
60+
});
61+
62+
button.addEventListener('click', event => {
63+
console.log('CLICKED BUTTON');
64+
event.stopPropagation();
65+
// event.stopImmediatePropagation();
66+
console.log(event);
67+
});
68+
69+
// Capturing Event
70+
// div.addEventListener('click', event => {
71+
// console.log('CLICKED DIV');
72+
// console.log(event);
73+
// }, true);
74+
75+
// button.addEventListener('click', event => {
76+
// console.log('CLICKED BUTTON');
77+
// console.log(event);
78+
// });
79+
80+
/*
81+
Event Delegation
82+
83+
Scenario - We have list of items and we want to change its color to red when clicked and if it is already red then revert.
84+
85+
*/
86+
87+
// Approach 1 - Brute Force (Demerits - Can get cumbersome and can impact performance)
88+
89+
// const listItems = document.querySelectorAll('li');
90+
91+
// listItems.forEach(listItem => {
92+
// listItem.addEventListener('click', event => {
93+
// event.target.classList.toggle('highlight');
94+
// })
95+
// });
96+
97+
// Approach 2 - Event Delegation
98+
99+
const list = document.querySelector('ul');
100+
// list.addEventListener('click', event => {
101+
// event.target.classList.toggle('highlight');
102+
// });
103+
104+
// Event delegation will work unexpectedly when in our example, list items are nested
105+
106+
list.addEventListener('click', function(event) {
107+
event.target.closest('li').classList.toggle('highlight');
108+
// Triggering button programmatically
109+
button.click();
110+
console.log(this);
111+
});

0 commit comments

Comments
 (0)