---
product: "AG Studio"
title: "Popups"
description: "Open a popup inside Studio that shows a component of your own, for example a detail view launched from a button in your application."
framework: vue
version: "3.1.0"
related:
    - title: "Context Menu"
      url: "https://www.ag-grid.com/studio/vue/context-menu/"
    - title: "Drill Through"
      url: "https://www.ag-grid.com/studio/vue/drill-through/"
    - title: "Undo & Redo"
      url: "https://www.ag-grid.com/studio/vue/undo-redo/"
    - title: "Exporting"
      url: "https://www.ag-grid.com/studio/vue/exporting/"
llms: "https://www.ag-grid.com/studio/llms.txt"
---

# Popups

The `createPopup` API opens a popup inside Studio that shows a component of your own. Studio owns the popup and closes it when Studio is destroyed.

Use `createPopup` when the content belongs with the report: a detail view of something in Studio, or a short form about it. The popup is rendered inside Studio, can be made movable and resizable, and does not block the report, so the user can keep working in Studio while it is open and your application can update it as they do. Use your application's own modal instead when the content must block the rest of the page or must stay open after Studio is destroyed.

#### Popup

```ts
import {
  createApp,
  defineComponent,
  onBeforeMount,
  ref,
  shallowRef,
} from "vue";
import { AgStudio } from "ag-studio-vue3";
import {
  AgDataEngine,
  AgDataSourcesDefinition,
  AgPopupInstance,
  AgReportHistory,
  AgReportState,
  AgStudioApi,
  AgStudioApiReadyEvent,
  AgStudioMode,
  AgStudioProperties,
  enableStudioDevValidations,
} from "ag-studio";
import PopupContent from "./popupContentVue.ts";
import { PopupContentParams, SportDetail } from "./interfaces.ts";

if (process.env.NODE_ENV !== "production") {
  // Enable extended validations only for development
  enableStudioDevValidations();
}

let medalRows: any[] = [];

let popup: AgPopupInstance<PopupContentParams> | undefined;

let shownSport: string | undefined;

// The popup computes its detail view from the same rows the report is given.
function keepMedalRows(rows: any[]): any[] {
  medalRows = rows;
  // A popup opened while the rows were loading has an empty detail view, so fill it in now.
  if (popup) {
    popup.refresh(buildParams(shownSport, popup.closePopup));
  }
  return rows;
}

function getSportDetail(sport: string): SportDetail {
  const detail: SportDetail = {
    sport,
    gold: 0,
    silver: 0,
    bronze: 0,
    total: 0,
    athletes: 0,
    topCountries: [],
  };
  const athletes = new Set<string>();
  const countryTotals = new Map<string, number>();
  for (const row of medalRows) {
    if (row.sport !== sport) {
      continue;
    }
    detail.gold += row.gold;
    detail.silver += row.silver;
    detail.bronze += row.bronze;
    detail.total += row.total;
    athletes.add(row.athlete);
    countryTotals.set(
      row.country,
      (countryTotals.get(row.country) ?? 0) + row.total,
    );
  }
  detail.athletes = athletes.size;
  detail.topCountries = [...countryTotals]
    .map(([country, total]) => ({ country, total }))
    .sort((a, b) => b.total - a.total)
    .slice(0, 3);
  return detail;
}

function getTopSport(): string {
  const sportTotals = new Map<string, number>();
  for (const row of medalRows) {
    sportTotals.set(row.sport, (sportTotals.get(row.sport) ?? 0) + row.total);
  }
  return [...sportTotals].sort((a, b) => b[1] - a[1])[0]?.[0] ?? "";
}

// A row clicked in the Medals by Sport grid is published as that widget's cross-filter selection.
// Ctrl/Cmd-click adds to the selection, so the most recently added sport is the last combination.
function getSelectedSport(state: AgReportState): string | undefined {
  const page = state.pages.find((p) => p.id === state.selectedPageId);
  const selection = page?.crossFilter?.values?.["medals-by-sport"]?.find(
    (s) => s.type === "multiValue",
  );
  if (selection?.type !== "multiValue") {
    return undefined;
  }
  const combination = selection.values[selection.values.length - 1] ?? [];
  return combination.find((pair) => pair.field.id === "medals.sport")?.value;
}

function buildParams(
  sport: string | undefined,
  closePopup: () => void,
): PopupContentParams {
  return {
    detail: getSportDetail(sport ?? getTopSport()),
    hint: sport
      ? undefined
      : "Select a sport in Medals by Sport to see its details.",
    closePopup,
  };
}

function refreshPopup(state: AgReportState) {
  if (!popup) {
    return;
  }
  const sport = getSelectedSport(state);
  if (sport === shownSport) {
    return;
  }
  shownSport = sport;
  popup.refresh(buildParams(sport, popup.closePopup));
}

const VueExample = defineComponent({
  template: `
        <div style="height: 100%">
                <div style="display: flex; flex-direction: column; height: 100%">
      <div class="example-controls">
        <div class="controls-row">
          <button v-on:click="openPopup()">Open Popup</button>
        </div>
      </div>
      <ag-studio
        style="width: 100%; height: 100%;"
        class="my-studio-container"
        @api-ready="onApiReady"
        :initialState="initialState"
        :mode="mode"
        :data="data"
        @state-updated="onStateUpdated"></ag-studio>
      </div>
        </div>
    `,
  components: {
    "ag-studio": AgStudio,
    PopupContent,
  },
  setup(props) {
    const studioApi = shallowRef<AgStudioApi | null>(null);
    const initialState = ref<AgReportState | AgReportHistory>({
      pages: [
        {
          id: "summary",
          widgets: {
            "medals-by-sport": {
              type: "grid",
              dataMapping: {
                cols: [
                  { id: "medals.sport" },
                  { id: "medals.gold", aggregation: "sum" },
                  { id: "medals.silver", aggregation: "sum" },
                  { id: "medals.bronze", aggregation: "sum" },
                  { id: "medals.total", aggregation: "sum" },
                ],
              },
              format: {
                title: {
                  enabled: true,
                  text: "Medals by Sport",
                },
              },
              sort: [
                {
                  field: { id: "medals.total", aggregation: "sum" },
                  direction: "desc",
                },
              ],
            },
            "medals-by-country": {
              type: "bar-chart-grouped",
              dataMapping: {
                categoryKey: [{ id: "medals.country" }],
                valueKey: [{ id: "medals.total", aggregation: "sum" }],
              },
              format: {
                title: {
                  enabled: true,
                  text: "Medals by Country",
                },
              },
              sort: [
                {
                  field: { id: "medals.total", aggregation: "sum" },
                  direction: "desc",
                },
              ],
            },
          },
          widgetLayout: {
            "medals-by-sport": { xTrack: 0, yTrack: 0, xSpan: 24, ySpan: 18 },
            "medals-by-country": {
              xTrack: 0,
              yTrack: 18,
              xSpan: 24,
              ySpan: 14,
            },
          },
          filter: {
            page: [],
            widget: {
              "medals-by-country": [
                {
                  field: { id: "medals.total" },
                  view: { expanded: true, viewTypeId: "rank" },
                  model: { operator: "rank", value: [10, null] },
                },
              ],
            },
          },
        },
      ],
      selectedPageId: "summary",
      panels: {
        filters: {
          collapsed: true,
        },
        data: {
          collapsed: true,
        },
        edit: {
          collapsed: true,
        },
      },
    });
    const mode = ref<AgStudioMode>("edit");
    const data = ref<AgDataSourcesDefinition | AgDataEngine>(null);

    function onStateUpdated({ state }) {
      refreshPopup(state);
    }
    function openPopup() {
      if (popup) {
        return;
      }
      shownSport = getSelectedSport(studioApi.value.getState());
      popup = studioApi.value.createPopup<PopupContentParams>({
        comp: PopupContent,
        title: "Sport Details",
        movable: true,
        resizable: true,
        width: 320,
        height: 360,
        compParams: ({ closePopup }) => buildParams(shownSport, closePopup),
        closedCallback: () => {
          popup = undefined;
        },
      });
    }
    const onApiReady = (params: AgStudioApiReadyEvent) => {
      studioApi.value = params.api;

      const toStudioData = (data) => ({
        sources: [{ id: "medals", data: keepMedalRows(data) }],
      });

      fetch("https://www.ag-grid.com/studio/example-assets/olympic-winners.json")
        .then((resp) => resp.json())
        .then((respData) => (data.value = toStudioData(respData)));
    };

    return {
      studioApi,
      initialState,
      mode,
      data,
      onApiReady,
      onStateUpdated,
      openPopup,
    };
  },
});

const app = createApp(VueExample);
app.mount("#app");
```

