Skip to content

docs: make the asynchronous create demo work on right click - #806

Merged
bbrala merged 2 commits into
masterfrom
docs/issue-735-async-create-right-click
Jul 29, 2026
Merged

docs: make the asynchronous create demo work on right click#806
bbrala merged 2 commits into
masterfrom
docs/issue-735-async-create-right-click

Conversation

@bbrala

@bbrala bbrala commented Jul 29, 2026

Copy link
Copy Markdown
Member

Closes #735

The problem

documentation/demo/async-create.md says "right click me", but it opened the menu from a mouseup handler. That is not the event you get when a user right clicks:

  • Chromium keeps its own context menu open over the page and never delivers the mouseup, so only the native menu appears and the plugin menu never opens.
  • Firefox does deliver it, but only after the native menu is already on screen, so you get both.

The demo was also asynchronous in name only. createSomeMenu() built the items inline and the setTimeout merely deferred the call to $this.contextMenu(position), so it did not actually demonstrate items that take a while to arrive.

The fix

Demo only, no plugin source changes.

  • Handle contextmenu on the trigger and preventDefault() it, so the browser's own menu stays away while we wait.
  • Move the items behind a fetchSomeMenu(done) helper whose setTimeout stands in for a server round trip. build() picks the result up off the trigger, which is the pattern the reporter described.
  • Guard the re-entrant trigger. $.fn.contextMenu() opens the menu by triggering a contextmenu event on the element, so the handler runs a second time. An asyncMenuBusy flag on the trigger makes that call a no-op, and it doubles as a guard against firing a second request while one is still in flight. It is cleared right after the open, so the next right click works normally.
  • Added prose above the demo explaining both of those points, since they are the non-obvious parts of doing this correctly.

Tests

test/specs/aync-create.js was renamed to test/specs/async-create.js (the old filename had a typo) and extended from one test to five:

  1. the menu is not there right after the right click, and only appears once the simulated fetch resolves, with 3 items
  2. the browser context menu is prevented (defaultPrevented on the real contextmenu event)
  3. the menu also opens from a contextmenu event that is never followed by a mouseup, which is the Chromium situation from the issue
  4. one right click produces exactly two jQuery contextmenu invocations and one menu, so the re-entrant event settles instead of recursing
  5. a second right click after closing the first still opens the menu, proving the guard resets

Tests 2 and 3 fail against the demo as it is on master and pass with this change. Verified green on jQuery 1.12.4, 2.2.4, 3.7.1 and 4.0.0.

Note on the plugin source

No source change seemed warranted, the correct pattern is expressible today. One rough edge worth recording though: $.fn.contextMenu({x: undefined, y: undefined}) does not match the x/y branch, falls through to $.isPlainObject() and ends up in $.contextMenu('create', ...), which throws No selector specified. That is a confusing failure mode for what looks like a position argument, but it is out of scope here.

bbrala added 2 commits July 29, 2026 16:31
The demo opened its menu from a `mouseup` handler, which is not the event
you get when the user right clicks. Chromium keeps its own context menu
open over the page and never delivers the `mouseup`, so only the native
menu showed up. Firefox delivers it, but only after the native menu is
already on screen.

Bind `contextmenu` instead and call `preventDefault()` on it so the
browser menu stays away. The items are now handed to a callback from a
`setTimeout`, so the demo really is asynchronous rather than building the
items inline and merely deferring the open.

Because `$.fn.contextMenu()` opens the menu by triggering a `contextmenu`
event on the trigger, the handler runs again. A flag on the trigger makes
that re-entrant call a no-op, and it doubles as a guard against starting
a second request while one is still in flight.

The spec file was renamed from `aync-create.js` to `async-create.js` and
extended to cover the right click flow: the menu only appears after the
delay, the native menu is prevented, the menu also opens from a
`contextmenu` event that is never followed by a `mouseup`, the re-entrant
event settles instead of recursing, and a second right click still works.
Asserting the menu is still hidden right after the right click passes on
the first poll, but it would fail on a machine slow enough to let the 1s
delay elapse first. Measure the time from the click to the menu appearing
and only bound it from below instead.
@bbrala

bbrala commented Jul 29, 2026

Copy link
Copy Markdown
Member Author

👍

@bbrala
bbrala merged commit add6fc2 into master Jul 29, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Create Context Menu (asynchronous) demo doesn't work with right-click

1 participant