PageSourceSearch

https://healthylifenetwork.com.au/assets/controllers/ads-wizard-custom-audiences_controller-VpN6NGY.js

js healthylifenetwork.com.au collected 2026-09-25 03:35:57 UTC 4,378 bytes, 116 lines download raw bytes

1import { Controller } from '@hotwired/stimulus';
2
3/**
4 * ads-wizard-custom-audiences — load Meta custom/lookalike audiences from the
5 * wizard API and let the user pick them via toggle chips.
6 *
7 * Values:
8 *   url — GET endpoint that returns [{id, name, subtype, approximate_count}]
9 *
10 * Targets:
11 *   status   — small status text span
12 *   list     — container for the audience toggle chips
13 *   selected — holds <input type=hidden name="custom_audience_ids[]"> per chosen id
14 *   chips    — shows chosen audience label pills (mirrors selected inputs)
15 */
16export default class extends Controller {
17    static values  = { url: String };
18    static targets = ['status', 'list', 'selected', 'chips'];
19
20    #chosen = new Set(); // ids already selected before load
21
22    connect() {
23        // Seed from existing hidden inputs so the set stays consistent on re-load.
24        this.selectedTarget.querySelectorAll('input[type=hidden]')
25            .forEach(inp => this.#chosen.add(inp.value));
26    }
27
28    async load() {
29        if (!this.urlValue) { return; }
30        this.statusTarget.textContent = 'Loading…';
31        try {
32            const res = await fetch(this.urlValue, { headers: { 'X-Requested-With': 'XMLHttpRequest' } });
33            if (!res.ok) { throw new Error('HTTP ' + res.status); }
34            const rows = await res.json();
35            this.#render(rows);
36            this.statusTarget.textContent = rows.length ? '' : 'No custom audiences found on this account.';
37        } catch (e) {
38            this.statusTarget.textContent = 'Could not load audiences.';
39        }
40    }
41
42    toggle(event) {
43        const btn = event.currentTarget;
44        const id   = btn.dataset.id;
45        const name = btn.dataset.name;
46        if (this.#chosen.has(id)) {
47            this.#chosen.delete(id);
48            btn.classList.remove('btn-secondary');
49            btn.classList.add('btn-outline');
50            this.#removeInput(id);
51            this.#removeChip(id);
52        } else {
53            this.#chosen.add(id);
54            btn.classList.remove('btn-outline');
55            btn.classList.add('btn-secondary');
56            this.#addInput(id);
57            this.#addChip(id, name);
58        }
59    }
60
61    remove(event) {
62        const chip = event.currentTarget;
63        const id = chip.dataset.id;
64        this.#chosen.delete(id);
65        chip.remove();
66        this.#removeInput(id);
67        // Un-highlight the toggle button if the list is visible.
68        this.listTarget.querySelectorAll(`[data-id="${id}"]`).forEach(btn => {
69            btn.classList.remove('btn-secondary');
70            btn.classList.add('btn-outline');
71        });
72    }
73
74    #render(rows) {
75        this.listTarget.innerHTML = '';
76        rows.forEach(row => {
77            const btn = document.createElement('button');
78            btn.type = 'button';
79            btn.className = 'btn btn-xs ' + (this.#chosen.has(row.id) ? 'btn-secondary' : 'btn-outline') + ' gap-1';
80            btn.dataset.id   = row.id;
81            btn.dataset.name = row.name;
82            btn.dataset.action = 'click->ads-wizard-custom-audiences#toggle';
83            const label = row.name + (row.subtype ? ' (' + row.subtype + ')' : '');
84            btn.title = row.approximate_count > 0 ? '~' + row.approximate_count.toLocaleString() + ' people' : '';
85            btn.textContent = label;
86            this.listTarget.appendChild(btn);
87        });
88        this.listTarget.classList.remove('hidden');
89    }
90
91    #addInput(id) {
92        const inp = document.createElement('input');
93        inp.type  = 'hidden';
94        inp.name  = 'custom_audience_ids[]';
95        inp.value = id;
96        this.selectedTarget.appendChild(inp);
97    }
98
99    #removeInput(id) {
100        this.selectedTarget.querySelectorAll(`input[value="${id}"]`).forEach(n => n.remove());
101    }
102
103    #addChip(id, name) {
104        const span = document.createElement('span');
105        span.className   = 'badge badge-secondary badge-sm gap-1 cursor-pointer';
106        span.dataset.id  = id;
107        span.dataset.action = 'click->ads-wizard-custom-audiences#remove';
108        span.title = 'Remove audience ' + name;
109        span.innerHTML   = `${name} <span aria-hidden="true">×</span>`;
110        this.chipsTarget.appendChild(span);
111    }
112
113    #removeChip(id) {
114        this.chipsTarget.querySelectorAll(`[data-id="${id}"]`).forEach(n => n.remove());
115    }
116}

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.