UNPKG

globular-mvc

Version:

Generic template to create web-application that made use of globular as backend and materialize as css (wrap in web-component's)

1,194 lines (1,016 loc) 47.4 kB
import { Model } from "../Model"; import '@polymer/paper-input/paper-input.js'; import '@polymer/paper-dropdown-menu/paper-dropdown-menu.js' import '@polymer/paper-listbox/paper-listbox.js' import '@polymer/paper-item/paper-item.js' import '@polymer/paper-menu-button/paper-menu-button.js' import '@polymer/neon-animation/neon-animation-runner-behavior.js' import 'web-animations-js/web-animations-next.min.js' import '@polymer/paper-input/paper-textarea.js'; /** * * <globular-form > * <globular-form-section> * <globular-string-field label="grape"> * </globular-string-field> * <globular-string-field label="grapefruit" x="2" y="1"> * </globular-string-field> * <globular-form-section> * <globular-form-section> * <globular-string-field label="Grapefruit Pie" x="1" y="1" width="2" height="3" xsmall ysmall widthsmall heightsmall xlarge ylarge widthlarge heightlarge> * </globular-string-field> * <globular-form-section> * </globular-form> */ export class Form extends HTMLElement { //Should be defined with screen size as well. Might be useful to make it grid instead of flex. constructor() { super() // Set the shadow dom. this.attachShadow({ mode: "open" }); // Setup basic HTML this.shadowRoot.innerHTML = ` <style> #container { display: flex; flex-direction: column; width: 85vw; } </style> <div id="container"> <slot> </slot> <paper-button id="save-btn">Enregistrer</paper-button> </div> ` this.style.display = "flex" this.container = this.shadowRoot.getElementById("container") this.shadowRoot.getElementById("save-btn").onclick = this.confirm.bind(this) } clear() { this.container.innerHTML = '' } appendFormSection(formSection) { if (formSection) { this.appendChild(formSection) } // TODO: Create side menu item for each new section which navigates to the new form section. } /** * Creates a Toast popup to confirm if the user wants to save their form. */ confirm() { const toastHtml = ` <style> #button-container { display: flex; flex-direction: row; justify-content: space-between; background-color: transparent; flex-grow: 1; } #toast-main-container { display: flex; flex-direction: column; background-color: transparent; } #toast-text { padding-bottom: 2rem; } .toast-btn { box-shadow: none; background-color: transparent; cursor: pointer; transition: background-color .2s; border: none; border-radius: 3px; padding: 0.5rem; } .toast-btn:focus { background-color: rgba(255,255,255,0.1); } </style> <div id="toast-main-container"> <span id="toast-text">Est-ce que vous voulez sauvegarder votre formulaire?</span> <div id="button-container"> <button class="toast-btn" id="save-btn">Enregistrer</button> <button class="toast-btn" id="cancel-btn">Annuler</button> </div> </div> ` M.toast({html: toastHtml, displayLength: 999999}) let toast = document.querySelector(".toast") toast.querySelector("#save-btn").onclick = this.save.bind(this) toast.querySelector("#cancel-btn").onclick = this.cancel.bind(this) Model.eventHub.publish("lock_form_evt", true, true) } /** * Sends an event over your local network to save, unlock, then reset the current form. */ save() { Model.eventHub.publish("save_form_evt", true, true) document.querySelector(".toast").remove() Model.eventHub.publish("unlock_form_evt", true, true) // Model.eventHub.publish("reset_form_evt", true, true) } /** * Sends an event over your local network to unlock the current form. */ cancel() { document.querySelector(".toast").remove() Model.eventHub.publish("unlock_form_evt", true, true) } } customElements.define("globular-form", Form); export class FormSection extends HTMLElement { constructor(title, subtitle, sectionWidth, sectionHeight) { super() this.idTitle = title.split(" ").join("_"); this.title = title this.subtitle = subtitle // Set the shadow dom. this.attachShadow({ mode: "open" }); // Setup basic HTML this.shadowRoot.innerHTML = ` <style> #container { display: grid; grid-template-columns: repeat(${sectionWidth}, 1fr); grid-template-rows: repeat(${sectionHeight}, 1fr); gap: 1rem 1rem; paddding: 1rem; } .card-title{ position: absolute; font-size: 2rem; text-transform: uppercase; color: var(--cr-primary-text-color); font-weight: 400; top: .25rem; letter-spacing: .25px; margin-bottom: .35rem; margin-top: var(--cr-section-vertical-margin); outline: none; padding-bottom: .25rem; padding-top: .5rem; padding-left: 2rem; background: transparent; } .card-subtitle{ padding: 48px; letter-spacing: .01428571em; font-family: Roboto,Arial,sans-serif; font-size: 1.125rem; font-weight: 400; line-height: 1.25rem; hyphens: auto; word-break: break-word; word-wrap: break-word; color: var(--cr-primary-text-color); flex-grow: 1; } .card { display:flex; flex-direction:column; } paper-card{ background-color: var(--palette-background-paper); color: var(--palette-text-primary); } </style> <paper-card class="card" id="${this.idTitle}_form_section"> <h2 class="card-title">${this.title}</h2> <div class="card-subtitle">${this.subtitle}</div> <div> <slot id="container"> </slot> </div> </paper-card> ` this.container = this.shadowRoot.getElementById("container") } clear() { this.container.innerHTML = '' } appendField(field) { if (field) { this.appendChild(field) } } } customElements.define("globular-form-section", FormSection); /** * Never instantiate a Field variable. This is meant to be an abstract class that must be implemented by a derived class in order to be used properly. */ export class Field extends HTMLElement { /** * If x or y are 0 or negative, then there cannot be a width or a height since the grid is dependent on initial position. * The position will also be automatically placed within the grid. * * If width or height are 0 or negative, then their value will be a default of 1. * * The above conditions apply for all variations of those parameters. * Also, it isn't necessary to have all the different dimensions. It is possible to only have an arbitrary amount of the small dimensions, the phone dimensions or the regular dimensions. * * @param {*} label (aka Title) The string which will be display at the top of the Field. * @param {*} initialValue The initial value that the input will show * @param {*} x The initial position of the Field on the x axis. Starts at 1. * @param {*} y The initial position of the Field on the y axis. Starts at 1. * @param {*} width The width of the Field in grid units. * @param {*} height The height of the Field in grid units. * @param {*} xSmall The position of the Field on the x axis when the screen is small. Starts at 1. * @param {*} ySmall The position of the Field on the y axis when the screen is small. Starts at 1. * @param {*} widthSmall The width of the Field when the screen is small in grid units. * @param {*} heightSmall The height of the Field when the screen is small in grid units. * @param {*} xPhone The position of the Field on the x axis when the screen is about the size of a phone. Starts at 1. * @param {*} yPhone The position of the Field on the y axis when the screen is about the size of a phone. Starts at 1. * @param {*} widthPhone The width of the Field when the screen is about the size of a phone in grid units. * @param {*} heightPhone The height of the Field when the screen is about the size of a phone in grid units. */ constructor(label, initialValue, x = 0, y = 0, width = 0, height = 0, xSmall = 0, ySmall = 0, widthSmall = 0, heightSmall = 0, xPhone = 0, yPhone = 0, widthPhone = 0, heightPhone = 0) { super() this.initialValue = initialValue this.smallThreshold = 800 this.phoneThreshold = 500 this.invalidColor = "red" this.label = label let hostHtml = this._getAllSizes(x, y, width, height, xSmall, ySmall, widthSmall, heightSmall, xPhone, yPhone, widthPhone, heightPhone) // Set the shadow dom. this.attachShadow({ mode: "open" }); // Setup basic HTML this.shadowRoot.innerHTML = ` <style> .field-label { line-height: 1rem; font-size: .875rem; font-weight: 500; letter-spacing: .07272727em; text-transform: uppercase; hyphens: auto; word-break: break-word; word-wrap: break-word; padding: .5rem; } #container { width: 100%; height: 100%; display: flex; flex-direction: column; } #container paper-input { margin-top: auto; padding: 0.5rem; } #container paper-input:invalid { border-bottom: 1px dashed red; } #container paper-dropdown-menu { margin-top: auto; padding: 0.5rem; } #container textarea { margin-top:auto; word-wrap: break-word; margin:0.5rem; outline: var(--paper-input-container-shared-input-style_-_outline); background: var(--paper-input-container-shared-input-style_-_background); border: var(--paper-input-container-shared-input-style_-_border); font-family: var(--paper-input-container-shared-input-style_-_font-family); font-size: var(--paper-input-container-shared-input-style_-_font-size); font-weight: var(--paper-input-container-shared-input-style_-_font-weight); } #field-view { text-align: center; font-size: 1rem; } paper-card{ background-color: var(--palette-background-paper); color: var(--palette-text-primary); } ${hostHtml} </style> <paper-card id="container"> <div id="name-div" class="field-label">${label}</div> </paper-card> ` this.container = this.shadowRoot.getElementById("container") } /** * Gets the HTML resize and size code for the all the different dimensions. * * @param {*} x The initial position of the Field on the x axis. Starts at 1. * @param {*} y The initial position of the Field on the y axis. Starts at 1. * @param {*} width The width of the Field in grid units. * @param {*} height The height of the Field in grid units. * @param {*} xSmall The position of the Field on the x axis when the screen is small. Starts at 1. * @param {*} ySmall The position of the Field on the y axis when the screen is small. Starts at 1. * @param {*} widthSmall The width of the Field when the screen is small. * @param {*} heightSmall The height of the Field when the screen is small. * @param {*} xPhone The position of the Field on the x axis when the screen is about the size of a phone. Starts at 1. * @param {*} yPhone The position of the Field on the y axis when the screen is about the size of a phone. Starts at 1. * @param {*} widthPhone The width of the Field when the screen is about the size of a phone. * @param {*} heightPhone The height of the Field when the screen is about the size of a phone. */ _getAllSizes(x, y, width, height, xSmall, ySmall, widthSmall, heightSmall, xPhone, yPhone, widthPhone, heightPhone) { let hostHtml = this._getSize(x, y, width, height) hostHtml += this._getConditionalSize(this.smallThreshold, xSmall, ySmall, widthSmall, heightSmall) hostHtml += this._getConditionalSize(this.phoneThreshold, xPhone, yPhone, widthPhone, heightPhone) return hostHtml } /** * Gets the conditional resize code for the specified dimensions. * * @param {*} pixelWidth If the width of the screen drops underneath this value, then the dimensions will change * @param {*} x The initial position of the Field on the x axis. Starts at 1. * @param {*} y The initial position of the Field on the y axis. Starts at 1. * @param {*} width The width of the Field in grid units. * @param {*} height The height of the Field in grid units. */ _getConditionalSize(pixelWidth, x, y, width, height) { let conditionalHtml = `` if (!pixelWidth || pixelWidth < 0) { return conditionalHtml } conditionalHtml = `@media only screen and (max-width: ${pixelWidth}px) { ${this._getSize(x, y, width, height)} }` return conditionalHtml } /** * Gets the CSS for the size of the host element. The size is setup using grid-columns. * * If x or y are 0 or negative, then there cannot be a width or a height since the grid is dependent on initial position. * The position will also be automatically placed within the grid. * * If width or height are 0 or negative, then their value will be 1. * * @param {*} x The initial position of the Field on the x axis. Starts at 1. * @param {*} y The initial position of the Field on the y axis. Starts at 1. * @param {*} width The width of the Field in grid units. * @param {*} height The height of the Field in grid units. */ _getSize(x, y, width, height) { let hostHtml = `:host { ` if (x && x > 0) { hostHtml += `grid-column: ${x}` if (width && width > 0) { hostHtml += ` / ${x + width}` } hostHtml += `; ` } if (y && y > 0) { hostHtml += `grid-row: ${y}` if (height && height > 0) { hostHtml += ` / ${y + height}` } hostHtml += `; ` } hostHtml += `} ` return hostHtml } /** * Hides all the field's elements */ hide() { this.container.style.display = "none"; } /** * Shows all the field's elements */ show() { this.container.style.display = ""; } /** * Reset the value of the view and input elements of the configuration with their initial value. */ reset() { this.setValue(this.initialValue) } /** * Changes the initial value of the component to the new value. Modifies the current value. * @param {*} v New reset value */ set(v) { this.initialValue = v this.setValue(v) } /** * Returns the value of the current input. */ getValue() { return "" } /** * Sets the value of the current input of the view and input elements. * * @param {*} v New value for the current input */ setValue(v) { } /** * Sets the value of the view and input elements to a nill value. */ clear() { } /** * Checks to see whether the input field is valid. */ isValid() { return false } /** * Returns text saying what is invalid within the field. */ getInvalidText() { return `Le champs, ${this.label}, n'est pas valide.` } /** * Marks the field as being invalid either by making it red or something of the sort. */ markInvalid() { } /** * Marks the field as being valid by making it normal. */ markValid() { } /** * Disables the input element and enables the view element. */ lock() { } /** * Enables the input element and disables the view element. */ unlock() { } } /** * A simple input field which accepts any string as its input. */ export class StringField extends Field { /** * If x or y are 0 or negative, then there cannot be a width or a height since the grid is dependent on initial position. * The position will also be automatically placed within the grid. * * If width or height are 0 or negative, then their value will be a default of 1. * * The above conditions apply for all variations of those parameters. * Also, it isn't necessary to have all the different dimensions. It is possible to only have the small dimensions, the phone dimensions or the regular dimensions. * * @param {*} label (aka Title) The string which will be display at the top of the Field. * @param {*} description The string which is displayed within the input and hovers above it once text is entered. * @param {*} initialValue The initial value that the input will show * @param {*} x The initial position of the Field on the x axis. Starts at 1. * @param {*} y The initial position of the Field on the y axis. Starts at 1. * @param {*} width The width of the Field in grid units. * @param {*} height The height of the Field in grid units. * @param {*} xSmall The position of the Field on the x axis when the screen is small. Starts at 1. * @param {*} ySmall The position of the Field on the y axis when the screen is small. Starts at 1. * @param {*} widthSmall The width of the Field when the screen is small in grid units. * @param {*} heightSmall The height of the Field when the screen is small in grid units. * @param {*} xPhone The position of the Field on the x axis when the screen is about the size of a phone. Starts at 1. * @param {*} yPhone The position of the Field on the y axis when the screen is about the size of a phone. Starts at 1. * @param {*} widthPhone The width of the Field when the screen is about the size of a phone in grid units. * @param {*} heightPhone The height of the Field when the screen is about the size of a phone in grid units. */ constructor(label, description, initialValue = "", x = 0, y = 0, width = 0, height = 0, xSmall = 0, ySmall = 0, widthSmall = 0, heightSmall = 0, xPhone = 0, yPhone = 0, widthPhone = 0, heightPhone = 0) { super(label, initialValue, x, y, width, height, xSmall, ySmall, widthSmall, heightSmall, xPhone, yPhone, widthPhone, heightPhone) let html = ` <paper-input id="field-input" label="${description}" raised required error="This field is required."></paper-input> <div id="field-view"></div> ` // Inserts the html into the proper area in the container let range = document.createRange(); this.container.appendChild(range.createContextualFragment(html)) // Gets the elements necessary for manipulating the DOM this.input = this.shadowRoot.getElementById("field-input"); this.view = this.shadowRoot.getElementById("field-view") //By default, show the input element and not the view element this.unlock() this.reset() } getValue() { return this.input.value } setValue(v) { this.input.value = v this.view.innerHTML = v } clear() { this.setValue("") } isValid() { return this.getValue() !== undefined && this.getValue() !== null && this.getValue() !== "" } markInvalid() { this.input.style.borderColor = this.invalidColor this.input.style.borderStyle = "dashed" } markValid() { this.input.style.border = "none" } lock() { this.view.innerHTML = this.input.value // TODO: Change the method to remove and replace the elements this.input.style.display = "none" this.view.style.display = "" } unlock() { this.input.style.display = "" this.view.style.display = "none" } } customElements.define("globular-string-field", StringField); /** * An input field with multiple lines which can accept any string as input. */ export class TextAreaField extends Field { /** * If x or y are 0 or negative, then there cannot be a width or a height since the grid is dependent on initial position. * The position will also be automatically placed within the grid. * * If width or height are 0 or negative, then their value will be a default of 1. * * The above conditions apply for all variations of those parameters. * Also, it isn't necessary to have all the different dimensions. It is possible to only have the small dimensions, the phone dimensions or the regular dimensions. * * @param {*} label (aka Title) The string which will be display at the top of the Field. * @param {*} description The string which is displayed within the input and hovers above it once text is entered. * @param {*} initialValue The initial value that the input will show * @param {*} x The initial position of the Field on the x axis. Starts at 1. * @param {*} y The initial position of the Field on the y axis. Starts at 1. * @param {*} width The width of the Field in grid units. * @param {*} height The height of the Field in grid units. * @param {*} xSmall The position of the Field on the x axis when the screen is small. Starts at 1. * @param {*} ySmall The position of the Field on the y axis when the screen is small. Starts at 1. * @param {*} widthSmall The width of the Field when the screen is small in grid units. * @param {*} heightSmall The height of the Field when the screen is small in grid units. * @param {*} xPhone The position of the Field on the x axis when the screen is about the size of a phone. Starts at 1. * @param {*} yPhone The position of the Field on the y axis when the screen is about the size of a phone. Starts at 1. * @param {*} widthPhone The width of the Field when the screen is about the size of a phone in grid units. * @param {*} heightPhone The height of the Field when the screen is about the size of a phone in grid units. */ constructor(label, description, initialValue = "", x = 0, y = 0, width = 0, height = 0, xSmall = 0, ySmall = 0, widthSmall = 0, heightSmall = 0, xPhone = 0, yPhone = 0, widthPhone = 0, heightPhone = 0) { super(label, initialValue, x, y, width, height, xSmall, ySmall, widthSmall, heightSmall, xPhone, yPhone, widthPhone, heightPhone) const rows = 3 + Math.floor(5.5 * Math.max(height - 1, 0)) let html = ` <textarea id="field-input" placeholder="${description}" rows="${rows}"></textarea> <div id="field-view"></div> ` // Inserts the html into the proper area in the container let range = document.createRange(); this.container.appendChild(range.createContextualFragment(html)) // Gets the elements necessary for manipulating the DOM this.input = this.shadowRoot.getElementById("field-input"); this.view = this.shadowRoot.getElementById("field-view") //Three listeners to change amount of rows this._setMaxHeightListener(heightSmall, this.smallThreshold) this._setMaxHeightListener(heightPhone, this.phoneThreshold) this._setMinHeightListener(heightSmall, this.smallThreshold) //By default, show the input element and not the view element this.unlock() this.reset() } /** * Sets a listener which checks when the viewport width is greater than a certain threshold * @param {*} height The height of the input after the modification in grid units. * @param {*} threshold in pixels. */ _setMinHeightListener(height, threshold) { if (height && height > 0) { let watcher = window.matchMedia("(min-width: " + (threshold + 1) + "px)") this._mediaQueryRows(watcher, height) watcher.addEventListener("change", () => { this._mediaQueryRows(watcher, height) }) } } /** * Sets a listener which checks when the viewport width is lesser than a certain threshold * @param {*} height The height of the input after the modification in grid units. * @param {*} threshold in pixels. */ _setMaxHeightListener(height, threshold) { if (height && height > 0) { let watcher = window.matchMedia("(max-width: " + threshold + "px)") this._mediaQueryRows(watcher, height) watcher.addEventListener("change", () => { this._mediaQueryRows(watcher, height) }) } } /** * Queries the watcher to check when the condition is met, then modifies the rows to the appropriate height. * @param {*} watcher The media query which specifies the necessary condition. * @param {*} height The height of the input after the modification in grid units. */ _mediaQueryRows(watcher, height) { if (watcher.matches) { this.input.setAttribute("rows", this._calcRow(height)) } } /** * Calculates the amount of rows necessary for a given height. (Subject to change if found to be ugly) * @param {*} height The height of the input after the modification in grid space. */ _calcRow(height) { return 3 + Math.floor(5.5 * Math.max(height - 1, 0)) } getValue() { return this.input.value } setValue(v) { this.input.value = v this.view.innerHTML = v } clear() { this.setValue("") } isValid() { return this.getValue() !== undefined && this.getValue() !== null && this.getValue() !== "" } markInvalid() { this.input.style.borderColor = this.invalidColor this.input.style.borderStyle = "dashed" } markValid() { this.input.style.border = "none" } lock() { this.view.innerHTML = this.input.value // TODO: Change the method to remove and replace the elements this.input.style.display = "none" this.view.style.display = "" } unlock() { this.input.style.display = "" this.view.style.display = "none" } } customElements.define("globular-text-area-field", TextAreaField); /** * An input field which uses a list that the user will choose from. */ export class DropdownField extends Field { /** * If x or y are 0 or negative, then there cannot be a width or a height since the grid is dependent on initial position. * The position will also be automatically placed within the grid. * * If width or height are 0 or negative, then their value will be a default of 1. * * The above conditions apply for all variations of those parameters. * Also, it isn't necessary to have all the different dimensions. It is possible to only have the small dimensions, the phone dimensions or the regular dimensions. * * @param {*} label (aka Title) The string which will be display at the top of the Field. * @param {*} description The string which is displayed within the input and hovers above it once text is entered. * @param {*} itemList The items displayed in the dropdown list. * @param {*} initialValue The initial value that the input will show * @param {*} x The initial position of the Field on the x axis. Starts at 1. * @param {*} y The initial position of the Field on the y axis. Starts at 1. * @param {*} width The width of the Field in grid units. * @param {*} height The height of the Field in grid units. * @param {*} xSmall The position of the Field on the x axis when the screen is small. Starts at 1. * @param {*} ySmall The position of the Field on the y axis when the screen is small. Starts at 1. * @param {*} widthSmall The width of the Field when the screen is small in grid units. * @param {*} heightSmall The height of the Field when the screen is small in grid units. * @param {*} xPhone The position of the Field on the x axis when the screen is about the size of a phone. Starts at 1. * @param {*} yPhone The position of the Field on the y axis when the screen is about the size of a phone. Starts at 1. * @param {*} widthPhone The width of the Field when the screen is about the size of a phone in grid units. * @param {*} heightPhone The height of the Field when the screen is about the size of a phone in grid units. */ constructor(label, description, itemList, initialValue = "", x = 0, y = 0, width = 0, height = 0, xSmall = 0, ySmall = 0, widthSmall = 0, heightSmall = 0, xPhone = 0, yPhone = 0, widthPhone = 0, heightPhone = 0) { super(label, initialValue, x, y, width, height, xSmall, ySmall, widthSmall, heightSmall, xPhone, yPhone, widthPhone, heightPhone) this.itemList = itemList let html = ` <paper-dropdown-menu id="field-input" class="dropdown-menu" label="${description}" raised> <paper-listbox class="dropdown-content" slot="dropdown-content"> </paper-listbox> </paper-dropdown-menu> <div id="field-view"></div> ` // Inserts the html into the proper area in the container let range = document.createRange(); this.container.appendChild(range.createContextualFragment(html)) // Gets the elements necessary for manipulating the DOM this.input = this.shadowRoot.getElementById("field-input") this.view = this.shadowRoot.getElementById("field-view") this.listbox = this.shadowRoot.querySelector(".dropdown-content") this.listbox.innerHTML = this._getHtmlArray() //By default, show the input element and not the view element this.unlock() this.reset() } /** * A connected callback executes after the constructor and is necessary to make sure HTML is properly initialized */ connectedCallback() { this.addEventListener('iron-resize', this._onIronResize.bind(this)); } /** * Resizes the dropdown list to be the same size as the input. */ _onIronResize() { this.listbox.style.width = this.input.shadowRoot.getElementById("menuButton").offsetWidth + "px" } /** * Gets the HTML for the items within the dropdown list. */ _getHtmlArray() { let htmlArray = `` if (!this.itemList || this.itemList.length < 0) { return htmlArray; } for (let i = 0; i < this.itemList.length; i++) { htmlArray += `<paper-item value="${this.itemList[i]}">${this.itemList[i]}</paper-item> ` } return htmlArray } setList(l) { this.itemList = l this.listbox.innerHTML = this._getHtmlArray() } setInputOnChange(f) { this.listbox.addEventListener('iron-select', f); } getValue() { if(this.listbox.selected === null || this.listbox.selected === undefined || this.listbox.selected < 0) return "" return this.listbox.getElementsByTagName("paper-item")[this.listbox.selected].getAttribute("value") } setValue(v) { let htmlItemlist = this.listbox.getElementsByTagName("paper-item") for (let i = 0; i < htmlItemlist.length; i++) { if (htmlItemlist[i].getAttribute("value") == v) { this.listbox.selected = i return } } } clear() { this.setValue("") } isValid() { return this.getValue() !== undefined && this.getValue() !== null && this.getValue() !== "" } markInvalid() { this.input.style.borderColor = this.invalidColor this.input.style.borderStyle = "dashed" } markValid() { this.input.style.border = "none" } lock() { this.view.innerHTML = this.getValue() // TODO: Change the method to remove and replace the elements this.input.style.display = "none" this.view.style.display = "" } unlock() { this.input.style.display = "" this.view.style.display = "none" } reset() { this.listbox.selected = -1 } } customElements.define("globular-dropdown-field", DropdownField); /** * An input field which accepts an Image from the user's disk. */ export class ImageField extends Field { /** * If x or y are 0 or negative, then there cannot be a width or a height since the grid is dependent on initial position. * The position will also be automatically placed within the grid. * * If width or height are 0 or negative, then their value will be a default of 1. * * The above conditions apply for all variations of those parameters. * Also, it isn't necessary to have all the different dimensions. It is possible to only have the small dimensions, the phone dimensions or the regular dimensions. * * @param {*} label (aka Title) The string which will be display at the top of the Field. * @param {*} description The string which is displayed within the input and hovers above it once text is entered. * @param {*} initialValue The initial value that the input will show * @param {*} x The initial position of the Field on the x axis. Starts at 1. * @param {*} y The initial position of the Field on the y axis. Starts at 1. * @param {*} width The width of the Field in grid units. * @param {*} height The height of the Field in grid units. * @param {*} xSmall The position of the Field on the x axis when the screen is small. Starts at 1. * @param {*} ySmall The position of the Field on the y axis when the screen is small. Starts at 1. * @param {*} widthSmall The width of the Field when the screen is small in grid units. * @param {*} heightSmall The height of the Field when the screen is small in grid units. * @param {*} xPhone The position of the Field on the x axis when the screen is about the size of a phone. Starts at 1. * @param {*} yPhone The position of the Field on the y axis when the screen is about the size of a phone. Starts at 1. * @param {*} widthPhone The width of the Field when the screen is about the size of a phone in grid units. * @param {*} heightPhone The height of the Field when the screen is about the size of a phone in grid units. */ constructor(label, description, initialValue = "", x = 0, y = 0, width = 0, height = 0, xSmall = 0, ySmall = 0, widthSmall = 0, heightSmall = 0, xPhone = 0, yPhone = 0, widthPhone = 0, heightPhone = 0) { super(label, initialValue, x, y, width, height, xSmall, ySmall, widthSmall, heightSmall, xPhone, yPhone, widthPhone, heightPhone) let html = ` <style> #custom-file-upload span{ flex-grow: 1; } #custom-file-upload iron-icon{ padding-right: 15px; } #custom-file-upload{ display: flex; align-items: flex-end; border-bottom: 1px solid var(--palette-text-primary); font-size: 1rem; flex-basis: 100%; letter-spacing: .00625em; font-weight: 400; line-height: 1.5rem; word-break: break-word; margin-top: auto; padding: 0.5rem; } #custom-file-upload:hover{ cursor: pointer; } #field-input { display: none; } #image-view { height: 100%; width: 100%; } #image-display-div{ position: relative; display: flex; align-items: center; justify-content: center; min-width: 32px; height: 100%; max-height:100%; width:auto; max-width:100%; } img { position: absolute; height: 100%; width: auto; } </style> <div id="visual-input"> <div id="custom-file-upload"> <iron-icon icon="cloud-upload"> </iron-icon> <span>${description}</span> </div> </div> <div id="image-view"> <div id="image-display-div"> <img id="image-display" src="#" /> <iron-icon id="no-image-display" icon="image:photo"></iron-icon> </div> </div> <input type="file" id="field-input"></input> ` // Inserts the html into the proper area in the container let range = document.createRange(); this.container.appendChild(range.createContextualFragment(html)) // Gets the elements necessary for manipulating the DOM this.visualInput = this.shadowRoot.getElementById("visual-input") this.input = this.shadowRoot.getElementById("field-input") this.image = this.shadowRoot.getElementById("image-display") this.icon = this.shadowRoot.getElementById("no-image-display") this.image.style.display = "none" this.onchange = null this._initFileEvent() // Links the fancy div's onclick event with the input's onclick event. this.shadowRoot.getElementById("custom-file-upload").onclick = () => { this.input.click(); } //By default, show the input element and not the view element this.unlock() this.reset() } /** * Tells the input to put the file that was found by the user into an img tag to display it. */ _initFileEvent() { this.input.onchange = (evt) => { const files = evt.target.files if (files && files[0]) { let reader = new FileReader() reader.onload = (e) => { this.image.src = e.target.result // Setup the onchange event if (this.onchange) { this.onchange(this.image.src) } this.image.style.display = "block"; this.icon.style.display = "none"; } reader.readAsDataURL(files[0]) } } } getValue() { return this.image.src } setValue(v) { this.input.value = v } clear() { this.setValue("") } isValid() { return this.getValue() !== undefined && this.getValue() !== null && this.getValue() !== "" } markInvalid() { this.visualInput.style.borderColor = this.invalidColor this.visualInput.style.borderStyle = "dashed" } markValid() { this.visualInput.style.border = "none" } lock() { // TODO: Change the method to remove and replace the elements this.visualInput.style.display = "none" } unlock() { // TODO: The actual view is not disabled in this case when unlocked because you want to see it this.visualInput.style.display = "" // this.view.style.display = "none" } } customElements.define("globular-image-field", ImageField); /** * An input field which accepts a date and also has a fancy date picker. */ export class DateField extends Field { /** * If x or y are 0 or negative, then there cannot be a width or a height since the grid is dependent on initial position. * The position will also be automatically placed within the grid. * * If width or height are 0 or negative, then their value will be a default of 1. * * The above conditions apply for all variations of those parameters. * Also, it isn't necessary to have all the different dimensions. It is possible to only have the small dimensions, the phone dimensions or the regular dimensions. * * @param {*} label (aka Title) The string which will be display at the top of the Field. * @param {*} description The string which is displayed within the input and hovers above it once text is entered. * @param {*} initialValue The initial value that the input will show * @param {*} x The initial position of the Field on the x axis. Starts at 1. * @param {*} y The initial position of the Field on the y axis. Starts at 1. * @param {*} width The width of the Field in grid units. * @param {*} height The height of the Field in grid units. * @param {*} xSmall The position of the Field on the x axis when the screen is small. Starts at 1. * @param {*} ySmall The position of the Field on the y axis when the screen is small. Starts at 1. * @param {*} widthSmall The width of the Field when the screen is small in grid units. * @param {*} heightSmall The height of the Field when the screen is small in grid units. * @param {*} xPhone The position of the Field on the x axis when the screen is about the size of a phone. Starts at 1. * @param {*} yPhone The position of the Field on the y axis when the screen is about the size of a phone. Starts at 1. * @param {*} widthPhone The width of the Field when the screen is about the size of a phone in grid units. * @param {*} heightPhone The height of the Field when the screen is about the size of a phone in grid units. */ constructor(label, description, initialValue = "", x = 0, y = 0, width = 0, height = 0, xSmall = 0, ySmall = 0, widthSmall = 0, heightSmall = 0, xPhone = 0, yPhone = 0, widthPhone = 0, heightPhone = 0) { super(label, initialValue, x, y, width, height, xSmall, ySmall, widthSmall, heightSmall, xPhone, yPhone, widthPhone, heightPhone) let html = ` <paper-input id="field-input" label="${description}" type="datetime-local" raised required error="This field is required."></paper-input> <div id="field-view"></div> ` // Inserts the html into the proper area in the container let range = document.createRange(); this.container.appendChild(range.createContextualFragment(html)) // Gets the elements necessary for manipulating the DOM this.input = this.shadowRoot.getElementById("field-input"); this.view = this.shadowRoot.getElementById("field-view") //By default, show the input element and not the view element this.unlock() this.reset() } getValue() { return this.input.value } setValue(v) { this.input.value = v this.view.innerHTML = v } clear() { this.setValue("") } isValid() { return this.getValue() !== undefined && this.getValue() !== null && this.getValue() !== "" } markInvalid() { this.input.style.borderColor = this.invalidColor this.input.style.borderStyle = "dashed" } markValid() { this.input.style.border = "none" } lock() { this.view.innerHTML = this.input.value // TODO: Change the method to remove and replace the elements this.input.style.display = "none" this.view.style.display = "" } unlock() { this.input.style.display = "" this.view.style.display = "none" } } customElements.define("globular-date-field", DateField);