[Live example: Popup](https://www.ag-grid.com/studio/examples/popups/popup-example/vue3/)

The example above opens a Sport Details popup from a button outside Studio. The popup shows the medal counts, number of athletes and top countries for one sport. Click a row in Medals by Sport and the open popup updates to show that sport. Until a sport is selected, or when the selection is cleared, the popup shows the sport with the most medals. The popup's component has its own Close button.

## Opening a Popup

Call `createPopup` with the component to show and the params to pass to it:

```js
api.createPopup({
    comp: PopupContent,
    compParams: { detail: getSportDetail('Swimming') },
    title: 'Sport Details',
});
```

`comp` is a Vue component. It receives `compParams` as `params`.

Component names registered on the `components` property cannot be used as `comp`.

The popup is rendered inside Studio, or inside the element set on the [`popupParent`](https://www.ag-grid.com/studio/vue/studio-properties/) property.

## Popup Options

- `title` sets the text shown in the title bar.
- `movable` lets the user drag the popup by its title bar.
- `resizable` lets the user resize the popup by dragging its edges and corners.
- `maximizable` adds a title bar button that expands the popup to fill Studio.

`movable`, `resizable` and `maximizable` are off by default.

The popup opens at 600 by 400 pixels and does not grow to fit its content. Set `width` and `height` when your component needs a different size. When the popup is resizable, `minWidth` and `minHeight` set how small the user can make it.

## Closing the Popup

The popup stays open when the user clicks outside it, and the rest of the page stays interactive. It closes when:

- The user presses Escape while focus is inside the popup.
- The user clicks the close button in the title bar.
- `closePopup` is called, either on the instance returned by `createPopup` or from the component.
- Studio is destroyed.

To let the component close the popup, for example from its own button, supply `compParams` as a callback. The callback receives a `closePopup` function, which you can pass on to the component:

```js
api.createPopup({
    comp: PopupContent,
    compParams: ({ closePopup }) => ({ detail: getSportDetail('Swimming'), closePopup }),
    title: 'Sport Details',
});
```

`closedCallback` runs once each time the popup closes, however it was closed. It receives the keyboard event when Escape closed the popup, and `undefined` otherwise. The example uses it to forget the closed instance, so the next click on Open Popup opens a new one.

Several popups can be open at the same time. Each one stays open until it is closed.

## Updating the Popup

`createPopup` returns an instance with two methods:

- `refresh(params)` passes new params to the component.
- `closePopup()` closes the popup.

To receive the new params, add a `refresh(params)` method to the component. Without it, `refresh` has no effect. `params` is a prop, so render from data that `refresh` updates rather than assigning to `params`.

The example keeps the instance and refreshes the popup from `onStateUpdated` when the sport selected in Medals by Sport changes. `refresh` takes the params themselves, not a callback. If the component needs `closePopup`, pass the instance's `closePopup` in the new params:

```js
const popup = api.createPopup({
    comp: PopupContent,
    compParams: ({ closePopup }) => ({ detail: getSportDetail('Swimming'), closePopup }),
    title: 'Sport Details',
});

popup.refresh({ detail: getSportDetail('Athletics'), closePopup: popup.closePopup });
```

## API Reference

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `createPopup` | `Function` |  | Open a popup showing a component of your own. The popup is rendered inside Studio, or inside the element given by the `popupParent` property, and Studio owns it: the popup closes and your component is destroyed when Studio is destroyed. The popup stays open until something closes it: clicking outside it leaves it open, nothing is dimmed, and the rest of the page stays interactive. The Escape key and the close button in its title bar close it. To close it from inside your component, supply `compParams` as a callback and call the `closePopup()` function it receives. `closedCallback` runs once each time the popup closes, whichever of those closed it. The popup is sized as it opens and does not grow to fit its content, so set `width` and `height` when your component needs more or less room than the default 600 by 400 pixels. Several popups can be open at once, each staying open until it is closed. |

Properties available on the `AgCreatePopupParams&lt;TParams&gt;` interface.

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `comp` | `any` |  | The component shown inside the popup. Either a TypeScript class implementing `AgTypeScriptComponent`, or a component of the framework you are using (React, Angular or Vue). Component names registered on the `components` property are not supported here. |
| `compParams` | `TParams \| ((context: AgPopupCompParamsContext) => TParams)` |  | The parameters passed to the component when it is created. Either the parameters themselves, or a callback that returns them. The callback is given a `closePopup()` function, so that the component can close the popup itself, for example from its own Close button. |
| `title` | `string` |  | The text shown in the popup title bar. |
| `movable` | `boolean` | `false` | Whether the user can move the popup by dragging its title bar. |
| `maximizable` | `boolean` | `false` | Whether the title bar offers a button that expands the popup to fill Studio. |
| `resizable` | `boolean` | `false` | Whether the user can resize the popup by dragging its edges and corners. |
| `width` | `number` | `600` | The width of the popup in pixels when it opens. The popup is sized as it opens and does not grow to fit its content, so set this when your component needs more or less room than the default. The user can still change the size afterwards if `resizable` or `maximizable` is enabled. |
| `height` | `number` | `400` | The height of the popup in pixels when it opens. The popup is sized as it opens and does not grow to fit its content, so set this when your component needs more or less room than the default. The user can still change the size afterwards if `resizable` or `maximizable` is enabled. |
| `minWidth` | `number` | `250` | The smallest width in pixels the user can resize the popup down to. The default never exceeds `width`, so a popup opened narrower than 250 pixels is not forced back up to 250 the first time it is resized. |
| `minHeight` | `number` | `250` | The smallest height in pixels the user can resize the popup down to. The default never exceeds `height`, so a popup opened shorter than 250 pixels is not forced back up to 250 the first time it is resized. |
| `closedCallback` | `Function` |  | Called once when the popup closes, whatever closed it. The argument is the keyboard event when the Escape key closed the popup, and `undefined` otherwise: the title bar close button, `closePopup()` and Studio being destroyed all report no event. |
