swagger-api / swagger-ui

Swagger UI is a collection of HTML, JavaScript, and CSS assets that dynamically generate beautiful documentation from a Swagger-compliant API.
https://swagger.io
Apache License 2.0
26.47k stars 8.96k forks source link

DeepLinking onClick does not scroll or open Operation. #3958

Open travispamaral opened 6 years ago

travispamaral commented 6 years ago

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 commented 6 years ago

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 commented 6 years ago

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 commented 6 years ago

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

shockey commented 6 years ago

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

sal-pal commented 6 years ago

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 commented 6 years ago

Thanks @srpalomino! I'm sure @shockey would love to point you in the right direction. ๐Ÿ˜„

shockey commented 6 years ago

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 commented 6 years ago

@shockey Thanks for the info!

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

I appreciate all the help!!!

shockey commented 6 years ago

@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 commented 6 years ago

@shockey This is exactly the amount of depth I was looking for in an explanation!

Thanks again, and I'll definitely reach out if I have more questions :)

sal-pal commented 6 years ago

@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 commented 6 years ago

@srpalomino, the layout state changes are the calls to the show action I mentioned earlier ๐Ÿ˜„

tomkludy commented 6 years ago

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 commented 6 years ago

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 commented 6 years ago

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

shockey commented 6 years ago

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

zurmuehl commented 6 years ago

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 commented 6 years ago

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.

avinashdubeyse commented 3 years ago

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 commented 3 years ago

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

carueda commented 2 years ago

Just a quick note that I upgraded an app from a very old swagger-ui to the latest 4.12.0, expecting to see this important usability issue finally fixed ... but not; then I found this tracker entry, so at least it's in (hopefully) someone's radar. Cheers.

GhaythFuad commented 2 years ago

We are struggling with this too, merely importing the SwaggerUI components (without actually using it) is causing all hash-links not to function.

pdelancie commented 2 years ago

When a page (html file) has a script tag with swagger-ui-bundle.js, ALL links to in-page anchors (e.g. href="#myanchor") -- even those that are completely independent of any swagger-related content -- are broken even when you don't even fetch the swagger.json itself. So displaying swagger in a page breaks the page (renders all anchor links in the page useless). This is a huge BUG, still present in swagger-ui-4.14.0 even though it was initially reported 6 (based on the start of this thread) 6 years ago. Please, please make this a priority to fix ASAP. Thank you.

sal-pal commented 1 year ago

@shockey Hi! After taking a long hiatus, I'm ready to jump back in to help get this issue resolved. Is there anywhere you'd recommend I start first?

ponelat commented 1 year ago

@sal-pal not sure if you're keen on looking into this, but would be appreciated. Shout if we can help with that (core team).

cxx5208 commented 8 months ago

Hey Can you assign this to me? I will try to fix the issue Thank you