PageSourceSearch

https://platform.isms.online/assets/controllers/calendars/user_todo_filters_controller-e785deb4.js

js isms.online collected 2026-09-24 15:34:53 UTC 9,411 bytes, 313 lines download raw bytes

1import { Controller } from "@hotwired/stimulus"
2import { get } from "@rails/request.js"
3
4const TOKENINPUT_FIELDS = ["initiatives", "assigned_to_ids", "created_by_ids"]
5const SELECT_FIELDS = ["cluster_id", "task_type", "status", "due_status"]
6const HIDDEN_FIELDS = ["sort_cols", "direction"]
7const FILTER_FIELDS = [...TOKENINPUT_FIELDS, ...SELECT_FIELDS]
8const FILTERS_OFF = { status: "all_statuses" }
9
10export default class extends Controller {
11  static outlets = ["forms--tokeninput"]
12  static targets = [
13    "form",
14    "results",
15    "initiativesField",
16    "assignedToIdsField",
17    "createdByIdsField",
18    "clusterIdField",
19    "taskTypeField",
20    "statusField",
21    "dueStatusField",
22    "pageField",
23    "sortColsField",
24    "directionField"
25  ]
26
27  static values = {
28    currentUserId: Number,
29    initialFilters: Object,
30    storageKey: String
31  }
32
33  connect() {
34    this.isProgrammaticChange = false
35    this.initialTodosFetchTriggered = false
36    this.#fetchTodosWhenReady()
37  }
38
39  formsTokeninputOutletConnected() {
40    this.#fetchTodosWhenReady()
41  }
42
43  selectChanged(event) {
44    if (event.target === this.clusterIdFieldTarget) {
45      this.#clearFieldsOnClusterChange()
46    }
47
48    this.#saveFiltersAndReload()
49  }
50
51  tokeninputChanged() {
52    this.#saveFiltersAndReload()
53  }
54
55  clearSelect(event) {
56    const property = event.params.field
57    this.#resetSelect(this.#fieldTarget(property))
58
59    if (property === "cluster_id") {
60      this.#clearFieldsOnClusterChange()
61    }
62
63    this.#saveFiltersAndReload()
64  }
65
66  clearAllFilters() {
67    this.isProgrammaticChange = true
68
69    this.#tokeninputFields().forEach((field) => this.#tokeninputController(field).clear())
70    this.#selectFields().forEach((field) => this.#resetSelect(field))
71
72    this.isProgrammaticChange = false
73    this.#saveFiltersAndReload()
74  }
75
76  applySort({ sortCols, direction }) {
77    this.#setHiddenValue(this.sortColsFieldTarget, sortCols)
78    this.#setHiddenValue(this.directionFieldTarget, direction)
79    this.#saveFiltersAndReload()
80  }
81
82  #fetchTodosWhenReady() {
83    if (this.initialTodosFetchTriggered || !this.#tokeninputsReady()) return
84
85    this.initialTodosFetchTriggered = true
86    this.#updateFilters(this.#getFilters())
87    this.#reloadResults()
88  }
89
90  #getFilters() {
91    const initialFilters = this.#cleanSavedFilters(this.initialFiltersValue || {})
92    if (this.#linkedToDo()) return { ...this.#withoutFilterFields(initialFilters), ...FILTERS_OFF }
93
94    const rememberedFilters = this.#readSavedFilters()
95    return  {
96      ...(this.#hasSavedFilterSelections(initialFilters) || this.#hasSavedFilterSelections(rememberedFilters) ? {} : this.#defaultFilters()),
97      ...rememberedFilters,
98      ...initialFilters
99    }
100  }
101
102  #linkedToDo() {
103    return Boolean(window.task_dom_id)
104  }
105
106  #withoutFilterFields(filters) {
107    return Object.fromEntries(Object.entries(filters).filter(([field]) => !FILTER_FIELDS.includes(field)))
108  }
109
110  #saveFiltersAndReload() {
111    if (this.isProgrammaticChange) return
112
113    this.#writeSavedFilters(this.#currentFilters())
114    this.#reloadResults()
115  }
116
117  #updateFilters(filters) {
118    this.isProgrammaticChange = true
119
120    SELECT_FIELDS.forEach((property) => this.#setSelectValue(this.#fieldTarget(property), filters[property]))
121    TOKENINPUT_FIELDS.forEach((property) => this.#setTokeninputValues(this.#fieldTarget(property), filters[property] || []))
122    HIDDEN_FIELDS.forEach((property) => this.#setHiddenValue(this.#fieldTarget(property), filters[property]))
123
124    this.isProgrammaticChange = false
125  }
126
127  #defaultFilters() {
128    return { assigned_to_ids: [String(this.currentUserIdValue)] }
129  }
130
131  #currentFilters() {
132    return {
133      ...Object.fromEntries(SELECT_FIELDS.map((property) => [property, this.#getSelectValue(this.#fieldTarget(property))])),
134      ...Object.fromEntries(TOKENINPUT_FIELDS.map((property) => [property, this.#getTokeninputValues(this.#fieldTarget(property))])),
135      ...Object.fromEntries(HIDDEN_FIELDS.map((property) => [property, this.#getHiddenValue(this.#fieldTarget(property))]))
136    }
137  }
138
139  #getSelectValue(field) {
140    const value = field.value || null
141    return value && value !== this.#defaultValue(field) ? value : null
142  }
143
144  #getTokeninputValues(field) {
145    const controller = this.#tokeninputController(field)
146
147    const values = this.#cleanListValues(controller.valueArray)
148    return values.length > 0 ? values : null
149  }
150
151  #getHiddenValue(field) {
152    const value = field.value || null
153    return value && value !== this.#defaultValue(field) ? value : null
154  }
155
156  #setSelectValue(field, value) {
157    field.value = value || this.#defaultValue(field)
158    this.#updateSelectActiveState(field)
159  }
160
161  #resetSelect(field) {
162    this.#setSelectValue(field)
163  }
164
165  #setHiddenValue(field, value) {
166    field.value = value || this.#defaultValue(field)
167  }
168
169  #setTokeninputValues(field, values) {
170    const selectedValues = this.#cleanListValues(values || [])
171    const controller = this.#tokeninputController(field)
172    if (this.#listsMatch(controller.valueArray, selectedValues)) return
173
174    controller.clear()
175    selectedValues.forEach((value) => {
176      const option = this.#getOptionForToken(controller, value)
177      if (!option) return
178
179      const token = this.#tokenData(option)
180      controller.addToken(token.label, this.#serializedValue(token.value))
181    })
182  }
183
184  #getOptionForToken(controller, value) {
185    return controller.optionTargets.find((option) => {
186      const token = this.#tokenData(option)
187      return this.#serializedValue(token.value) === value
188    })
189  }
190
191  #tokenData(option) {
192    return JSON.parse(option.getAttribute("data-forms--tokeninput-token-param"))
193  }
194
195  #serializedValue(value) {
196    return typeof value === "string" ? value : JSON.stringify(value)
197  }
198
199  #tokeninputController(field) {
200    return this.formsTokeninputOutlets.find((controller) => controller.element === field)
201  }
202
203  #clearFieldsOnClusterChange() {
204    this.#tokeninputFields()
205      .filter((field) => field.dataset.clearOnClusterChange === "true")
206      .forEach((field) => this.#tokeninputController(field).clear())
207  }
208
209  #updateSelectActiveState(field) {
210    field.classList.toggle("field_active", field.value !== this.#defaultValue(field))
211  }
212
213  #defaultValue(field) {
214    return field.dataset.defaultValue || ""
215  }
216
217  #fieldTarget(property) {
218    return this[`${this.#fieldTargetName(property)}Target`]
219  }
220
221  #fieldTargetName(property) {
222    return `${this.#camelize(property)}Field`
223  }
224
225  #camelize(value) {
226    return value.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase())
227  }
228
229  #hasSavedFilterSelections(filters) {
230    return FILTER_FIELDS.some((field) => {
231      const value = filters[field]
232      return Array.isArray(value) ? value.length > 0 : Boolean(value)
233    })
234  }
235
236  #tokeninputsReady() {
237    return this.formsTokeninputOutlets.length === this.#tokeninputFields().length
238  }
239
240  #readSavedFilters() {
241    try {
242      const parsed = JSON.parse(localStorage.getItem(this.storageKeyValue) || "{}")
243      return this.#cleanSavedFilters(parsed)
244    } catch (_error) {
245      return {}
246    }
247  }
248
249  #writeSavedFilters(filters) {
250    const cleanedFilters = this.#cleanSavedFilters(filters)
251
252    if (Object.keys(cleanedFilters).length === 0) {
253      localStorage.removeItem(this.storageKeyValue)
254      return
255    }
256
257    localStorage.setItem(this.storageKeyValue, JSON.stringify(cleanedFilters))
258  }
259
260  #cleanSavedFilters(filters) {
261    const cleanedFilters = {}
262
263    if (filters.cluster_id) cleanedFilters.cluster_id = filters.cluster_id.toString()
264    if (filters.initiatives?.length) cleanedFilters.initiatives = this.#cleanListValues(filters.initiatives)
265    if (filters.assigned_to_ids?.length) cleanedFilters.assigned_to_ids = this.#cleanListValues(filters.assigned_to_ids)
266    if (filters.created_by_ids?.length) cleanedFilters.created_by_ids = this.#cleanListValues(filters.created_by_ids)
267    if (filters.task_type) cleanedFilters.task_type = filters.task_type.toString()
268    if (filters.status) cleanedFilters.status = filters.status.toString()
269    if (filters.due_status) cleanedFilters.due_status = filters.due_status.toString()
270    if (filters.sort_cols) cleanedFilters.sort_cols = filters.sort_cols.toString()
271    if (filters.direction) cleanedFilters.direction = filters.direction.toString()
272
273    return cleanedFilters
274  }
275
276  #cleanListValues(values) {
277    return [...new Set(values.map((value) => value.toString()))].sort()
278  }
279
280  #listsMatch(leftValues, rightValues) {
281    return new Set(leftValues.map((value) => value.toString()))
282      .symmetricDifference(new Set(rightValues.map((value) => value.toString())))
283      .size === 0
284  }
285
286  async #reloadResults() {
287    this.#resetPaginationValue()
288    PopupMessages.HoverInProgress.display("", { width: 40 })
289
290    try {
291      const query = new URLSearchParams(new FormData(this.formTarget))
292      const response = await get(`${this.formTarget.action}?${query.toString()}`, {
293        responseKind: "turbo-stream"
294      })
295
296      if (!response.ok) return
297    } finally {
298      PopupMessages.HoverInProgress.hide()
299    }
300  }
301
302  #resetPaginationValue() {
303    this.pageFieldTarget.value = "1"
304  }
305
306  #selectFields() {
307    return SELECT_FIELDS.map((property) =>
307 this.#fieldTarget(property)).filter(Boolean)
308  }
309
310  #tokeninputFields() {
311    return TOKENINPUT_FIELDS.map((property) => this.#fieldTarget(property)).filter(Boolean)
312  }
313}

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.