# Gift chooser JavaScript API

> Open the AndOr gift chooser dialog from your own code with window.AndOrQuickview.open(options).

Source: https://backtrackhq.com/docs/widgets/gift-chooser/javascript-api/

`window.AndOrQuickview.open(options)` opens the gift chooser dialog and
returns its `<andor-quickview>` element. The cart gift slot is the only
caller in AndOr today. You can call it from your own code to reuse the
same two-step product and variant picker.

> **Caution:** The script loads only when needed. The gift slot loads
> it the first time it needs the chooser, so
> `window.AndOrQuickview` may not exist yet. Check for it before you call
> it.

## open(options)

```js
const dialog = window.AndOrQuickview.open(options);
```

The dialog is a native `<dialog>` opened with `showModal()`. It draws in
the browser's top layer, above the page and the theme's cart drawer. It
traps focus, makes the page behind it inert and returns focus to the
button that opened it.

### Options

| Option | Type | What it does |
|---|---|---|
| `into` | element | where the dialog is appended. Keep it inside the cart drawer when you open it from the drawer |
| `title` | string | the dialog's heading |
| `slots` | number | how many picks. Default `1` |
| `products` | array | the products to offer (shape below) |
| `price(product, variant)` | function | returns `[was, now]` text, or `null` |
| `strings` | object | the dialog's words (keys below) |
| `onSubmit(picks)` | function | gets the picks. Resolve to close, reject to show the error |
| `onClose()` | function | runs after the dialog closes, added or not |
| `buttonClass` | string | extra classes for the Add button |

#### products

Each product is `{ id, title, image, options, variants, swatches }`:

- `options` is `[{ name, values }]`.
- Each variant is `{ id, title, price, available, options, image }`.
- A variant that is not `available` may add `reason`. The chooser says
  the reason instead of "Sold out".
- `swatches` is optional. It maps an option's position (1 based, as a
  string) to a list of `[name, swatch]` pairs. A swatch is a CSS color
  or an image URL. Example: `{ "1": [["Red", "#c0392b"], ["Blue",
  "#1f4e9c"]] }`. An option is drawn as swatches only when every value
  has one. Without a listed swatch, an option named like a color tries
  the value's name as a CSS color, else it is drawn as pills.

#### price

`price(product, variant)` returns two strings, `[was, now]`, for example
`["$24.95", "Free"]`, or `null`. `variant` is `null` on a product card in
step 1. Return `null` when you are not sure of the price or of "Free":
the chooser then shows no price at all.

#### strings

Keys: `close`, `back`, `add`, `addMany`, `slot`, `products`,
`optionsTitle`, `options`, `chosen`, `soldOut`, `clear`, `error`. In
`addMany`, `slot`, `chosen` and `clear`, `@` and `#` are filled in. For
example `"Add @ gifts"` becomes "Add 2 gifts", and `"Gift @ of #"`
becomes "Gift 1 of 2".

The chooser has no built-in words. Pass every key you need.

#### onSubmit

`onSubmit(picks)` gets `[{ product, variant }]`, one entry per filled
slot. Return a promise. Resolve it to close the dialog. Reject it to show
the `error` string and keep the dialog open.

## Example

```js
function openGiftPicker(button) {
  if (!window.AndOrQuickview) return; // not loaded on this page yet
  window.AndOrQuickview.open({
    into: button.closest("cart-drawer") || document.body,
    title: "Choose your free gift",
    slots: 1,
    products: [
      {
        id: 111,
        title: "Ski wax",
        image: "https://cdn.shopify.com/.../wax.jpg",
        options: [{ name: "Scent", values: ["Pine", "Citrus"] }],
        swatches: {}, // no swatches: Scent is drawn as pills
        variants: [
          { id: 1, title: "Pine", price: 2495, available: true,
            options: ["Pine"], image: null },
          { id: 2, title: "Citrus", price: 2495, available: false,
            options: ["Citrus"], image: null },
        ],
      },
    ],
    price: () => null, // not sure it is free: show no price
    strings: {
      close: "Close", back: "Back", add: "Add gift",
      addMany: "Add @ gifts", slot: "Gift @ of #",
      products: "Choose a product", optionsTitle: "Choose your options",
      options: "Choose options", chosen: "@ of # gifts chosen",
      soldOut: "Sold out", clear: "Remove @",
      error: "We could not add your gift. Please try again.",
    },
    onSubmit: (picks) =>
      fetch("/cart/add.js", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          items: picks.map(({ variant }) => ({ id: variant.id, quantity: 1 })),
        }),
      }).then((response) => {
        if (!response.ok) throw new Error("add failed");
      }),
    onClose: () => button.focus(),
  });
}
```

## Behavior to know

- **Escape** closes the chooser and leaves Dawn's cart drawer open. A
  `cancel` event is stopped too.
- Everything is drawn with `textContent`. No product data becomes HTML.
- The Add button also gets the class list of your theme's checkout
  button (`[name="checkout"]`) when the page has one, so it looks like
  your primary button on Dawn and Horizon.
- The chooser adds its own stylesheet the first time the script runs. A
  first open with the stylesheet still loading keeps the dialog
  invisible until it loads, for 800 ms at most.
