Type 'HTMLCollection | undefined' must have a '[Symbol.iterator]()' method that returns an iterator

huangapple go评论79阅读模式
英文:

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 &#39;HTMLCollection | undefined&#39; must have a &#39;[Symbol.iterator]()&#39; method that returns an iterator on the line const [prevBtn, calendarDateElement, nextBtn] = this.calendarDropdown?.querySelector(&quot;.calendar-header&quot;)?.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(&quot;.date-toggle&quot;);
    this.calendarDropdown = this.shadow.querySelector(&quot;.calendar-dropdown&quot;);
    const [prevBtn, calendarDateElement, nextBtn] = this.calendarDropdown?.querySelector(&quot;.calendar-header&quot;)?.children; // &lt;--- This is the line complain 
    this.calendarDateElement = calendarDateElement;

    this.toggleButton?.addEventListener(&quot;click&quot;, () =&gt; this.toggleCalendar());
    prevBtn.addEventListener(&quot;click&quot;, () =&gt; this.prevMonth());
    nextBtn.addEventListener(&quot;click&quot;, () =&gt; 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(&quot;.calendar-header&quot;)?.children; I found each node and assigned it to a variable separately as in the following code:

connectedCallback() {
...
this.calendarDropdown = this.shadow.querySelector(&quot;.calendar-dropdown&quot;);
const prevBtn = this.shadow.querySelector(&quot;.previous-month&quot;);
const calendarDateElement =
this.shadow.querySelector(&quot;.previous-month&quot;)?.nextElementSibling;
const nextBtn = this.shadow.querySelector(&quot;.next-month&quot;);
this.calendarDateElement = calendarDateElement;
...
prevBtn?.addEventListener(&quot;click&quot;, () =&gt; this.prevMonth());
nextBtn?.addEventListener(&quot;click&quot;, () =&gt; this.nextMonth());
}

Here below is the entire class:

import { Calendar, Day } from &quot;.&quot;;
class DatePicker extends HTMLElement {
date: any = null;
format = &quot;MMM DD, YYYY&quot;;
position: string | null = &quot;bottom&quot;;
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(&quot;date&quot;) || Date.now())
);
this.shadow = this.attachShadow({ mode: &quot;open&quot; });
this.date = new Day(date);
this.calendar = new Calendar(this.date.year, this.date.monthNumber, lang);
this.format = this.getAttribute(&quot;format&quot;) || this.format;
this.position = DatePicker.position.includes(
this.getAttribute(&quot;position&quot;) as string
)
? this.getAttribute(&quot;position&quot;)
: this.position;
this.visible =
this.getAttribute(&quot;visible&quot;) === &quot;&quot; ||
this.getAttribute(&quot;visible&quot;) === &quot;true&quot; ||
this.visible;
this.render();
}
connectedCallback() {
this.mounted = true;
this.toggleButton = this.shadow.querySelector(&quot;.date-toggle&quot;);
this.calendarDropdown = this.shadow.querySelector(&quot;.calendar-dropdown&quot;);
const prevBtn = this.shadow.querySelector(&quot;.previous-month&quot;);
const calendarDateElement =
this.shadow.querySelector(&quot;.previous-month&quot;)?.nextElementSibling;
const nextBtn = this.shadow.querySelector(&quot;.next-month&quot;);
this.calendarDateElement = calendarDateElement;
this.toggleButton?.addEventListener(&quot;click&quot;, () =&gt; this.toggleCalendar());
prevBtn?.addEventListener(&quot;click&quot;, () =&gt; this.prevMonth());
nextBtn?.addEventListener(&quot;click&quot;, () =&gt; this.nextMonth());
}
// = this.calendarDropdown.sec
//this.calendarDropdown?.querySelector(&quot;.calendar-header&quot;)?.children;
prevMonth() {
console.log(&quot;Prev Clicked&quot;);
this.calendar.goToPreviousMonth();
this.updateCalendarHeaderText();
}
nextMonth() {
console.log(&quot;Next Clicked&quot;);
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(&quot;visible&quot;)
: visible
? this.calendarDropdown?.classList.add(&quot;visible&quot;)
: this.calendarDropdown?.classList.remove(&quot;visible&quot;);
this.visible = this.calendarDropdown?.className.includes(&quot;visible&quot;);
}
static get position() {
return [&quot;top&quot;, &quot;right&quot;, &quot;bottom&quot;, &quot;left&quot;];
}
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: &#39;&#39;;
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 = `
&lt;style&gt;${this.styles}&lt;/style&gt;
&lt;button type=&quot;button&quot; class=&quot;date-toggle&quot;&gt;${date}&lt;/button&gt;
&lt;div class=&quot;calendar-dropdown ${this.visible ? &quot;visible&quot; : &quot;&quot;} 
${this.position}&quot;&gt;
&lt;div class=&quot;calendar-header&quot;&gt;
&lt;button aria-label=&quot;previous month&quot; class=&quot;previous-month&quot; type=&quot;button&quot;&gt;&lt;/button&gt;
&lt;h4 class=&quot;month-year&quot;&gt;
${monthYear}
&lt;/h4&gt;
&lt;button aria-label=&quot;next month&quot; class=&quot;next-month&quot; type=&quot;button&quot;&gt;&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
`;
}
}
export default DatePicker;
window.customElements.get(&quot;date-picker&quot;) ||
window.customElements.define(&quot;date-picker&quot;, DatePicker);

huangapple
  • 本文由 发表于 2023年2月27日 00:15:02
  • 转载请务必保留本文链接:https://go.coder-hub.com/75573308.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定