The Wayback Machine - https://web.archive.org/web/20211002014344/https://github.com/swagger-api/swagger-ui/issues/3958
Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DeepLinking onClick does not scroll or open Operation. #3958

Open
travispamaral opened this issue Nov 28, 2017 · 20 comments
Open

DeepLinking onClick does not scroll or open Operation. #3958

travispamaral opened this issue Nov 28, 2017 · 20 comments

Comments

@travispamaral
Copy link

@travispamaral travispamaral commented Nov 28, 2017

Hi there! I see that an issue #2884 deeplinking was integrated to 3.x however if I have a hyperlink with a hash to the operationID nothing happens on click. If I reload the page the UI scrolls to the open panel as expected. Is there something I am missing here? My url looks as follows and I am using the 3.0 dist repo installed via npm.

Again when clicked the URL address is updated but nothing happens until the page is refreshed manually. Any suggestions?
<a href="#/pet/deletePet">Deletes a pet</a>

@shockey
Copy link
Contributor

@shockey shockey commented Nov 29, 2017

Hey @travispamaral!

Unfortunately, this is intended behavior. The deep linking feature runs once, on page load - we don't (currently) have a router that's keeping an eye on the URL hash and mediating layout state changes.

This was mostly done for compatibility with our previous version, so we'd be happy to accept a PR for this!

Switching this over to a feature enhancement ticket.

@shockey
Copy link
Contributor

@shockey shockey commented Nov 29, 2017

Quoting myself from #3963, since it's relevant here:

Currently, the deep linking feature isn't suitable for linking dynamically to operations as you are in your example - see #3958 for discussion on this. It's mostly useful as a way to share links to a particular operation to colleagues, or get back to what you were looking at when reopening a tab.

I'm going to close this, because part of implementing #3958 will involve addressing the target="_blank" - but I'd like to highlight that you filing this did help out, I hadn't thought of this particular use case 😄

@travispamaral
Copy link
Author

@travispamaral travispamaral commented Nov 29, 2017

@shockey got it! Thanks for the info! Is this feature in the road map?

@shockey
Copy link
Contributor

@shockey shockey commented Nov 30, 2017

@travispamaral, unfortunately I have no ETA for this 😞 we don't keep a formal roadmap at the moment

@sal-pal
Copy link

@sal-pal sal-pal commented Dec 8, 2017

Hey guys!

I wanna take this ticket, but I'm running into a problem: I don't know where's the code responsible for expanding and scrolling to an operation in response to clicking a deeplink.

I know the expand and scroll behavior is implemented via updateResolved() in deep-link/spec-wrap-actions.js, but in which file is this function used to respond to the clicking of a deeplink?

@webron
Copy link
Contributor

@webron webron commented Dec 8, 2017

Thanks @srpalomino! I'm sure @shockey would love to point you in the right direction. 😄

@shockey
Copy link
Contributor

@shockey shockey commented Dec 9, 2017

Hi @srpalomino! Most of the action is here: https://github.com/swagger-api/swagger-ui/tree/master/src/core/plugins/deep-linking

The layout action wrappers listen to layout state changes and set the URL hash when it changes, and the spec action wrappers trigger the scrolling when the spec we're displaying is loaded or changes.

@sal-pal
Copy link

@sal-pal sal-pal commented Dec 12, 2017

@shockey Thanks for the info!

Unfortunately I'm still a little bit confused. In what file/files does:

  • layout action wrappers get invoked in response to a layout state change.
  • spec action wrappers get invoked in response to the spec being displayed changing or loading.

I appreciate all the help!!!

@shockey
Copy link
Contributor

@shockey shockey commented Dec 15, 2017

@srpalomino - most of the wiring comes from the plugin system. To start, I'd suggest reading this and this in our documentation, as it provides some context. Let me step back a bit...

The deep linking feature handles two pieces of work: updating the URL hash when tags/operations are expanded/collapsed and reading the URL hash when the API definition is rendered after page load.

Updating the URL hash

The layout plugin governs the piece of application state that controls what tags and operations are expanded at any given time. One action in particular, show (action and reducer), is used to toggle the state of a particular tag or operation.

