לבנות סלקט ידנית זה הבסיס לרכיבים כמו בחירה מרובה, השלמה מתוך אופציות ועוד הרבה סוגים של בחירות.
לכן, אם נדע איך לבנות אותו, נוכל בשינויים נוספים לבנות את כל השאר.
אם כבר אנחנו בונים סלקט אז שיהיה גם נגיש (כי יש לזה חשיבות גדולה עבור המשתמשים שלנו).
אני אבנה אותו ב-javascript נקי לחלוטין כך שיהיה כל להטמיע אותו בכל מקום.
<div class="select-wrapper" id="random_id">
<button class="select-btn" onkeydown="accessibilitySelect.onEsc(event)"
onkeyup="accessibilitySelect.onKeyUp(event)" onclick="accessibilitySelect.onClick(event)"
aria-controls="random_list_id" aria-haspopup="listbox" role="combobox" aria-expanded="false">בחר</button>
<div class="select-list-wrapper" id="random_list_id" role="listbox">
<ul class="select-list">
<li class="select-item" tabindex="-1" role="option" onmouseenter="accessibilitySelect.itemHover(event)"
data-value="1" aria-selected="false" onclick="accessibilitySelect.optionClick(event)"><span
class="select-option">1</span></li>
<li class="select-item" tabindex="-1" role="option" onmouseenter="accessibilitySelect.itemHover(event)"
data-value="2" aria-selected="false" onclick="accessibilitySelect.optionClick(event)"><span
class="select-option">2</span></li>
<li class="select-item" tabindex="-1" role="option" onmouseenter="accessibilitySelect.itemHover(event)"
data-value="3" aria-selected="false" onclick="accessibilitySelect.optionClick(event)"><span
class="select-option">3</span></li>
<li class="select-item" tabindex="-1" role="option" onmouseenter="accessibilitySelect.itemHover(event)"
data-value="4" aria-selected="false" onclick="accessibilitySelect.optionClick(event)"><span
class="select-option">4</span></li>
<li class="select-item" tabindex="-1" role="option" onmouseenter="accessibilitySelect.itemHover(event)"
data-value="5" aria-selected="false" onclick="accessibilitySelect.optionClick(event)"><span
class="select-option">5</span></li>
<li class="select-item" tabindex="-1" role="option" onmouseenter="accessibilitySelect.itemHover(event)"
data-value="1a" aria-selected="false" onclick="accessibilitySelect.optionClick(event)"><span
class="select-option">1a</span></li>
<li class="select-item" tabindex="-1" role="option" onmouseenter="accessibilitySelect.itemHover(event)"
data-value="2a" aria-selected="false" onclick="accessibilitySelect.optionClick(event)"><span
class="select-option">2a</span></li>
<li class="select-item" tabindex="-1" role="option" onmouseenter="accessibilitySelect.itemHover(event)"
data-value="3a" aria-selected="false" onclick="accessibilitySelect.optionClick(event)"><span
class="select-option">3a</span></li>
<li class="select-item" tabindex="-1" role="option" onmouseenter="accessibilitySelect.itemHover(event)"
data-value="4a" aria-selected="false" onclick="accessibilitySelect.optionClick(event)"><span
class="select-option">4a</span></li>
<li class="select-item" tabindex="-1" role="option" onmouseenter="accessibilitySelect.itemHover(event)"
data-value="5a" aria-selected="false" onclick="accessibilitySelect.optionClick(event)"><span
class="select-option">5a</span></li>
</ul>
</div>
</div>זה מבנה הסלקט שלנו , כעט ניתן לו מראה יותר דומה לסלקט
.select-wrapper {
display: inline-block;
position: relative;
}
.select-btn {
min-width: 100px;
padding: 4px 10px;
border: 1px solid #ededed;
border-radius: 5px;
background-color: transparent;
text-align: right;
}
.select-btn::after {
content: '\2B9F';
position: absolute;
left: 4px;
top: 2px;
}
.select-wrapper.active .select-btn::after {
transform: rotate(180deg);
}
.select-list-wrapper {
width: 100%;
position: absolute;
left: 0;
top: 100%;
box-shadow: 1px 1px 4px #ededed;
display: none;
}
.select-wrapper.active .select-list-wrapper {
display: block;
}
.select-list {
list-style-type: none;
text-align: right;
padding: 4px;
margin: 0;
max-height: 100px;
overflow: auto;
}
.select-item {
padding: 2px 0;
}
.select-item[aria-selected="true"] {
background-color: lightblue;
}אין כאן משהו יותר מידי מיוחד, מדובר בעיצוב בסיסי רק כדי שהרכיב שלנו יראה כמו סלקט.
עכשיו זה הזמן לגרום לו גם לתפקד כמו סלקט אמיתי
const accessibilitySelect = new Object();
accessibilitySelect.selectWrapper = document.getElementById('random_id');
accessibilitySelect.selectMainBtn = accessibilitySelect.selectWrapper.getElementsByClassName('select-btn')[0];
accessibilitySelect.selectedItemValue = null;
accessibilitySelect.temporerSelectedItemValue = null;
accessibilitySelect.selectItems = Array.from(accessibilitySelect.selectWrapper.getElementsByClassName('select-item'));
accessibilitySelect.onKeyUp = (event) => {
if (event.keyCode === 13 || event.which === 13 ||
event.keyCode === 32 || event.which === 32) {
accessibilitySelect.selectWrapper.classList.toggle('active');
accessibilitySelect.selectMainBtn.setAttribute('aria-expanded',
accessibilitySelect.boolStringToggler(accessibilitySelect.selectMainBtn.getAttribute('aria-expanded')));
accessibilitySelect.onToggleOptionsList();
} // 13 enter and 32 space
accessibilitySelect.keyboardMove(event);
}
accessibilitySelect.onEsc = (event) => {
if (event.keyCode === 27 || event.which === 27) {
accessibilitySelect.selectWrapper.classList.remove('active');
accessibilitySelect.selectMainBtn.setAttribute('aria-expanded', 'false');
accessibilitySelect.temporerSelectedItemValue = null;
}
}
accessibilitySelect.onClick = (event) => {
if (event.type === 'click' && event.pointerType === 'mouse') {
accessibilitySelect.selectWrapper.classList.toggle('active');
accessibilitySelect.selectMainBtn.setAttribute('aria-expanded',
accessibilitySelect.boolStringToggler(accessibilitySelect.selectMainBtn.getAttribute('aria-expanded')));
accessibilitySelect.onToggleOptionsList();
}
}
accessibilitySelect.optionClick = (event) => {
accessibilitySelect.temporerSelectedItemValue = event.target.dataset.value;
accessibilitySelect.selectWrapper.classList.toggle('active');
accessibilitySelect.selectMainBtn.setAttribute('aria-expanded',
accessibilitySelect.boolStringToggler(accessibilitySelect.selectMainBtn.getAttribute('aria-expanded')));
accessibilitySelect.onToggleOptionsList();
}
accessibilitySelect.itemHover = (event) => {
accessibilitySelect.temporerSelectedItemValue = event.target.dataset.value;
accessibilitySelect.syncTempSelectedItem();
}
accessibilitySelect.syncTempSelectedItem = () => {
accessibilitySelect.selectItems.forEach(item => {
item.setAttribute('aria-selected', item.dataset.value === accessibilitySelect.temporerSelectedItemValue);
});
}
accessibilitySelect.keyboardMove = (event) => {
const itemIdx = accessibilitySelect.selectItems.findIndex(item => item.getAttribute('aria-selected') === 'true');
if (event.keyCode === 38 || event.which === 38) {
if (itemIdx === -1 || itemIdx === 0) {
accessibilitySelect.temporerSelectedItemValue = accessibilitySelect.selectItems[0].dataset.value;
accessibilitySelect.selectItems[0].scrollIntoView();
} else {
accessibilitySelect.temporerSelectedItemValue = accessibilitySelect.selectItems[itemIdx - 1].dataset.value;
accessibilitySelect.selectItems[itemIdx - 1].scrollIntoView();
}
accessibilitySelect.syncTempSelectedItem();
} // UP
if (event.keyCode === 40 || event.which === 40) {
if (itemIdx === -1) {
accessibilitySelect.temporerSelectedItemValue = accessibilitySelect.selectItems[0].dataset.value;
accessibilitySelect.selectItems[0].scrollIntoView();
} else {
if (itemIdx === (accessibilitySelect.selectItems.length - 1)) {
accessibilitySelect.temporerSelectedItemValue = accessibilitySelect.selectItems[itemIdx].dataset.value;
accessibilitySelect.selectItems[itemIdx].scrollIntoView();
} else {
accessibilitySelect.temporerSelectedItemValue = accessibilitySelect.selectItems[itemIdx + 1].dataset.value;
accessibilitySelect.selectItems[itemIdx + 1].scrollIntoView();
}
}
accessibilitySelect.syncTempSelectedItem();
} // DOWN
}
accessibilitySelect.updateSelectedItem = () => {
if (accessibilitySelect.temporerSelectedItemValue !== null) {
accessibilitySelect.selectedItemValue = accessibilitySelect.temporerSelectedItemValue;
accessibilitySelect.selectWrapper.getElementsByClassName('select-btn')[0].innerText = accessibilitySelect.selectItems.find(item =>
item.dataset.value === accessibilitySelect.selectedItemValue).children[0].innerText;
}
}
accessibilitySelect.onToggleOptionsList = () => {
if (accessibilitySelect.selectWrapper.classList.contains('active')) {
if (accessibilitySelect.selectedItemValue !== null) {
accessibilitySelect.temporerSelectedItemValue = accessibilitySelect.selectedItemValue;
accessibilitySelect.syncTempSelectedItem();
}
} else {
accessibilitySelect.updateSelectedItem();
}
}
accessibilitySelect.boolStringToggler = (checker) => {
if (checker === "true") {
return "false";
}
return "true";
}אם צורת הכתיבה הזו לא ברורה, אז אני אסביר מה היתרון שלה.
בשורה 1 : יצרנו אוביקט, הוא יחזיק את הכל, יהיו בו כל הפונקציות וכל המשתנים.
זה אומר, שכל עוד אף אחד לא ידרוס את האוביקט עם אוביקט באותו השם אין סכנה לדליפות.
לא מסקריפטים אחרים ולא מהסקריפט שלנו לפונקציות אחרות.
אנחנו תמיד נפנה לאוביקט ומשם לפונקציה או למשתנה שאנחנו רוצים. כך אנחנו יכולים להקים משתנים גלובלים לסקריפט שלנו מבלי שהם יהיו גלובלים לכל האתר.
אם ירצו לגשת אליהם מבחוץ, זה אכן אפשרי, אבל רק דרך האוביקט.
בשאר השורות ניתן לראות פונקציות שמטפלות בלחיצות מיקלדת כמו esc או רווח ו-enter , כמו כן יש גם טיפול בחצים למעלה ולמטה.
שימו לב, שהפוקוס אף פעם לא עוזב את הכפתור פתיחה/סגירה, אנחנו רק מסמלצים פוקוס ובחירה על אחד האופציות של רכיב הבחירה.
ניתן לראות איך הפונקציות קוראות אחת לשניה בתוך הסקריפט, אחד הדברים החשובים כאן הוא להשתדל ליצור פונקציות אשר כל אחת אחראית על פעולה מסויימת ואם צריך, היא תקרא לפונקציה אחרת שעושה פעולה נוספת.
כך השליטה שלנו עולה בקוד ובמידה שצריך לעשות שינוי , יהיה יותר קל.
הרכיב נבדק על מחשב עם ובלי קורא מסך, במקביל הוא נבדק בנייד עם קורא מסך והכל עובד ומקריא תקין.
אפשר להוסיף למי שרוצה, פונקציונליות שכאשר לוחצים בחוץ אז הסלקט ייסגר.





