英文:
Type 'HTMLCollection | undefined' must have a '[Symbol.iterator]()' method that returns an iterator
问题
这是您提供的代码的中文翻译:
class DatePicker extends HTMLElement {
shadow: ShadowRoot;
calendar: Calendar;
mounted: boolean = false;
/** 元素 */
calendarDropdown: Element | null = null;
calendarDateElement: HTMLHeadingElement | null | undefined = null;
constructor() {
super();
// ...
}
connectedCallback() {
this.mounted = true;
this.toggleButton = this.shadow.querySelector(".date-toggle");
this.calendarDropdown = this.shadow.querySelector(".calendar-dropdown");
const [prevBtn, calendarDateElement, nextBtn] = this.calendarDropdown?.querySelector(".calendar-header")?.children; // <--- 这是引发错误的那一行
this.calendarDateElement = calendarDateElement;
this.toggleButton?.addEventListener("click", () => this.toggleCalendar());
prevBtn.addEventListener("click", () => this.prevMonth());
nextBtn.addEventListener("click", () => this.nextMonth());
}
}
请注意,我已经将代码中的HTML实体字符(如")替换为正常的引号和标记。
英文:
I don't know what I am missing out here. I am following a tutorial in building a custom date-picker using a web component here https://www.youtube.com/watch?v=g1Zd0Y7OJuI&t=723s and am translating the JavaScript logic to Typescript. I have reached a line inside connectedCallback() {...}
that throws the following error: Type 'HTMLCollection | undefined' must have a '[Symbol.iterator]()' method that returns an iterator
on the line const [prevBtn, calendarDateElement, nextBtn] = this.calendarDropdown?.querySelector(".calendar-header")?.children;
I have looked at similar questions here on Stackoverflow but found that their suggested solutions are not working for me. Kindly assist me in understanding what I need to take into account to eliminate this horrific error!
class DatePicker extends HTMLElement {
shadow: ShadowRoot;
calendar: Calendar;
mounted: boolean = false;
/** Elements */
calendarDropdown: Element | null = null;
calendarDateElement: HTMLHeadingElement | null | undefined = null;
constructor() {
super();
...
}
connectedCallback() {
this.mounted = true;
this.toggleButton = this.shadow.querySelector(".date-toggle");
this.calendarDropdown = this.shadow.querySelector(".calendar-dropdown");
const [prevBtn, calendarDateElement, nextBtn] = this.calendarDropdown?.querySelector(".calendar-header")?.children; // <--- This is the line complain
this.calendarDateElement = calendarDateElement;
this.toggleButton?.addEventListener("click", () => this.toggleCalendar());
prevBtn.addEventListener("click", () => this.prevMonth());
nextBtn.addEventListener("click", () => this.nextMonth());
}
}
答案1
得分: 0
I looked up this resource https://www.javascripttutorial.net/javascript-dom/javascript-get-child-element/ and found a workaround for my problem but I would like an even better solution to this issue. Instead of destructuring child nodes of the parent node like this const [prevBtn, calendarDateElement, nextBtn] = this.calendarDropdown?.querySelector(".calendar-header")?.children;
I found each node and assigned it to a variable separately as in the following code:
connectedCallback() {
...
this.calendarDropdown = this.shadow.querySelector(".calendar-dropdown");
const prevBtn = this.shadow.querySelector(".previous-month");
const calendarDateElement =
this.shadow.querySelector(".previous-month")?.nextElementSibling;
const nextBtn = this.shadow.querySelector(".next-month");
this.calendarDateElement = calendarDateElement;
...
prevBtn?.addEventListener("click", () => this.prevMonth());
nextBtn?.addEventListener("click", () => this.nextMonth());
}
Here below is the entire class:
import { Calendar, Day } from ".";
class DatePicker extends HTMLElement {
date: any = null;
format = "MMM DD, YYYY";
position: string | null = "bottom";
visible: boolean | undefined = false;
shadow: ShadowRoot;
calendar: Calendar;
mounted: boolean = false;
/** Elements */
toggleButton: HTMLButtonElement | null = null;
calendarDropdown: Element | null = null;
calendarDateElement: ChildNode | null | undefined = null;
constructor() {
super();
const lang = window.navigator.language;
const date = new Date(
this.date ?? (this.getAttribute("date") || Date.now())
);
this.shadow = this attachShadow({ mode: "open" });
this.date = new Day(date);
this.calendar = new Calendar(this.date.year, this.date.monthNumber, lang);
this.format = this.getAttribute("format") || this.format;
this.position = DatePicker.position.includes(
this.getAttribute("position") as string
)
? this.getAttribute("position")
: this.position;
this.visible =
this.getAttribute("visible") === "" ||
this.getAttribute("visible") === "true" ||
this.visible;
this.render();
}
connectedCallback() {
this.mounted = true;
this.toggleButton = this.shadow.querySelector(".date-toggle");
this.calendarDropdown = this.shadow.querySelector(".calendar-dropdown");
const prevBtn = this.shadow.querySelector(".previous-month");
const calendarDateElement =
this.shadow.querySelector(".previous-month")?.nextElementSibling;
const nextBtn = this.shadow.querySelector(".next-month");
this.calendarDateElement = calendarDateElement;
this.toggleButton?.addEventListener("click", () => this.toggleCalendar());
prevBtn?.addEventListener("click", () => this.prevMonth());
nextBtn?.addEventListener("click", () => this.nextMonth());
}
// = this.calendarDropdown.sec
//this.calendarDropdown?.querySelector(".calendar-header")?.children;
prevMonth() {
console.log("Prev Clicked");
this.calendar.goToPreviousMonth();
this.updateCalendarHeaderText();
}
nextMonth() {
console.log("Next Clicked");
this.calendar.goToNextMonth();
this.updateCalendarHeaderText();
}
updateCalendarHeaderText() {
if (this.calendarDateElement)
this.calendarDateElement.textContent = `${this.calendar.month.name}, ${this.calendar.year}`;
}
toggleCalendar(visible: boolean | null = null) {
visible === null
? this.calendarDropdown?.classList.toggle("visible")
: visible
? this.calendarDropdown?.classList.add("visible")
: this.calendarDropdown?.classList.remove("visible");
this.visible = this.calendarDropdown?.className.includes("visible");
}
static get position() {
return ["top", "right", "bottom", "left"];
}
get styles() {
return `
:host {
position: relative;
}
.date-toggle {
background: #eee;
border: none;
border-radius: 0.5em;
color: var(--teal);
cursor: pointer;
font-size: medium;
font-weight: lighter;
margin: 1em 0;
padding: 1.1em;
text-transform: capitalize;
width: 100%;
}
.calendar-dropdown {
background: #008080;
border-radius: 0.5em;
box-shadow: 0 0 8px rgba(0, 0, 0, 0.2);
color: var(--white);
display: block;
// height: 300px;
left: 50%;
min-width: 200px;
padding: 20px;
position: absolute;
top: 100%;
transform: translate(-52%, 15px);
width: 95%;
z-index: 3;
}
.calendar-dropdown.visible {
display: block;
}
.calendar-header {
display: flex;
justify-content: space between;
align-items: center;
margin: 10px 0 30px;
}
.calendar-header h4 {
margin: 0;
font-size: 21px;
font-weight: lighter;
text-transform: capitalize;
}
.calendar-header button {
background: none;
border: 8px solid transparent;
border-radius: 0.1em;
border-top-color: var(--white);
cursor: pointer;
height: 0;
padding: 0;
position: relative;
transform: rotate(90deg);
width: 0;
}
.calendar-header button::after {
content: '';
display: block;
height: 25px;
left: 50%;
position: absolute;
top: 50%;
transform: translate(-50%, -50%);
width: 25px;
}
.calendar-header button:last-of-type {
transform: rotate(-90deg);
}
`;
}
render() {
const monthYear = `${this.calendar.month.name}, ${this.calendar.year}`;
const date = this.date.format(this.format);
this.shadow.innerHTML = `
<style>${this.styles}</style>
<button type="button" class="date-toggle">${date}</button>
<div class="calendar-dropdown ${this.visible ? "visible" : ""}
${this.position}">
<div class="calendar-header">
<button aria-label="previous month" class="previous-month" type="button"></button>
<h4 class="month-year">
${monthYear}
</h4>
<button aria-label="next month" class="next-month" type="button"></button>
</div>
</div>
`;
}
}
export default DatePicker;
window.customElements.get("date-picker") ||
window.customElements.define("date-picker", DatePicker);
英文:
I looked up this resource https://www.javascripttutorial.net/javascript-dom/javascript-get-child-element/ and found a workaround for my problem but I would like an even better solution to this issue. Instead of destructuring child nodes of the parent node like this const [prevBtn, calendarDateElement, nextBtn] = this.calendarDropdown?.querySelector(".calendar-header")?.children;
I found each node and assigned it to a variable separately as in the following code:
connectedCallback() {
...
this.calendarDropdown = this.shadow.querySelector(".calendar-dropdown");
const prevBtn = this.shadow.querySelector(".previous-month");
const calendarDateElement =
this.shadow.querySelector(".previous-month")?.nextElementSibling;
const nextBtn = this.shadow.querySelector(".next-month");
this.calendarDateElement = calendarDateElement;
...
prevBtn?.addEventListener("click", () => this.prevMonth());
nextBtn?.addEventListener("click", () => this.nextMonth());
}
Here below is the entire class:
import { Calendar, Day } from ".";
class DatePicker extends HTMLElement {
date: any = null;
format = "MMM DD, YYYY";
position: string | null = "bottom";
visible: boolean | undefined = false;
shadow: ShadowRoot;
calendar: Calendar;
mounted: boolean = false;
/** Elements */
toggleButton: HTMLButtonElement | null = null;
calendarDropdown: Element | null = null;
calendarDateElement: ChildNode | null | undefined = null;
constructor() {
super();
const lang = window.navigator.language;
const date = new Date(
this.date ?? (this.getAttribute("date") || Date.now())
);
this.shadow = this.attachShadow({ mode: "open" });
this.date = new Day(date);
this.calendar = new Calendar(this.date.year, this.date.monthNumber, lang);
this.format = this.getAttribute("format") || this.format;
this.position = DatePicker.position.includes(
this.getAttribute("position") as string
)
? this.getAttribute("position")
: this.position;
this.visible =
this.getAttribute("visible") === "" ||
this.getAttribute("visible") === "true" ||
this.visible;
this.render();
}
connectedCallback() {
this.mounted = true;
this.toggleButton = this.shadow.querySelector(".date-toggle");
this.calendarDropdown = this.shadow.querySelector(".calendar-dropdown");
const prevBtn = this.shadow.querySelector(".previous-month");
const calendarDateElement =
this.shadow.querySelector(".previous-month")?.nextElementSibling;
const nextBtn = this.shadow.querySelector(".next-month");
this.calendarDateElement = calendarDateElement;
this.toggleButton?.addEventListener("click", () => this.toggleCalendar());
prevBtn?.addEventListener("click", () => this.prevMonth());
nextBtn?.addEventListener("click", () => this.nextMonth());
}
// = this.calendarDropdown.sec
//this.calendarDropdown?.querySelector(".calendar-header")?.children;
prevMonth() {
console.log("Prev Clicked");
this.calendar.goToPreviousMonth();
this.updateCalendarHeaderText();
}
nextMonth() {
console.log("Next Clicked");
this.calendar.goToNextMonth();
this.updateCalendarHeaderText();
}
updateCalendarHeaderText() {
if (this.calendarDateElement)
this.calendarDateElement.textContent = `${this.calendar.month.name}, ${this.calendar.year}`;
}
toggleCalendar(visible: boolean | null = null) {
visible === null
? this.calendarDropdown?.classList.toggle("visible")
: visible
? this.calendarDropdown?.classList.add("visible")
: this.calendarDropdown?.classList.remove("visible");
this.visible = this.calendarDropdown?.className.includes("visible");
}
static get position() {
return ["top", "right", "bottom", "left"];
}
get styles() {
return `
:host {
position: relative;
}
.date-toggle {
background: #eee;
border: none;
border-radius: 0.5em;
color: var(--teal);
cursor: pointer;
font-size: medium;
font-weight: lighter;
margin: 1em 0;
padding: 1.1em;
text-transform: capitalize;
width: 100%;
}
.calendar-dropdown {
background: #008080;
border-radius: 0.5em;
box-shadow: 0 0 8px rgba(0, 0, 0, 0.2);
color: var(--white);
display: block;
// height: 300px;
left: 50%;
min-width: 200px;
padding: 20px;
position: absolute;
top: 100%;
transform: translate(-52%, 15px);
width: 95%;
z-index: 3;
}
.calendar-dropdown.visible {
display: block;
}
.calendar-header {
display: flex;
justify-content: space-between;
align-items: center;
margin: 10px 0 30px;
}
.calendar-header h4 {
margin: 0;
font-size: 21px;
font-weight: lighter;
text-transform: capitalize;
}
.calendar-header button {
background: none;
border: 8px solid transparent;
border-radius: 0.1em;
border-top-color: var(--white);
cursor: pointer;
height: 0;
padding: 0;
position: relative;
transform: rotate(90deg);
width: 0;
}
.calendar-header button::after {
content: '';
display: block;
height: 25px;
left: 50%;
position: absolute;
top: 50%;
transform: translate(-50%, -50%);
width: 25px;
}
.calendar-header button:last-of-type {
transform: rotate(-90deg);
}
`;
}
render() {
const monthYear = `${this.calendar.month.name}, ${this.calendar.year}`;
const date = this.date.format(this.format);
this.shadow.innerHTML = `
<style>${this.styles}</style>
<button type="button" class="date-toggle">${date}</button>
<div class="calendar-dropdown ${this.visible ? "visible" : ""}
${this.position}">
<div class="calendar-header">
<button aria-label="previous month" class="previous-month" type="button"></button>
<h4 class="month-year">
${monthYear}
</h4>
<button aria-label="next month" class="next-month" type="button"></button>
</div>
</div>
`;
}
}
export default DatePicker;
window.customElements.get("date-picker") ||
window.customElements.define("date-picker", DatePicker);
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论