UNPKG

ui-dropdown

Version:
213 lines (173 loc) 5.98 kB
"use strict" import view from "./view.js"; class DropdownViewController extends HTMLElement { static get observedAttributes(){ return ["value"]; } constructor(model){ super(); this.state = {}; this.state.connected = false; this.model = model || {}; this.event = {}; this.view = {}; this.shadowRoot = this.attachShadow({mode: "open"}); this.shadowRoot.appendChild(view.content.cloneNode(true)); } //Fires when the dropdown is inserted into the DOM. It's a good place to set //the initial role, tabindex, internal state, and install event listeners. // //NOTE: A user may set a property on an instance of an dropdown, before its //prototype has been connected to this class. The _upgradeProperty() method //will check for any instance properties and run them through the proper //class setters. connectedCallback() { //Set ARIA role, if necesary if(!this.hasAttribute("role")){ this.setAttribute("role", "select"); } //Wire views here this.view.container = this.shadowRoot.querySelector("#container"); this.view.label = this.shadowRoot.querySelector("#label"); this.view.errorMessage = this.shadowRoot.querySelector("#errorMessage"); this.view.select = this.shadowRoot.querySelector("select"); this.view.options = this.shadowRoot.querySelectorAll("option"); //Reference events with bindings this.event.click = this._onClick.bind(this); this.event.change = this._onChange.bind(this); this.view.container.addEventListener("click", this.event.click); this.view.select.addEventListener("change", this.event.change) this.state.connected = true; this.state.startTime = performance.now(); this._updateView(); } attributeChangedCallback(attrName, oldVal, newVal) { switch(attrName){ case "value": if(newVal !== this.value){ this.value = newVal; } break; default: throw new Error(`Attribute ${attrName} is not handled, you should probably do that`); } } get shadowRoot(){return this._shadowRoot;} set shadowRoot(value){ this._shadowRoot = value} get hasInvalidResponse(){ return !this.hasValidResponse; } get hasValidResponse(){ if(this.model.required){ return this.data[0].response !== null; } return true; } get data(){ this.datum = {}; this.datum.rt = performance.now() - this.state.startTime; this.datum.name = this.model.name; this.datum.question = this.model.label; if(this.view && this.view.select && this.view.select.value && this.view.select.value !== this.model.placeholder){ this.datum.response = this.view.select.value; } else{ this.datum.response = null; } return [this.datum]; } //UPDATE ELEMENT TEMPALTE get model(){ return this._model; } set model(value){ value.type = "ui-dropdown"; this._model = value; } //UPDATE ELEMENT TEMPALTE get value(){ return this.model; } set value(value){ //Check if attribute matches property value, Sync the property with the //attribute if they do not, skip this step if already sync if(this.getAttribute("value") !== this._toString(value)){ //By setting the attribute, the attributeChangedCallback() function is //called, which inturn calls this setter again. this.setAttribute("value", this._toString(value)); //attributeChangeCallback() implicitly called return; } //Ensure it is saved as an objet, and not a string this.model = this._toJSON(value); this._updateView(); } _toString(value){ switch(value.constructor){ case Object: value = JSON.stringify(value); break; } return value; } _toJSON(value){ switch(value.constructor){ case String: value = JSON.parse(value); break; } return value; } _onChange(){ this.dispatchEvent(new CustomEvent("response", {detail: this.data})); } _onClick(){ this.dispatchEvent(new CustomEvent("update", {detail: this.data})); } _updateView(view) { //No point in rendering if there isn't a model source, or a view on screen if(!this.model || !this.state.connected){ return; } switch(view){ case this.view.label: this._updateLabelView(); break; case this.view.select: this._updateSelectView(); break; default: this._updateLabelView(); this._updateSelectView(); } } _updateLabelView(){ this.view.label.innerHTML = this.model.label; } _updateSelectView(){ this.view.select.innerHTML = ""; this.view.options = null; this.model.placeholder = this.model.placeholder || "--- Select One ---"; let optionElement = this._newOptionElement(null, this.model.placeholder); this.view.select.appendChild(optionElement); if(this.model.options){ this.model.options.forEach(option => { optionElement = this._newOptionElement(option,option); this.view.select.appendChild(optionElement); }) this.view.options = this.shadowRoot.querySelectorAll("option"); } this.view.select.setAttribute("name", this.model.name); } _newOptionElement(value, innerHTML){ const temp = document.createElement("div"); temp.innerHTML = `<option ${value? "value="+value : ""}>${innerHTML}</option>`; return temp.querySelector("*"); } _removeEvents(){ this.view.container.removeEventListener("click", this.event.click); } disconnectedCallback() { this._removeEvents() this.state.connected = false; } showValidationError(error){ error = error || "Please provide a valid selection." this.view.errorMessage.innerText = error; this.view.select.classList.add("is-invalid"); } hideValidationError(){ this.view.errorMessage.innerText = ""; this.view.select.classList.remove("is-invalid"); } } window.customElements.define("ui-dropdown", DropdownViewController);