1
\$\begingroup\$

Below is code for a front end calendar event element I have just finished. Though I have been programming in JavaScript for a while, this is the first time I have created a custom class and element, so I am interested in whether or not I have understood how to do this correctly?

  • Do I need the top if statement with connectedCallback? I worry that the calendar information elements could otherwise be added twice.

  • I am using ics.js with this. I got a basic test to work, but I haven't tested thoroughly (I plan to though.) If you have previously used ics.js (https://github.com/nwcell/ics.js/) I would appreciate your review / comments using ics.js too.

  • In the code towards the bottom you will see code for an extObject / callback listener. This is for front end editing functionality that has a "normal" (read) mode and an edit mode. These "x-events" will be serialized and saved / reloaded in HTML documents (which is why I am specifically defining the value fields.) I am hoping that CalEvents that are saved will have connectedCallback called on them at that time so that the listeners will be reattached. Will that happen?

  • Any other comments that could make this better?

     class CalEvent extends HTMLElement {
     constructor () {
     super();
     console.log("CalEvent constructor");
     }
     disconnectedCallback() {
     calTableInputFunc(this, false);
     }
     connectedCallback () {
     if (this.childNodes.length == 0) {
     let form = document.createElement("FORM");
     this.appendChild(form);
     let h = document.createElement("H1");
     h.textContent = "Event";
     form.appendChild(h);
     let evtTable = document.createElement("TABLE");
     evtTable.setAttribute("contentEditable", "false");
     form.appendChild(evtTable);
     let createRow = function(name, input) {
     let row = evtTable.insertRow();
     let cell = row.insertCell();
     cell.textContent = name;
     cell = row.insertCell();
     if (!input) input = document.createElement("INPUT");
     input.name = name.replace(/\s/,"").toLowerCase();
     input.style.width = "100%";
     // input.style.boxSizing = "border-box";
     // if (element.hasAttribute(input.name)) {
     // input.value = element.getAttribute(input.name);
     // }
     cell.appendChild(input);
     return row; 
     }
     createRow("Subject");
     let ta = document.createElement("TEXTAREA");
     ta.rows=4;
     createRow("Description", ta);
     createRow("Location");
     row = createRow("Start");
     let input = row.cells[1].querySelector("input");
     console.log("input.name: " + input.name);
     input.type = "datetime-local";
     row = createRow("End");
     input = row.cells[1].querySelector("input");
     input.type = "datetime-local";
     row = createRow("Repeat");
     input = row.cells[1].querySelector("input");
     input.type = "radio";
     input.name = "freq";
     input.value = "none";
     input.checked = true;
     input.removeAttribute("style");
     row.cells[1].appendChild(document.createTextNode("None"));
     let input2 = document.createElement("input");
     input2.name = "freq";
     input2.value = "DAILY";
     input2.type = "radio";
     row.cells[1].appendChild(input2);
     row.cells[1].appendChild(document.createTextNode("Day"));
     input2 = document.createElement("input");
     input2.name = "freq";
     input2.value = "WEEKLY";
     input2.type = "radio";
     row.cells[1].appendChild(input2);
     row.cells[1].appendChild(document.createTextNode("Week"));
     // Month is disabled as it is not supported in ics.js. Also, there are two flavors of month in ics
     // And how dates are added would be a design issue
     // row.cells[1].appendChild(document.createTextNode("Month"));
     // input2 = document.createElement("input");
     // input2.name = "freq";
     // input2.value = "MONTHLY";
     // input2.type = "radio";
     // row.cells[1].appendChild(input2);
     input2 = document.createElement("input");
     input2.name = "freq";
     input2.value = "YEARLY";
     input2.type = "radio";
     row.cells[1].appendChild(input2);
     row.cells[1].appendChild(document.createTextNode("Year"));
     let link = document.createElement("A");
     link.href = "https://icalendar.org/rrule-tool.html";
     link.target = "rrule";
     link.appendChild(document.createTextNode("Custom"));
     input2 = document.createElement("input");
     input2.name = "freq";
     input2.value = "custom";
     input2.type = "radio";
     row.cells[1].appendChild(input2);
     row.cells[1].appendChild(link);
     row = createRow("Custom");
     row.style.display = "none";
     row.className = "custom";
     row = createRow("Weekly Days");
     row.cells[0].title = "Days of the week for weekly reoccurance";
     input = row.cells[1].querySelector("input");
     input.removeAttribute("style");
     let cell = row.cells[1];
     input.type = "checkbox";
     input.name = "byday";
     input.value = "SU";
     cell.appendChild(document.createTextNode("Su"))
     input2 = document.createElement("INPUT");
     input2.type = "checkbox";
     input2.name = "byday";
     input2.value = "MO";
     cell.appendChild(input2);
     cell.appendChild(document.createTextNode("M"));
     input2 = document.createElement("INPUT");
     input2.type = "checkbox";
     input2.name = "byday";
     input2.value = "TU";
     cell.appendChild(input2);
     cell.appendChild(document.createTextNode("Tu"));
     input2 = document.createElement("INPUT");
     input2.type = "checkbox";
     input2.name = "byday";
     input2.value = "WE";
     cell.appendChild(input2);
     cell.appendChild(document.createTextNode("We"));
     input2 = document.createElement("INPUT");
     input2.type = "checkbox";
     input2.name = "byday";
     input2.value = "TH";
     cell.appendChild(input2);
     cell.appendChild(document.createTextNode("Th"));
     input2 = document.createElement("INPUT");
     input2.type = "checkbox";
     input2.name = "byday";
     input2.value = "FR";
     cell.appendChild(input2);
     cell.appendChild(document.createTextNode("Fr"));
     input2 = document.createElement("INPUT");
     input2.type = "checkbox";
     input2.name = "byday";
     input2.value = "SA";
     cell.appendChild(input2);
     cell.appendChild(document.createTextNode("Sa"));
     row.querySelectorAll("select, input, textarea").forEach ((input) => {
     input.disabled = true;
     });
     row = createRow("Interval");
     row.className = "unsupported-dontlist";
     row.cells[0].title = "1 = every week, 2 = every 2 weeks";
     input = row.cells[1].querySelector("input");
     input.type = "number";
     input.name = "interval";
     input.min = 1;
     input.max = 12;
     input.value = 1;
     row.querySelectorAll("select, input, textarea").forEach ((input) => {
     input.disabled = true;
     });
     row = createRow("Until");
     row.cells[0].title = "Reoccur until this date";
     input = row.cells[1].querySelector("input"); 
     input.type = "date";
     input.name = "until";
     calTableInputFunc(this, true);
     row.querySelectorAll("select, input, textarea").forEach ((input) => {
     input.disabled = true;
     }); 
     } 
    }
    $(() => {
     customElements.define("x-event", CalEvent);
     var downloadEvent = function(e) {
     let target = e.currentTarget;
     if (target.tagName != 'X-EVENT') {
     throw "downloadEvent is for use on x-events only";
     }
     if (typeof ics == "undefined") {
     throw "Required ICS Library is Missing";
     }
     let form = target.querySelector("form");
     let fd = new FormData(form);
     if (!fd.get("start") || !fd.get("end") || !fd.get("subject")) {
     error.reportError("Cannot add event with start, end or title not filled in");
     return;
     }
     let recur = fd.get("freq") != "none" && fd.get("recur") != null;
     let rRule = null;
     if (recur) {
     rRule = {};
     if (fd.get("freq") == "custom") {
     rRule.rule = fd.get("rule");
     } else {
     rRule.FREQ = fd.get("freq");
     rRule.UNTIL = fd.get("until");
     if (fd.get("freq") == "WEEKLY") {
     rRule.BYDAY = fd.get("byday");
     }
     }
     }
     let cal = ics();
     cal.addEvent(fd.get("subject"),fd.get("description"), fd.get("location"), fd.get("start"), fd.get("end"), Rrule);
     console.log(cal.download(fd.get("subject")));
     alert("Reminder: Use 'Import' from your calendar app to properly load file");
     }
     let extListener = {};
     extListener.initialize = () => {
     console.log("calevent: initialize");
     if (calTableInputFunc) {
     document.querySelectorAll("x-event").forEach((xEvent) => {
     calTableInputFunc(xEvent, true);
     });
     }
     }
     extListener.enterEditMode = () => {
     document.body.querySelectorAll("x-event").forEach((xEvent)=> {
     xEvent.removeEventListener("click", xEvent.dlEvent);
     xEvent.querySelectorAll("input,select,textarea").forEach((input) => {
     input.readOnly = false;
     });
     });
     }
     extListener.enterRegularMode = () => {
     document.body.querySelectorAll("x-event").forEach((xEvent)=> {
     xEvent.querySelectorAll("input, textarea, select").forEach((elem) => {
     if (elem.value) elem.setAttribute("value",elem.value); else elem.removeAttribute("value");
     });
     xEvent.dlEvent = downloadEvent;
     xEvent.addEventListener("click", xEvent.dlEvent);
     });
     // console.log("disabling event controls");
     document.body.querySelectorAll("x-event").forEach((xEvent)=> {
     console.log("found xEvent");
     xEvent.querySelectorAll("select, input, textarea").forEach((input) => {
     input.readOnly = true;
     });
     });
     }
     registerExt(extListener); 
     });
     var calTableInputFunc = (xEvent, enable) => {
     let changeFunc = (ev) => {
     console.log("cal table input func");
     ev.target.closest("x-event").setAttribute(ev.target.name, ev.target.value);
     let evtTable = xEvent.querySelector("table");
     let startRow = 7;
     let customRow = ev.target.parentNode.parentNode.parentNode.querySelector(".custom");
     let test = (ev.target.getAttribute("name") == "freq" && ev.target.value == "custom");
     customRow.style.display = test ? "table-row" : "none";
     customRow.querySelector("input").disabled = !test;
     if (ev.target.getAttribute("name") == "freq") {
     evtTable.rows[startRow].querySelectorAll("select, input, textarea").forEach ((input) => {
     input.disabled = ev.target.value != "WEEKLY";
     });
     evtTable.rows[startRow + 1].querySelectorAll("select, input, textarea").forEach ((input) => {
     input.disabled = (ev.target.value == "none" || ev.target.value == "custom");
     });
     evtTable.rows[startRow + 2].querySelectorAll("select, input, textarea").forEach ((input) => {
     input.disabled = (ev.target.value == "none" || ev.target.value == "custom");
     });
     }
     }
     if (enable) {
     // xEvent.querySelector("table").querySelectorAll("INPUT, TEXTAREA").forEach((elem) => elem.addEventListener("change", changeFunc));
     xEvent.querySelectorAll("[name=freq]").forEach((elem) => elem.addEventListener("change", changeFunc));
     } else {
     xEvent.querySelectorAll("[name=freq]").querySelectorAll("INPUT, TEXTAREA").forEach((elem) => elem.removeEventListener("change", changeFunc));
     }
     }
    
asked Oct 14 at 4:27
\$\endgroup\$

0

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.