javascriptgoogle-picker

How do I attach a callback to the Google Picker in a Chakra UI Modal?


I'm trying to integrate the Google Picker in my application, and the UI is showing up perfectly as expected, but I can't seem to get it to call any callback I've tried attaching to it.

There are 2 ways of using the Picker, and I'd prefer option 1, but I'd be okay with either

Option 1: Build the Picker as a URL to render in a custom modal

I'm building my application in React (specifically Chakra), and I'd prefer to use Chakra's <Popover> element to help the Picker blend in with the rest of the page.

Using this route, this is the relevant code I have so far:

function getPicker(callback) {
  const view = new google.picker.DocsView(google.picker.ViewId.FOLDERS);
  view.setSelectFolderEnabled(true);

  return (
    new google.picker.PickerBuilder()
      .addView(view)
      .setSelectableMimeTypes("application/vnd.google-apps.folder")
      .setTitle("")
      .setCallback(callback)
      .enableFeature(google.picker.Feature.NAV_HIDDEN)
      .setOAuthToken(getAccessToken() /* defined elsewhere - assume this works */)
      .setDeveloperKey(process.env.API_KEY)
      .setOrigin(window.location.host)
      .toUri();
  );
}

// This is an event listener 
function showPicker() {
  function handleFolderSelected(folder) {
    const { Response, Action, Document } = google.picker;

    if (folder[Response.ACTION] === Action.PICKED) {
      const targetFolder = folder[Response.DOCUMENTS][0][Document.ID];
      // do stuff
    }

    setIsFolderPickerOpen(false); // closes the <Popover>
  }

  const pickerUrl = getPicker(handleFolderSelected);

  // Opens the <Popover> and sets the source of an iframe inside to the url
  setPickerUrl(pickerUrl);
  setIsFolderPickerOpen(true);
}

Option 2: Let the Picker build its own iframe

If necessary, although I don't think it's as visually appealing, I'm okay with going this route. This is the code I tried for this option so far (which is mostly the same, but not quite)

function getPicker(callback) {
  const view = new google.picker.DocsView(google.picker.ViewId.FOLDERS);
  view.setSelectFolderEnabled(true);

  return (
    new google.picker.PickerBuilder()
      .addView(view)
      .setSelectableMimeTypes("application/vnd.google-apps.folder")
      .setTitle("")
      .setCallback(callback)
      .enableFeature(google.picker.Feature.NAV_HIDDEN)
      .setOAuthToken(getAccessToken() /* defined elsewhere - assume this works */)
      .setDeveloperKey(process.env.API_KEY)
      .setOrigin(window.location.host)
      .build();
  );
}

// This is an event listener 
function showPicker() {
  function handleFolderSelected(folder) {
    const { Response, Action, Document } = google.picker;

    if (folder[Response.ACTION] === Action.PICKED) {
      const targetFolder = folder[Response.DOCUMENTS][0][Document.ID];
      // do stuff
    }
  }

  const picker = getPicker(handleFolderSelected);
  picker.setVisible(true);
}

Other information

I've tried searching Stack for this problem, but I haven't found anything specifically for this problem, so I don't think it's a duplicate. The closest I found was this one where a comment in an unaccepted answer suggests perhaps it's impossible to attach a callback via Option 1, but I can't be certain

I also noticed a couple times there was a message from the Picker being posted to the parent window when I expected the callback to be called, so I tried to catch it, but it wasn't being fired consistently enough.

Any help on this would be greatly appreciated!


Solution

  • The problem, as it turns out, was not Google Picker (thank you to iansedano for pointing me to a working example to work off of!)

    The problem is that, for accessibility reasons, Chakra UI traps focus in modals. This is ordinarily a good thing, but due to how the Picker works, every time you try to click the "pick file" button, Chakra steals the focus back to the modal's close button before the Picker can handle the click.

    The solution is to use the following property to "untrap" the focus. Note: I recommend doing this only temporarily while the Picker is open. In all other cases, focus trapping is a good thing.

    import { Modal } from "@chakra-ui/react"
    
    function Component() {
      return (
        <Modal trapFocus={false}>
          ...
        </Modal>
      );
    }