PageSourceSearch

https://dashboard.tastevinapp.com/assets/controllers/pos_sku_choos…8175f17d8e258306944b597ced56ce0001d837e1.js

js tastevinapp.com collected 2026-10-01 16:09:42 UTC 7,032 bytes, 185 lines download raw bytes

1import { Controller } from "@hotwired/stimulus"
2
3// Phase 8 — pos_sku_chooser_controller.
4//
5// Page-level companion to surfaces that need the "pick a POS SKU" modal.
6// Two trigger APIs:
7//
8// 1) Custom event (legacy, used by `beverage_sku_list_controller`):
9//      element.dispatchEvent(new CustomEvent("beverage-sku-list:open-pos-chooser",
10//        { detail: { url, triggerEl } }))
11//    The trigger has a `.sku_edit` ancestor with `.sku_input` + `.price_input`
12//    hidden fields that the controller writes back on row select.
13//
14// 2) Direct action (Phase 8 session 8 — used by MenuItem SKU tab):
15//      <a data-action="click->pos-sku-chooser#trigger"
16//         data-pos-chooser-url="/point_of_sale_skus/for_assigning?..."
17//         data-pos-chooser-id-input="<CSS selector>"
18//         data-pos-chooser-id-input-scope="closest:fieldset"
19//         data-pos-chooser-label-display="<CSS selector, optional>">
20//    The controller stashes references to those fields/spans and writes
21//    them back on row click. No `.sku_edit` ancestor needed.
22//
23// Either path opens the same shared modal (`shared/ui/_pos_sku_chooser`)
24// and reuses the same row-click delegation logic.
25//
26// Replaces the legacy jQuery-UI `.dialog({...}).load(url)` flow
27// (pos_sku_chooser_dialog.js).
28export default class extends Controller {
29  static targets = ["frame"]
30
31  connect() {
32    this._activeTrigger = null
33    // Per-open writeback state. Either resolved from declarative attrs
34    // on the trigger element (path 2) or computed from the trigger's
35    // ancestor chain at selectRow time (path 1).
36    this._writeback = null
37    this._openHandler = (event) => this.open(event)
38    this._rowClickHandler = (event) => this.selectRow(event)
39    document.addEventListener("beverage-sku-list:open-pos-chooser", this._openHandler)
40    if (this.hasFrameTarget) {
41      this.frameTarget.addEventListener("click", this._rowClickHandler)
42    }
43  }
44
45  disconnect() {
46    document.removeEventListener("beverage-sku-list:open-pos-chooser", this._openHandler)
47    if (this.hasFrameTarget) {
48      this.frameTarget.removeEventListener("click", this._rowClickHandler)
49    }
50  }
51
52  // ---- Open paths ----
53
54  // Custom-event entry — legacy `beverage-sku-list:open-pos-chooser`.
55  open(event) {
56    const { url, triggerEl } = event.detail || {}
57    if (!url || !this.hasFrameTarget) return
58    this._activeTrigger = triggerEl
59    this._writeback = null  // legacy path uses on-demand sku_edit lookup
60    this._loadAndShow(url)
61  }
62
63  // Direct action entry — used by MenuItem SKU tab.
64  trigger(event) {
65    const el = event.currentTarget
66    if (!el) return
67    event.preventDefault()
68    const url = el.getAttribute("data-pos-chooser-url") || el.getAttribute("href")
69    if (!url) return
70    this._activeTrigger = el
71    this._writeback = this._buildDeclarativeWriteback(el)
72    this._loadAndShow(url)
73  }
74
75  _loadAndShow(url) {
76    if (!this.hasFrameTarget) return
77    this.frameTarget.setAttribute("src", url)
78    const modal = document.getElementById("pos-sku-chooser-modal")
79    if (modal && typeof modal.showModal === "function") {
80      modal.showModal()
81    }
82  }
83
84  _buildDeclarativeWriteback(triggerEl) {
85    const idInputSel = triggerEl.getAttribute("data-pos-chooser-id-input")
86    if (!idInputSel) return null
87    const scopeSpec = triggerEl.getAttribute("data-pos-chooser-id-input-scope") || ""
88    const scope = this._resolveScope(triggerEl, scopeSpec)
89    const root = scope || document
90    const idInput = root.querySelector(idInputSel)
91    if (!idInput) return null
92    const labelSel = triggerEl.getAttribute("data-pos-chooser-label-display")
93    const labelEl = labelSel ? root.querySelector(labelSel) : null
94    // #2069 — optional price carry-over: fill this SKU's price input with the
95    // chosen POS SKU's price on select.
96    const priceSel = triggerEl.getAttribute("data-pos-chooser-price-input")
97    const priceInput = priceSel ? root.querySelector(priceSel) : null
98    return { idInput: idInput, labelEl: labelEl, priceInput: priceInput, triggerEl: triggerEl }
99  }
100
101  _resolveScope(el, spec) {
102    if (!spec) return null
103    const m = /^closest:(.+)$/.exec(spec)
104    if (m) return el.closest(m[1])
105    return el.closest(spec) || null
106  }
107
108  // ---- Row click ----
109
110  // Row click delegated from the <turbo-frame> content — the
111  // for_assigning partial renders `<tr data-id="..." data-sku="..."
112  // data-price="...">`.
113  selectRow(event) {
114    const row = event.target && event.target.closest("tr[data-sku], tr[data-id]")
115    if (!row) return
116    event.preventDefault()
117    const skuId = row.dataset.id
118    const sku   = row.dataset.sku
119    const price = row.dataset.price
120
121    if (this._writeback) {
122      // Declarative writeback (path 2).
123      this._writeback.idInput.value = skuId || "0"
124      this._writeback.idInput.dispatchEvent(new Event("change", { bubbles: true }))
125      if (this._writeback.labelEl) {
126        this._writeback.labelEl.textContent = sku ? this._truncateSku(sku) : "None"
127      }
128      // #2069 — carry the chosen POS SKU's price into this SKU's price input.
129      if (this._writeback.priceInput && price) {
130        this._writeback.priceInput.value = price
131        this._writeback.priceInput.dispatchEvent(new Event("change", { bubbles: true }))
132      }
133      this._setTriggerLabel(this._activeTrigger, sku)
134    } else if (this._activeTrigger) {
135      // Legacy `.sku_edit` lookup (path 1 — BeverageSku editor).
136      this._writebackToSkuEditRow(skuId, sku, price)
137    }
138    this._closeModal()
139  }
140
141  _writebackToSkuEditRow(skuId, sku, price) {
142    const skuEdit = this._activeTrigger.closest(".sku_edit")
143    const readRowAncestor = this._activeTrigger.closest("[data-id]")
144    const readRowId = readRowAncestor ? readRowAncestor.dataset.id : null
145    const editRow = skuEdit ||
146      (readRowId && document.querySelector(`.sku_edit[data-id="${this._cssEscape(readRowId)}"]`))
147    if (editRow) {
148      const skuInput   = editRow.querySelector(".sku_input")
149      const priceInput = editRow.querySelector(".price_input")
150      if (skuInput) {
151        skuInput.value = skuId || "0"
152        skuInput.dispatchEvent(new Event("change", { bubbles: true }))
153      }
154      if (priceInput && price && priceInput.value === "0.00") {
155        priceInput.value = price
156      }
157    }
158    this._setTriggerLabel(this._activeTrigger, sku)
159  }
160
161  _setTriggerLabel(triggerEl, sku) {
162    if (!triggerEl) return
163    const buttonText = sku ? this._truncateSku(sku) : "None"
164    triggerEl.textContent = buttonText
165  }
166
167  _closeModal() {
168    const modal = document.getElementById("pos-sku-chooser-modal")
169    if (modal && typeof modal.close === "function") {
170      modal.close()
171    }
172    this._activeTrigger = null
173    this._writeback = null
174  }
175
176  _truncateSku(value) {
177    const str = String(value)
178    return str.length > 10 ? "..." + str.slice(-7) : str
179  }
180
181  _cssEscape(value) {
182    if (window.CSS && CSS.escape) return CSS.escape(value)
183    return String(value).replace(/(["\\])/g, "\\$1")
184  }
185};

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.