plugin-api.md 10.2 KB
Newer Older
K
Kyle Shockey 已提交
1
# Plugins
K
Kyle Shockey 已提交
2

K
Kyle Shockey 已提交
3
A plugin is a function that returns an object - more specifically, the object may contain functions and components that augment and modify Swagger-UI's functionality.
K
Kyle Shockey 已提交
4 5 6

### Format

K
Kyle Shockey 已提交
7
A plugin return value may contain any of these keys, where `myStateKey` is a name for a piece of state:
K
Kyle Shockey 已提交
8 9

```javascript
K
Kyle Shockey 已提交
10
{
K
Kyle Shockey 已提交
11
  statePlugins: {
K
Kyle Shockey 已提交
12
    myStateKey: {
K
Kyle Shockey 已提交
13 14 15 16 17 18 19
      actions,
      reducers,
      selectors,
      wrapActions,
      wrapSelectors
    }
  },
K
Kyle Shockey 已提交
20 21 22 23 24
  components: {},
  wrapComponents: {},
  fn: {}
}
```
K
Kyle Shockey 已提交
25

K
WIP  
Kyle Shockey 已提交
26
### System is provided to plugins
K
Kyle Shockey 已提交
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42

Let's assume we have a plugin, `NormalPlugin`, that exposes a `doStuff` action under the `normal` state namespace.

```javascript
const ExtendingPlugin = function(system) {
  return {
    statePlugins: {
      extending: {
        actions: {
          doExtendedThings: function(...args) {
            // you can do other things in here if you want
            return system.normalActions.doStuff(...args)
          }
        }
      }
    }
K
Kyle Shockey 已提交
43 44 45 46
  }
}
```

K
Kyle Shockey 已提交
47 48 49 50
As you can see, each plugin is passed a reference to the `system` being built up. As long as `NormalPlugin` is compiled before `ExtendingPlugin`, this will work without any issues.

There is no dependency management built into the plugin system, so if you create a plugin that relies on another, it is your responsibility to make sure that the dependent plugin is loaded _after_ the plugin being depended on.

K
Kyle Shockey 已提交
51 52 53 54
### Interfaces

##### Actions

K
Kyle Shockey 已提交
55 56 57 58 59 60
```javascript
const MyActionPlugin = () => {
  return {
    statePlugins: {
      example: {
        actions: {
K
WIP  
Kyle Shockey 已提交
61
          updateFavoriteColor: (str) => {
K
Kyle Shockey 已提交
62 63 64 65 66 67 68 69 70 71 72 73
            return {
              type: "EXAMPLE_SET_FAV_COLOR",
              payload: str
            }
          }
        }
      }
    }
  }
}
```

K
Kyle Shockey 已提交
74 75
Once an action has been defined, you can use it anywhere that you can get a system reference:

76
```javascript
K
Kyle Shockey 已提交
77
// elsewhere
K
Kyle Shockey 已提交
78
system.exampleActions.updateFavoriteColor("blue")
K
Kyle Shockey 已提交
79 80
```

K
Kyle Shockey 已提交
81 82
The Action interface enables the creation of new Redux action creators within a piece of state in the Swagger-UI system.