The deepLinking plugin wants to know when something is expanded or collapsed, because when something is expanded we want to update the URL hash to link to that thing, and when something is collapsed, we want to clear out the URL hash.

To this end, the deepLinking plugin uses the Wrap-Action plugin interface in order to listen in on the layout plugin's show action. This behavior is defined here and provided as part of the deepLinking plugin here.

At runtime, Swagger-UI's plugin system binds this Wrap-Action on top of the layout plugin's show action. The code for this is here, beware that this code is pretty low-level. Looking at the Wrap-Actions tests might be more productive, if you don't quite get the concept.

When the show layout action is called in response to a user click (for example here for a tag and here for an operation), the deepLinking plugin wrap-action is called instead of the actual layout show action, and it is passed a reference to the original action so it can still be executed here. This achieves our goal of knowing when the expansion state changes so we can update the URL hash accordingly, since we can peek into the data being passed to the show action.

Parsing the hash when the application starts

In a similar fashion to the above, we're going to wrap another plugin's action in order to know what's going on.

One thing to note is that we only do this once. We create a hasHashBeenParsed variable and check for it here. Once this is set after the first execution of the Wrap-Action, we don't parse the hash in later calls. This is important in order to prevent unwieldy behavior in contexts where the API definition's content may change, like when you load a new definition from the Topbar or edit your definition in Swagger-Editor.

We're wrapping the spec plugin's updateResolved action (action and reducer). In order to know why we chose updateResolved, we need to look at how the spec plugin wraps its own actions....

The spec actions updateSpec, updateJsonSpec, and updateResolved form a chain. First, we receive a raw string of YAML or JSON, usually from a HTTP request, and pass that to updateSpec here. In that process, the updateSpec Wrap-Action parses that string into a JS object and calls updateJsonSpec with that result. That, in turn, triggers the updateJsonSpec Wrap-Action to pass that JS object to Swagger-Client's resolver, which replaces all the $ref values in a definition with the values they refer to. Finally, that result is passed to updateResolved.

Once updateResolved is called, we're ready to display the API definition on the screen. So, in our deepLinking Wrap-Action for updateResolved, we call the original action first so the application state is updated and the UI is rendered. Now, everything is in the DOM... so we can scroll to what we want!

Operations and tags have HTML element IDs set on them (here and here), so we find the DOM element that matches here and use the zen scroll library to scroll the page down to it.


Wow, this is a bit longer than I thought it would be 😄 I tried to be as verbose as was useful to be, since there's a lot of moving parts that you should be aware of.

I think the best way forward is to modify the URL hash parsing code to also use WindowEventHandlers.onhashchange in order to trigger itself when the hash content changes. The main challenge there, I think, will be able to identify when the hash updater code is the source of the change vs when user intervention has caused it.

I hope this helped! You have my email, so if you get stuck on something, feel free to reach out either here or over email.

@sal-pal
Copy link

@sal-pal sal-pal commented Dec 16, 2017

@sal-pal
Copy link

@sal-pal sal-pal commented Dec 19, 2017

@shockey Can you please list concretely all the layout state changes related to what you said earlier??

.... we don't (currently) have a router that ... mediates layout state
changes.

@shockey
Copy link
Contributor

@shockey shockey commented Dec 27, 2017

@srpalomino, the layout state changes are the calls to the show action I mentioned earlier 😄

@tomkludy
Copy link

@tomkludy tomkludy commented May 14, 2018

Hi there, while waiting for some kind soul to implement this; is there anything I can do in the meantime to force the page to re-render? With the 2.0 UI I was able to do:

window.swaggerUi.options.success();

This was probably an evil hack, but it worked; it caused the open operation to close and then the target operation to open.

Is there anything similar in the 3.0 UI?

Thanks!

@wongJonathan
Copy link

@wongJonathan wongJonathan commented Aug 8, 2018

If anyone wants dynamic linking this is my current implementation using typescript and SwaggerUi v3.17.5.

const CustomInfoPlugin = () => {
    return {
        wrapComponents: {
            info: () => () => null,
            schemes: () => () => null,
            operations: (Original: React.ComponentClass, system: any) => (props: any) => {
                return (
                    <CustomOperationContainer originalOpContainer={<Original {...props} />} system={system} />
                );
            }
        }
    };
};
export default CustomInfoPlugin;

