Skip to content

Commit e9471e1

Browse files
committed
refactor: js also fixing multiple instances issue with mutation observer
1 parent 6138050 commit e9471e1

File tree

12 files changed

+364
-245
lines changed

12 files changed

+364
-245
lines changed

source/js/front/like.ts

Lines changed: 0 additions & 163 deletions
Original file line numberDiff line numberDiff line change
@@ -1,164 +1 @@
1-
import StorageInterface from "./storage/storageInterface";
2-
import LikeInstancesStorage from "./storage/likeInstancesStorage";
3-
4-
const likeIconSelector: string = '[data-like-icon]';
5-
const likedIconClass: string = 'material-symbols--filled';
6-
const postIdAttribute: string = 'data-post-id';
7-
const postTypeAttribute: string = 'data-post-type';
8-
const blogIdAttribute: string = 'data-blog-id';
9-
const tooltipAttribute: string = 'data-tooltip';
10-
const iconWrapperAttribute: string = 'data-js-like-icon-wrapper';
11-
const likeIconInitializedAttribute: string = 'data-like-icon-initialized';
12-
13-
export default class Like {
14-
private hasTooltip: boolean;
15-
private key: string;
16-
constructor(
17-
private likeStorage: StorageInterface,
18-
private likeInstancesStorage: LikeInstancesStorage,
19-
private button: Element,
20-
private wrapper: Element|null,
21-
private postId: string,
22-
private postType: string,
23-
private tooltipLike: string,
24-
private tooltipUnlike: string,
25-
private blogId: string
26-
) {
27-
this.key = `${this.blogId}-${this.postId}`;
28-
this.hasTooltip = Boolean(this.wrapper && (!!this.tooltipLike || !!this.tooltipUnlike));
29-
this.updateLikedStatus();
30-
this.setTooltip();
31-
this.setListener();
32-
}
33-
34-
// Sets the event listener for the like button
35-
private setListener() {
36-
this.button.addEventListener('click', (e) => {
371
e.preventDefault();
38-
e.stopPropagation();
39-
this.likeStorage.set(this.postId, this.postType, this.blogId);
40-
this.toggleLiked();
41-
window.dispatchEvent(this.likedPostsUpdatedEvent());
42-
});
43-
}
44-
45-
// Sets the tooltip for the like button
46-
private setTooltip() {
47-
if (!this.hasTooltip) {
48-
return;
49-
}
50-
51-
const isLiked = this.likeStorage.get()[this.key];
52-
53-
if (isLiked) {
54-
if (this.tooltipUnlike) {
55-
this.wrapper!.setAttribute(tooltipAttribute, this.tooltipUnlike);
56-
} else {
57-
this.wrapper!.removeAttribute(tooltipAttribute);
58-
}
59-
} else {
60-
if (this.tooltipLike) {
61-
this.wrapper!.setAttribute(tooltipAttribute, this.tooltipLike);
62-
} else {
63-
this.wrapper!.removeAttribute(tooltipAttribute);
64-
}
65-
}
66-
}
67-
68-
// Toggles the like button (liked/not liked) and updates all like buttons with the same postId
69-
private toggleLiked() {
70-
this.likeInstancesStorage.getInstances(this.key).forEach((instance) => {
71-
instance.updateLikedStatus();
72-
instance.setTooltip();
73-
});
74-
}
75-
76-
// Updates the state of the like button (liked/not liked)
77-
public updateLikedStatus() {
78-
const isLiked = this.likeStorage.get()[this.key];
79-
if (isLiked) {
80-
this.button.classList.add(likedIconClass);
81-
} else {
82-
this.button.classList.remove(likedIconClass);
83-
}
84-
}
85-
86-
// Custom event to update liked posts length
87-
public likedPostsUpdatedEvent() {
88-
return new CustomEvent('likedPostsLengthUpdated', {});
89-
}
90-
}
91-
92-
// Initialize like buttons/icons
93-
export function initializeLikeButtons(
94-
likeStorage: StorageInterface,
95-
likeInstancesStorage: LikeInstancesStorage,
96-
tooltipLike: string,
97-
tooltipUnlike: string
98-
) {
99-
const createLikeInstance = (button: Element) => {
100-
const postId = button.getAttribute(postIdAttribute);
101-
const postType = button.getAttribute(postTypeAttribute);
102-
const blogId = button.getAttribute(blogIdAttribute)
103-
104-
if (!postId || !postType || !blogId) {
105-
console.warn('Like button is missing required blogId for likeable post', button);
106-
return;
107-
}
108-
109-
110-
const wrapper = button.closest(`[${iconWrapperAttribute}]`);
111-
112-
likeInstancesStorage.addInstance(
113-
`${blogId}-${postId}`,
114-
new Like(
115-
likeStorage,
116-
likeInstancesStorage,
117-
button,
118-
wrapper,
119-
postId,
120-
postType,
121-
tooltipLike,
122-
tooltipUnlike,
123-
blogId
124-
)
125-
);
126-
};
127-
128-
document.querySelectorAll(likeIconSelector).forEach((button) => {
129-
createLikeInstance(button);
130-
});
131-
132-
const observer = new MutationObserver((mutations) => {
133-
mutations.forEach((mutation) => {
134-
if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
135-
[...mutation.addedNodes].forEach((node) => {
136-
if (node.nodeType === Node.ELEMENT_NODE) {
137-
const element = node as Element;
138-
139-
if (element.matches(likeIconSelector)) {
140-
if (element.hasAttribute(likeIconInitializedAttribute)) {
141-
return;
142-
}
143-
144-
element.setAttribute(likeIconInitializedAttribute, 'true');
145-
createLikeInstance(element);
146-
} else {
147-
element.querySelectorAll(likeIconSelector).forEach((button) => {
148-
if (button.hasAttribute(likeIconInitializedAttribute)) {
149-
return;
150-
}
151-
152-
button.setAttribute(likeIconInitializedAttribute, 'true');
153-
createLikeInstance(button);
154-
});
155-
}
156-
157-
}
158-
});
159-
}
160-
});
161-
});
162-
163-
observer.observe(document.body, { childList: true, subtree: true });
164-
}