K
Kyle Shockey 已提交
83
This action creator function will be exposed to container components as `exampleActions.updateFavoriteColor`. When this action creator is called, the return value (which should be a [Flux Standard Action](https://github.com/acdlite/flux-standard-action)) will be passed to the `example` reducer, which we'll define in the next section.
K
Kyle Shockey 已提交
84 85 86

For more information about the concept of actions in Redux, see the [Redux Actions documentation](http://redux.js.org/docs/basics/Actions.html).

K
Kyle Shockey 已提交
87 88
##### Reducers

K
Kyle Shockey 已提交
89
Reducers take a state (which is an [Immutable.js map](https://facebook.github.io/immutable-js/docs/#/Map)) and an action, and return a new state.
K
Kyle Shockey 已提交
90

K
WIP  
Kyle Shockey 已提交
91
Reducers must be provided to the system under the name of the action type that they handle, in this case, `EXAMPLE_SET_FAV_COLOR`.
K
Kyle Shockey 已提交
92

93
```javascript
K
Kyle Shockey 已提交
94 95 96 97 98 99
const MyReducerPlugin = function(system) {
  return {
    statePlugins: {
      example: {
        reducers: {
          "EXAMPLE_SET_FAV_COLOR": (state, action) => {
K
Kyle Shockey 已提交
100
            // you're only working with the state under the namespace, in this case "example".
K
Kyle Shockey 已提交
101
            // So you can do what you want, without worrying about /other/ namespaces
K
Kyle Shockey 已提交
102
            return state.set("favColor", action.payload)
K
Kyle Shockey 已提交
103 104 105 106 107 108 109 110
          }
        }
      }
    }
  }
}
```

K
Kyle Shockey 已提交
111 112
##### Selectors

113
Selectors reach into their namespace's state to retrieve or derive data from the state.
K
Kyle Shockey 已提交
114

115
They're an easy way to keep logic in one place, and is preferred over passing state data directly into components.
K
Kyle Shockey 已提交
116

K
WIP  
Kyle Shockey 已提交
117

118
```javascript
K
WIP  
Kyle Shockey 已提交
119 120 121 122 123 124 125 126 127 128 129 130 131
const MySelectorPlugin = function(system) {
  return {
    statePlugins: {
      example: {
        selectors: {
          myFavoriteColor: (state) => state.get("favColor")
        }
      }
    }
  }
}
```

K
Kyle Shockey 已提交
132
You can also use the Reselect library to memoize your selectors, which is recommended for any selectors that will see heavy use, since Reselect automatically memoizes selector calls for you:
K
WIP  
Kyle Shockey 已提交
133

134
```javascript
K
WIP  
Kyle Shockey 已提交
135 136
import { createSelector } from "reselect"

K
Kyle Shockey 已提交
137 138 139
const MySelectorPlugin = function(system) {
  return {
    statePlugins: {
K
WIP  
Kyle Shockey 已提交
140
      example: {
K
Kyle Shockey 已提交
141
        selectors: {
K
WIP  
Kyle Shockey 已提交
142 143 144
          // this selector will be memoized after it is run once for a
          // value of `state`
          myFavoriteColor: createSelector((state) => state.get("favColor"))
K
Kyle Shockey 已提交
145 146 147 148 149 150 151
        }
      }
    }
  }
}
```

K
Kyle Shockey 已提交
152
Once a selector has been defined, you can use it anywhere that you can get a system reference:
153
```javascript
K
Kyle Shockey 已提交
154
system.exampleSelectors.myFavoriteColor() // gets `favColor` in state for you
K
WIP  
Kyle Shockey 已提交
155 156
```

K
Kyle Shockey 已提交
157 158
##### Components

K
Kyle Shockey 已提交
159 160 161 162
You can provide a map of components to be integrated into the system.

Be mindful of the key names for the components you provide, as you'll need to use those names to refer to the components elsewhere.

163
```javascript
K
Kyle Shockey 已提交
164 165 166 167 168 169
class HelloWorldClass extends React.Component {
  render() {
    return <h1>Hello World!</h1>
  }
}

K
Kyle Shockey 已提交
170 171 172
const MyComponentPlugin = function(system) {
  return {
    components: {
K
Kyle Shockey 已提交
173 174 175
      HelloWorldClass: HelloWorldClass
      // components can just be functions, these are called "stateless components"
      HelloWorldStateless: () => <h1>Hello World!</h1>,
K
Kyle Shockey 已提交
176 177 178 179 180
    }
  }
}
```

181
```javascript
K
Kyle Shockey 已提交
182
// elsewhere
K
Kyle Shockey 已提交
183 184 185 186 187 188
const HelloWorldStateless = system.getComponent("HelloWorldStateless")
const HelloWorldClass = system.getComponent("HelloWorldClass")
```

You can also "cancel out" any components that you don't want by creating a stateless component that always returns `null`:

189
```javascript
K
Kyle Shockey 已提交
190 191 192 193 194 195 196
const NeverShowInfoPlugin = function(system) {
  return {
    components: {
      info: () => null
    }
  }
}
K
Kyle Shockey 已提交
197 198
```

K
Kyle Shockey 已提交
199 200
##### Wrap-Actions

K
Kyle Shockey 已提交
201 202
Wrap Actions allow you to override the behavior of an action in the system.

K
Kyle Shockey 已提交
203
They are function factories with the signature `(oriAction, system) => (...args) => result`.
K
Kyle Shockey 已提交
204 205 206

A Wrap Action's first argument is `oriAction`, which is the action being wrapped. It is your responsibility to call the `oriAction` - if you don't, the original action will not fire!

K
Kyle Shockey 已提交
207 208
This mechanism is useful for conditionally overriding built-in behaviors, or listening to actions.

209
```javascript
K
Kyle Shockey 已提交
210 211
// FYI: in an actual Swagger-UI, `updateSpec` is already defined in the core code
// it's just here for clarity on what's behind the scenes
K
Kyle Shockey 已提交
212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234
const MySpecPlugin = function(system) {
  return {
    statePlugins: {
      spec: {
        actions: {
          updateSpec: (str) => {
            return {
              type: "SPEC_UPDATE_SPEC",
              payload: str
            }
          }
        }
      }
    }
  }
}

// this plugin allows you to watch changes to the spec that is in memory
const MyWrapActionPlugin = function(system) {
  return {
    statePlugins: {
      spec: {
        wrapActions: {
K
Kyle Shockey 已提交
235 236 237 238
          updateSpec: (oriAction, system) => (str) => {
            // here, you can hand the value to some function that exists outside of Swagger-UI
            console.log("Here is my API definition", str)
            return oriAction(str) // don't forget! otherwise, Swagger-UI won't update
K
Kyle Shockey 已提交
239 240 241 242 243 244 245 246
          }
        }
      }
    }
  }
}
```

K
Kyle Shockey 已提交
247 248
##### Wrap-Selectors

K
Kyle Shockey 已提交
249 250
Wrap Selectors allow you to override the behavior of a selector in the system.

K
Kyle Shockey 已提交
251
They are function factories with the signature `(oriSelector, system) => (state, ...args) => result`.
K
Kyle Shockey 已提交
252 253 254

This interface is useful for controlling what data flows into components. We use this in the core code to disable selectors based on the API definition's version.

255
```javascript
K
Kyle Shockey 已提交
256 257
import { createSelector } from 'reselect'

K
Kyle Shockey 已提交
258 259
// FYI: in an actual Swagger-UI, the `url` spec selector is already defined
// it's just here for clarity on what's behind the scenes
K
Kyle Shockey 已提交
260 261 262 263 264
const MySpecPlugin = function(system) {
  return {
    statePlugins: {
      spec: {
        selectors: {
K
Kyle Shockey 已提交
265 266
          url: createSelector(
            state => state.get("url")
K
Kyle Shockey 已提交
267 268 269 270 271 272 273 274 275 276 277 278
          )
        }
      }
    }
  }
}

const MyWrapSelectorsPlugin = function(system) {
  return {
    statePlugins: {
      spec: {
        wrapSelectors: {
K
Kyle Shockey 已提交
279 280 281
          url: (oriSelector, system) => (state, ...args) => {
            console.log('someone asked for the spec url!!! it is', state.get('url'))
            // you can return other values here...
K
Kyle Shockey 已提交
282
            // but let's just enable the default behavior
K
Kyle Shockey 已提交
283
            return oriSelector(state, ...args)
K
Kyle Shockey 已提交
284 285 286 287 288 289 290 291
          }
        }
      }
    }
  }
}
```

K
Kyle Shockey 已提交
292
##### Wrap-Components
K
Kyle Shockey 已提交
293

K
Kyle Shockey 已提交
294 295
Wrap Components allow you to override a component registered within the system.

296
Wrap Components are function factories with the signature `(OriginalComponent, system) => props => ReactElement`. If you'd prefer to provide a React component class, `(OriginalComponent, system) => ReactClass` works as well.
K
Kyle Shockey 已提交
297

298 299 300 301 302 303 304 305 306 307 308 309 310 311 312
```javascript
const MyWrapBuiltinComponentPlugin = function(system) {
  return {
    wrapComponents: {
      info: (Original, system) => (props) => {
        return <div>
          <h3>Hello world! I am above the Info component.</h3>
          <Original {...props} />
        </div>
      }
    }
  }
}
```

313 314
Here's another example that includes a code sample of a component that will be wrapped:

315
```javascript
316
/////  Overriding a component from a plugin
317

318
// Here's our normal, unmodified component.
K
Kyle Shockey 已提交
319 320 321 322 323 324 325 326
const MyNumberDisplayPlugin = function(system) {
  return {
    components: {
      NumberDisplay: ({ number }) => <span>{number}</span>
    }
  }
}

327
// Here's a component wrapper defined as a function.
K
Kyle Shockey 已提交
328 329 330 331 332 333 334
const MyWrapComponentPlugin = function(system) {
  return {
    wrapComponents: {
      NumberDisplay: (Original, system) => (props) => {
        if(props.number > 10) {
          return <div>
            <h3>Warning! Big number ahead.</h3>
335
            <OriginalComponent {...props} />
K
Kyle Shockey 已提交
336 337 338 339 340 341 342 343
          </div>
        } else {
          return <Original {...props} />
        }
      }
    }
  }
}
344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363

// Alternatively, here's the same component wrapper defined as a class.
const MyWrapComponentPlugin = function(system) {
  return {
    wrapComponents: {
      NumberDisplay: (Original, system) => class WrappedNumberDisplay extends React.component {
        render() {
          if(props.number > 10) {
            return <div>
              <h3>Warning! Big number ahead.</h3>
              <OriginalComponent {...props} />
            </div>
          } else {
            return <Original {...props} />
          }
        }
      }
    }
  }
}
K
Kyle Shockey 已提交
364 365
```

366 367


K
Kyle Shockey 已提交
368
##### fn
K
Kyle Shockey 已提交
369 370 371

The fn interface allows you to add helper functions to the system for use elsewhere.

372
```javascript
K
Kyle Shockey 已提交
373 374 375 376 377 378 379 380 381 382
import leftPad from "left-pad"

const MyFnPlugin = function(system) {
  return {
    fn: {
      leftPad: leftPad
    }
  }
}
```