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 ( ) ;
0 commit comments