source/js/front/like/like.ts

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import StorageInterface from "../storage/storageInterface";
2+
import LikeInstancesStorage from "../storage/likeInstancesStorage";
3+
4+
const likedIconClass: string = 'material-symbols--filled';
5+
const tooltipAttribute: string = 'data-tooltip';
6+
7+
export default class Like {
8+
private hasTooltip: boolean;
9+
private key: string;
10+
constructor(
11+
private likeStorage: StorageInterface,
12+
private likeInstancesStorage: LikeInstancesStorage,
13+
private button: Element,
14+
private wrapper: Element|null,
15+
private postId: string,
16+
private postType: string,
17+
private tooltipLike: string,
18+
private tooltipUnlike: string,
19+
private blogId: string
20+
) {
21+
this.key = `${this.blogId}-${this.postId}`;
22+
this.hasTooltip = Boolean(this.wrapper && (!!this.tooltipLike || !!this.tooltipUnlike));
23+
this.updateLikedStatus();
24+
this.setTooltip();
25+
this.setListener();
26+
}
27+
28+
// Sets the event listener for the like button
29+
private setListener() {
30+
this.button.addEventListener('click', (e) => {
31+
e.preventDefault();
32+
e.stopPropagation();
33+
this.likeStorage.set(this.postId, this.postType, this.blogId);
34+
this.toggleLiked();
35+
window.dispatchEvent(this.likedPostsUpdatedEvent());
36+
});
37+
}
38+
39+
// Sets the tooltip for the like button
40+
private setTooltip() {
41+
if (!this.hasTooltip) {
42+
return;
43+
}
44+
45+
const isLiked = this.likeStorage.get()[this.key];
46+
47+
if (isLiked) {
48+
if (this.tooltipUnlike) {
49+
this.wrapper!.setAttribute(tooltipAttribute, this.tooltipUnlike);
50+
} else {
51+
this.wrapper!.removeAttribute(tooltipAttribute);
52+
}
53+
} else {
54+
if (this.tooltipLike) {
55+
this.wrapper!.setAttribute(tooltipAttribute, this.tooltipLike);
56+
} else {
57+
this.wrapper!.removeAttribute(tooltipAttribute);
58+
}
59+
}
60+
}
61+
62+
// Toggles the like button (liked/not liked) and updates all like buttons with the same postId
63+
private toggleLiked() {
64+
this.likeInstancesStorage.getInstances(this.key).forEach((instance) => {
65+
instance.updateLikedStatus();
66+
instance.setTooltip();
67+
});
68+
}
69+
70+
// Updates the state of the like button (liked/not liked)
71+
public updateLikedStatus() {
72+
const isLiked = this.likeStorage.get()[this.key];
73+
if (isLiked) {
74+
this.button.classList.add(likedIconClass);
75+
} else {
76+
this.button.classList.remove(likedIconClass);
77+
}
78+
}
79+
80+
// Custom event to update liked posts length
81+
public likedPostsUpdatedEvent() {
82+
return new CustomEvent('likedPostsLengthUpdated', {});
83+
}
84+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import Like from "./like";
2+
import LikeInstancesStorage from "../storage/likeInstancesStorage";
3+
import StorageInterface from "../storage/storageInterface";
4+
5+
class LikeFactory {
6+
private iconWrapperAttribute: string = 'data-js-like-icon-wrapper';
7+
private likeIconInitializedAttribute: string = 'data-like-icon-initialized'
8+
private postIdAttribute: string = 'data-post-id';
9+
private postTypeAttribute: string = 'data-post-type';
10+
private blogIdAttribute: string = 'data-blog-id';
11+
12+
constructor(
13+
private likeStorage: StorageInterface,
14+
private likeInstancesStorage: LikeInstancesStorage,
15+
private tooltipLike: string,
16+
private tooltipUnlike: string
17+
) {}
18+
19+
public create(likeButton: HTMLElement): Like | undefined {
20+
if (likeButton.hasAttribute(this.likeIconInitializedAttribute)) {
21+
return undefined;
22+
}
23+
24+
likeButton.setAttribute(this.likeIconInitializedAttribute, 'true');
25+
26+
const postId = likeButton.getAttribute(this.postIdAttribute);
27+
const postType = likeButton.getAttribute(this.postTypeAttribute);
28+
const blogId = likeButton.getAttribute(this.blogIdAttribute);
29+
const wrapper = likeButton.closest(`[${this.iconWrapperAttribute}]`);
30+
31+
if (!postId || !postType || !blogId) {
32+
console.warn('Like button is missing required blogId for likeable post', likeButton);
33+
return undefined;
34+
}
35+
36+
const like = new Like(
37+
this.likeStorage,
38+
this.likeInstancesStorage,
39+
likeButton,
40+
wrapper,
41+
postId,
42+
postType,
43+
this.tooltipLike,
44+
this.tooltipUnlike,
45+
blogId
46+
);
47+
48+
this.likeInstancesStorage.addInstance(
49+
`${blogId}-${postId}`,
50+
like
51+
);
52+
53+
return like;
54+
}
55+
}
56+
57+
export default LikeFactory;
Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
import LikedPostsApiUrlBuilder from "./helpers/likedPostsApiUrlBuilder";
2-
import LikedPostsStructurer from "./helpers/likedPostsStructurer";
3-
import { LikedPostsMeta, LikedPosts, WpApiSettings } from "./like-posts";
4-
import StorageInterface from "./storage/storageInterface";
1+
import LikedPostsApiUrlBuilder from "../helpers/likedPostsApiUrlBuilder";
2+
import { LikedPostsMeta, LikedPosts, WpApiSettings } from "../like-posts";
3+
import StorageInterface from "../storage/storageInterface";
54

65
class LikeModule {
76
private displayNoneClass: string = 'u-display--none';
@@ -10,7 +9,6 @@ class LikeModule {
109
private wpApiSettings: WpApiSettings,
1110
private likeStorage: StorageInterface,
1211
private sharedPosts: string|null,
13-
private likedPostsStructurer: LikedPostsStructurer,
1412
private likedPostsApiUrlBuilder: LikedPostsApiUrlBuilder,
1513
private postTypesToShow: Array<string>,
1614
private postAppearance: string,

0 commit comments

Comments
 (0)