export class CustomOperationContainer extends React.Component<CustomOpProps, CustomOpState> {

    // Opens and scrolls to selected link
    choosePath = (path: string) => {
        const scrollId; // Operation's div id
        const operationId; // Name of operation
        const tag; // The tag of the operation
        const previousPath = this.state.previousPath;
        const layoutActions = this.props.system.layoutActions;

        // Closes the previously opened path
        if (previousPath !== null && previousPath.operationId !== operationId) {
            layoutActions.show(['operations', previousPath.tag, previousPath.operationId], false);
        }

        // Opens the operation container
        layoutActions.show(['operations', tag, operationId], true);
        this.delay(500).then(() => this.scrollTo(scrollId));
    }

    scrollTo = (scrollId: string) => {
        let element = document.getElementById(scrollId);
        if (element) {
            element.scrollIntoView();
        }
    }

    /*...*/
}

A sidebar uses choosePath as a callback function and sends the desired link. layoutActions.show opens the chosen link and scrollTo will scroll the view to that link. Because it takes some time for the link to open I used a delay. While I understand using setTimeout is not the best I couldn't figure out another way around it. Hope this helps!

@zurmuehl
Copy link

@zurmuehl zurmuehl commented Sep 21, 2018

@wongJonathan I'm a newbie in SwaggerUI Plugins.
Can you provide some hints how to get your code included into SwaggerUI?
Thanks!

@shockey
Copy link
Contributor

@shockey shockey commented Sep 25, 2018

@zurmuehl, can you share how you're using Swagger UI?

@zurmuehl
Copy link

@zurmuehl zurmuehl commented Sep 26, 2018

Shure.
Could be best explained by examples:
We are exposing OData Services through Swagger UI. The OpenAPI description is transformed from the OData service description (see here). We are also generating a picture of the entity-relationship model; see an example in swagger-ui here.
User tend to click on the Entities like "Supplier" and expect that the UI scrolls down to the Suppliers section.
Therefore we want to replace the png with an svg including hrefs with fragment identifier. But this isn't working today: In this example the picture is substituted by an simple link to demonstrate the behavior. If you click on the "Me" link the Browser adds the "#/Me" fragment to the URL, but Swagger-UI is not reacting. Only after refreshing the page Swagger-UI navigates to the "Me" section.

@TheNorthMemory
Copy link
Contributor

@TheNorthMemory TheNorthMemory commented Oct 11, 2018

As of the MDN documentation described, the window.location.hash returns a DOMString containing a '#' followed by the fragment identifier of the URL.

Some of the i18n fragments, eg: the tag is шеллы, but the fragment is %D1%88%D0%B5%D0%BB%D0%BB%D1%8B, the 546a2eb is resolved this problem.

TheNorthMemory referenced this issue Oct 18, 2018
…(via #4953)

* add tests for operation lacking an operationId
* add deep linking tests for tags/operationIds with underscores
* migrate from `_` to `%20` for deeplink hash whitespace escaping
* add backwards compatibility for `_` whitespace escaping
* update util unit tests
shockey added a commit that referenced this issue Oct 18, 2018
shockey added a commit that referenced this issue Oct 18, 2018
* ref #3958, support utf16 fragments on the deeplink plugin
* put -> head for UTF16 operation
this is a temporary fix, eventually we will run out
of methods and need to use a new targeting strategy
* drop obsolete %20 decoder
* add full test suite for UTF16 operation
* use encodeURIComponent when setting hash
* drop obsolete test cases
@avinashdubeyse
Copy link

@avinashdubeyse avinashdubeyse commented Nov 26, 2020

Hi
do any one have the working solution for it ?
if I have a hyperlink with a hash to the operationID nothing happens on click

@HenrikHL
Copy link

@HenrikHL HenrikHL commented Mar 30, 2021

Any news on this? I have also tried to add <a href="#/components/schemas/event">Event</a> inside a description without anything happening when clicking it...
Expected behavior is that the page scrolls to the Event component

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

10 participants