1import { Controller } from "@hotwired/stimulus" 2import { Turbo } from "@hotwired/turbo-rails" 3 4export default class extends Controller { 5 static targets = ["display", "form", "input", "value"] 6 static values = { 7 field: String, 8 url: String, 9 type: { type: String, default: "text" } 10 } 11 12 connect() { 13 this._saving = false 14 this._originalValue = this.inputTarget.value 15 } 16 17 startEdit() { 18 this._originalValue = this.inputTarget.value 19 this.displayTarget.classList.add("hidden") 20 this.formTarget.classList.remove("hidden") 21 this.inputTarget.focus() 22 if (this.typeValue === "text") { 23 this.inputTarget.select() 24 } 25 } 26 27 cancelEdit() { 28 this.inputTarget.value = this._originalValue 29 this.formTarget.classList.add("hidden") 30 this.displayTarget.classList.remove("hidden") 31 } 32 33 handleKeydown(event) { 34 if (event.key === "Enter" && this.typeValue !== "textarea") { 35 event.preventDefault() 36 this.save() 37 } else if (event.key === "Escape") { 38 this.cancelEdit() 39 } 40 } 41 42 handleBlur() { 43 // Small delay to allow cancel button clicks to register 44 setTimeout(() => { 45 if (!this._saving && this.hasFormTarget && !this.formTarget.classList.contains("hidden")) { 46 this.save() 47 } 48 }, 150) 49 } 50 51 // Called via a data-action when the address-autocomplete controller has taken 52 // over saving (a Places selection). Prevents the duplicate blur-save. 53 suppressSave() { 54 this._saving = true 55 } 56 57 // Re-enables blur-save after the address-autocomplete controller's bundle 58 // save failed (it had called suppressSave on the Places selection). 59 resumeSave() { 60 this._saving = false 61 } 62 63 stopPropagation(event) { 64 event.stopPropagation() 65 } 66 67 formatEin() { 68 let v = this.inputTarget.value.replace(/[^0-9]/g, "").slice(0, 9) 69 if (v.length > 2) v = v.slice(0, 2) + "-" + v.slice(2) 70 this.inputTarget.value = v 71 } 72 73 formatPhone() { 74 let v = this.inputTarget.value.replace(/[^0-9]/g, "").slice(0, 10) 75 if (v.length > 6) v = `(${v.slice(0,3)}) ${v.slice(3,6)}-${v.slice(6)}` 76 else if (v.length > 3) v = `(${v.slice(0,3)}) ${v.slice(3)}` 77 this.inputTarget.value = v 78 } 79 80 async save() { 81 if (this._saving) return 82 this._saving = true 83 84 const newValue = this.inputTarget.value 85 // Don't save if value hasn't changed 86 if (newValue === this._originalValue) { 87 this._saving = false 88 this.cancelEdit() 89 return 90 } 91 92 this.element.style.opacity = "0.6" 93 94 try { 95 const formData = new FormData() 96 formData.append(`masjid[${this.fieldValue}]`, newValue) 97 98 const response = await fetch(this.urlValue, { 99 method: "PATCH", 100 headers: { 101 "X-CSRF-Token": document.querySelector('meta[name="csrf-token"]').content, 102 "Accept": "text/vnd.turbo-stream.html" 103 }, 104 body: formData 105 }) 106 107 if (response.ok) { 108 const html = await response.text() 109 Turbo.renderStreamMessage(html) 110 this._originalValue = newValue 111 } else { 112 this.inputTarget.classList.add("input-error") 113 setTimeout(() => this.inputTarget.classList.remove("input-error"), 2000) 114 this.inputTarget.value = this._originalValue 115 } 116 } catch { 117 this.inputTarget.value = this._originalValue 118 } finally { 119 this.element.style.opacity = "1" 120 this._saving = false 121 } 122 } 123};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.