diff --git a/README.md b/README.md index d4094f189d..ae92ab227b 100644 --- a/README.md +++ b/README.md @@ -138,6 +138,7 @@ ReDoc makes use of the following [vendor extensions](http://swagger.io/specifica * `lazy-rendering` - if set, enables lazy rendering mode in ReDoc. This mode is useful for APIs with big number of operations (e.g. > 50). In this mode ReDoc shows initial screen ASAP and then renders the rest operations asynchronously while showing progress bar on the top. Check out the [demo](\\rebilly.github.io/ReDoc) for the example. * `hide-hostname` - if set, the protocol and hostname is not shown in the method definition. * `expand-responses` - specify which responses to expand by default by response codes. Values should be passed as comma-separated list without spaces e.g. `expand-responses="200,201"`. Special value `"all"` expands all responses by default. Be careful: this option can slow-down documentation rendering time. +* `required-props-first` - show required properties first ordered in the same order as in `required` array. ## Advanced usage Instead of adding `spec-url` attribute to the `` element you can initialize ReDoc via globally exposed `Redoc` object: diff --git a/lib/components/JsonSchema/json-schema.ts b/lib/components/JsonSchema/json-schema.ts index e4e15ad312..bea5967580 100644 --- a/lib/components/JsonSchema/json-schema.ts +++ b/lib/components/JsonSchema/json-schema.ts @@ -10,7 +10,7 @@ import { Component, } from '@angular/core'; import { BaseSearchableComponent, SpecManager } from '../base'; -import { SchemaNormalizer, SchemaHelper, AppStateService } from '../../services/'; +import { SchemaNormalizer, SchemaHelper, AppStateService, OptionsService } from '../../services/'; import { JsonPointer, DescendantInfo } from '../../utils/'; import { Zippy } from '../../shared/components'; import { JsonSchemaLazy } from './json-schema-lazy'; @@ -39,11 +39,12 @@ export class JsonSchema extends BaseSearchableComponent implements OnInit { descendants: DescendantInfo[]; constructor( - specMgr:SpecManager, + specMgr: SpecManager, app: AppStateService, private _renderer: Renderer, private cdr: ChangeDetectorRef, - private _elementRef: ElementRef) { + private _elementRef: ElementRef, + private optionsService: OptionsService) { super(specMgr, app); this.normalizer = new SchemaNormalizer(specMgr); } @@ -126,7 +127,11 @@ export class JsonSchema extends BaseSearchableComponent implements OnInit { this.properties = this.schema._properties || []; if (this.isRequestSchema) { - this.properties = this.properties && this.properties.filter(prop => !prop.readOnly); + this.properties = this.properties.filter(prop => !prop.readOnly); + } + + if (this.optionsService.options.requiredPropsFirst) { + SchemaHelper.moveRequiredPropsFirst(this.properties, this.schema.required); } this._hasSubSchemas = this.properties && this.properties.some( diff --git a/lib/services/options.service.ts b/lib/services/options.service.ts index 4da11b425a..268a43f538 100644 --- a/lib/services/options.service.ts +++ b/lib/services/options.service.ts @@ -15,7 +15,8 @@ const OPTION_NAMES = new Set([ 'suppressWarnings', 'hideHostname', 'lazyRendering', - 'expandResponses' + 'expandResponses', + 'requiredPropsFirst' ]); interface Options { @@ -27,6 +28,7 @@ interface Options { lazyRendering?: boolean; expandResponses?: Set | 'all'; $scrollParent?: HTMLElement | Window; + requiredPropsFirst?: boolean; } @Injectable() @@ -91,6 +93,7 @@ export class OptionsService { if (isString(this._options.suppressWarnings)) this._options.suppressWarnings = true; if (isString(this._options.hideHostname)) this._options.hideHostname = true; if (isString(this._options.lazyRendering)) this._options.lazyRendering = true; + if (isString(this._options.requiredPropsFirst)) this._options.requiredPropsFirst = true; if (isString(this._options.expandResponses)) { let str = this._options.expandResponses as string; if (str === 'all') return; diff --git a/lib/services/schema-helper.service.spec.ts b/lib/services/schema-helper.service.spec.ts index 7364b26008..29554065c1 100644 --- a/lib/services/schema-helper.service.spec.ts +++ b/lib/services/schema-helper.service.spec.ts @@ -86,4 +86,49 @@ describe('Spec Helper', () => { (() => SchemaHelper.preprocessProperties(schema, '#/', {})).should.not.throw(); }); }); + + describe('moveRequiredPropsFirst', () => { + it('should move required props to the top', () => { + let props = [{ + name: 'prop2', + type: 'string' + }, + { + name: 'prop1', + type: 'number', + _required: true + }]; + + let required = ['prop1']; + + SchemaHelper.moveRequiredPropsFirst(props, required); + props[0].name.should.be.equal('prop1'); + props[1].name.should.be.equal('prop2'); + }); + + it('should sort required props by the order or required', () => { + var props = [{ + name: 'prop2', + type: 'string' + }, + { + name: 'prop1', + type: 'number', + _required: true + }, + { + name: 'prop3', + type: 'number', + _required: true + } + ]; + + let required = ['prop3', 'prop1']; + + SchemaHelper.moveRequiredPropsFirst(props, required); + props[0].name.should.be.equal('prop3'); + props[1].name.should.be.equal('prop1'); + props[2].name.should.be.equal('prop2'); + }); + }); }); diff --git a/lib/services/schema-helper.service.ts b/lib/services/schema-helper.service.ts index 4e1192d2cf..9f07b546c3 100644 --- a/lib/services/schema-helper.service.ts +++ b/lib/services/schema-helper.service.ts @@ -327,4 +327,19 @@ export class SchemaHelper { return tags; } + + static moveRequiredPropsFirst(properties: any[], _required: string[]|null) { + let required = _required || []; + properties.sort((a, b) => { + if ((!a._required && b._required)) { + return 1; + } else if (a._required && !b._required) { + return -1; + } else if (a._required && b._required) { + return required.indexOf(a.name) > required.indexOf(b.name) ? 1 : -1; + } else { + return 0; + } + }); + } }