PageSourceSearch

https://www.eventeny.com/js/Components/AvatarIcon.js?v=1790189618

js eventeny.com collected 2026-09-24 08:54:29 UTC 5,217 bytes, 148 lines download raw bytes

1/**
2 * <avatar-icon> Web Component
3 * ----------------------------
4 * A reusable avatar component that displays either a user image or fallback initials.
5 *
6 * USAGE:
7 * <avatar-icon
8 *   src="/users/pic/jane-150.jpg" //Optional -- full path, e.g. "/users/pic/{avatar_150}" or "/event-pics/{guest image}"
9 *   first-name="Jane" //Mandatory
10 *   last-name="Doe" //Mandatory
11 *   size="32" //Optional, defaults to 32 (px)
12 *   class="mr1"  //Optional
13 * >
14 * </avatar-icon>
15 *
16 * BEHAVIOR:
17 * - If the `src` attribute is provided, the component tries to load the image from that path directly.
18 * - If the image fails to load or `src` is not provided, it falls back to rendering the user's initials inside a circle.
19 * - The initials are derived from the `first-name` and `last-name` attributes (e.g., Jane Doe → "JD").
20 *
21 * ATTRIBUTES:
22 * - src (optional): Full path to the avatar image (e.g. "/users/pic/avatar-150.jpg")
23 * - first-name (required for fallback): The person's first name
24 * - last-name (required for fallback): The person's last name
25 * - size (optional): Diameter in pixels for both the image and the initials circle. Defaults to 32.
26 * - class (optional): CSS classes passed to the internal avatar element (image or initials container)
27 *
28 * STYLES:
29 * - avatar-icon: class applied to <img> element when using an image
30 * - avatar-container: fallback container for initials
31 * - avatar-initials: styled text for the initials
32 *
33 * NOTES:
34 * - External CSS classes added to <avatar-icon> are applied to the internal root element (either <img> or the fallback <div>).
35 * - The component uses Shadow DOM to encapsulate styles.
36 */
37
38class AvatarIcon extends HTMLElement {
39  constructor() {
40    super();
41    this.attachShadow({ mode: 'open' });
42
43    //Base CSS
44    const baseCss2017 = document.createElement('link');
45    baseCss2017.rel = 'stylesheet';
46    baseCss2017.href = '/css/base-2017-05-15.css';
47    this.shadowRoot.appendChild(baseCss2017);
48
49    const baseCss2018 = document.createElement('link');
50    baseCss2018.rel = 'stylesheet';
51    baseCss2018.href = '/css/base-2018-11-17.css';
52    this.shadowRoot.appendChild(baseCss2018);
53
54    // Diameter drives both the image/container box and the initials font size (kept at the
55    // original 14/32 ratio) so smaller avatars (e.g. an 18px pill avatar) still read cleanly.
56    const size = parseInt(this.getAttribute('size'), 10) || 32;
57    const initialsFontSize = Math.round(size * (14 / 32));
58
59    //Component styling
60    const style = document.createElement('style');
61    style.textContent = `
62      .avatar-icon {
63        border-radius: 50%;
64        height: ${size}px;
65        width: ${size}px;
66        object-fit: cover;
67        display: inline-block;
68        vertical-align: middle;
69      }
70
71      .avatar-container {
72        display: inline-flex;
73        align-items: center;
74        justify-content: center;
75        border-radius: 50%;
76        background-color: white;
77        margin-right: 4px;
78        border: 1.75px solid #484848;
79        box-sizing: border-box;
80        height: ${size}px;
81        width: ${size}px;
82        overflow: hidden;
83        text-align: center;
84      }
85
86      .avatar-initials {
87        color: #222;
88        font-weight: 700;
89        user-select: none;
90        font-size: ${initialsFontSize}px;
91        line-height: 1;
92        text-transform: uppercase;
93      }
94    `;
95    this.shadowRoot.appendChild(style);
96
97    //Render avatar
98    const avatarElement = this.createAvatarIcon();
99    this.shadowRoot.appendChild(avatarElement);
100  }
101
102  createAvatarIcon() {
103    const relativePath = this.getAttribute('src');
104    const src = relativePath
105      ? (relativePath.startsWith('/') ? relativePath : `/users/pic/${relativePath}`)
106      : null;
107    const firstName = this.getAttribute('first-name') || '';
108    const lastName = this.getAttribute('last-name') || '';
109    const externalClasses = this.getAttribute('class') || '';
110    const initials = `${firstName.charAt(0)}${lastName.charAt(0)}`.toUpperCase();
111
112    //Function to create initials fallback
113    const createInitialsFallback = () => {
114      const container = document.createElement('div');
115      container.className = `avatar-container ${externalClasses}`.trim();
116
117      const initialsDiv = document.createElement('div');
118      initialsDiv.classList.add('avatar-initials');
119      initialsDiv.setAttribute('aria-label', `${firstName}-${lastName}`);
120      initialsDiv.textContent = initials || '';
121
122      container.appendChild(initialsDiv);
123      return container;
124    };
125
126    if(src) {
127      const avatarIcon = document.createElement('img');
128      avatarIcon.src = src;
129      avatarIcon.alt = `${firstName || 'User'}'s icon`;
130      avatarIcon.className = `avatar-icon ${externalClasses}`.trim();
131
132      //Fallback if image fails to load
133      avatarIcon.onerror = () => {
134        //Remove the broken image and replace it with initials
135        const fallback = createInitialsFallback();
136        if(this.shadowRoot.contains(avatarIcon)) {
137          this.shadowRoot.replaceChild(fallback, avatarIcon);
138        }
139      };
140
141      return avatarIcon;
142    } else {
143      return createInitialsFallback();
144    }
145  }
146}
147
148customElements.define('avatar-icon', AvatarIcon);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.