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.