redoc/lib/components/base.js

250 lines
7.3 KiB
JavaScript
Raw Normal View History

'use strict';
2016-05-06 00:48:41 +03:00
import { Component, ChangeDetectionStrategy } from '@angular/core';
import { CORE_DIRECTIVES, JsonPipe, AsyncPipe } from '@angular/common';
2015-10-27 20:44:08 +03:00
import SchemaManager from '../utils/SchemaManager';
import JsonPointer from '../utils/JsonPointer';
2016-05-06 00:48:41 +03:00
import { MarkedPipe, JsonPointerEscapePipe } from '../utils/pipes';
export { SchemaManager };
// common inputs for all components
let commonInputs = ['pointer']; // json pointer to the schema chunk
// internal helper function
function safeConcat(a, b) {
let res = a && a.slice() || [];
b = (b == null) ? [] : b;
return res.concat(b);
}
2016-03-15 17:09:16 +03:00
function defaults(target, src) {
var props = Object.keys(src);
var index = -1,
length = props.length;
while (++index < length) {
var key = props[index];
if (target[key] === undefined) {
target[key] = src[key];
}
}
return target;
}
2016-01-09 23:31:39 +03:00
function snapshot(obj) {
if(obj == null || typeof(obj) != 'object') {
return obj;
}
var temp = new obj.constructor();
for(var key in obj) {
if (obj.hasOwnProperty(key)) {
temp[key] = snapshot(obj[key]);
}
}
return temp;
}
/**
* Class decorator
* Simplifies setup of component metainfo
* All options are options from either Component or View angular2 decorator
* For detailed info look angular2 doc
* @param {Object} options - component options
* @param {string[]} options.inputs - component inputs
* @param {*[]} options.directives - directives used by component
* (except CORE_DIRECTIVES)
* @param {*[]} options.pipes - pipes used by component
2015-10-17 21:56:24 +03:00
* @param {*[]} options.providers - component providers
* @param {string} options.templateUrl - path to component template
* @param {string} options.template - component template html
* @param {string} options.styles - component css styles
*/
export function RedocComponent(options) {
let inputs = safeConcat(options.inputs, commonInputs);
let directives = safeConcat(options.directives, CORE_DIRECTIVES);
let pipes = safeConcat(options.pipes, [JsonPointerEscapePipe, MarkedPipe, JsonPipe, AsyncPipe]);
return function decorator(target) {
let componentDecorator = Component({
selector: options.selector,
inputs: inputs,
2015-10-15 21:35:05 +03:00
outputs: options.outputs,
2015-11-29 13:47:42 +03:00
providers: options.providers,
2016-04-29 22:57:24 +03:00
changeDetection: options.changeDetection || ChangeDetectionStrategy.Default,
templateUrl: options.templateUrl,
template: options.template,
styles: options.styles,
directives: directives,
pipes: pipes
});
return componentDecorator(target) || target;
};
}
/**
* Generic Component
* @class
*/
@Reflect.metadata('parameters', [[SchemaManager]])
export class BaseComponent {
constructor(schemaMgr) {
this.schemaMgr = schemaMgr;
this.componentSchema = null;
}
/**
* onInit method is run by angular2 after all component inputs are resolved
*/
ngOnInit() {
2016-01-09 23:31:39 +03:00
this.componentSchema = snapshot(this.schemaMgr.byPointer(this.pointer || ''));
this.prepareModel();
2015-10-17 21:12:46 +03:00
this.init();
}
2015-12-21 22:35:57 +03:00
ngOnDestroy() {
this.destroy();
}
2015-10-21 17:22:22 +03:00
/**
* simple in-place schema dereferencing. Schema is already bundled so no need in global dereferencing.
*/
2015-11-28 01:44:35 +03:00
dereference(schema = Object.assign({}, this.componentSchema)) {
2016-01-16 01:44:10 +03:00
let dereferencedCache = {};
let resolve = (schema) => {
let resolvedRef;
2016-01-16 01:44:10 +03:00
if (schema && schema.$ref) {
resolvedRef = schema.$ref;
2016-01-16 01:44:10 +03:00
let resolved = this.schemaMgr.byPointer(schema.$ref);
let baseName = JsonPointer.baseName(schema.$ref);
if (!dereferencedCache[schema.$ref]) {
// if resolved schema doesn't have title use name from ref
resolved = Object.assign({}, resolved);
resolved._pointer = schema.$ref;
} else {
// for circular referenced save only title and type
resolved = {
title: resolved.title,
type: resolved.type
2016-01-16 01:44:10 +03:00
};
}
2016-01-17 00:35:57 +03:00
dereferencedCache[schema.$ref] = dereferencedCache[schema.$ref] ? dereferencedCache[schema.$ref] + 1 : 1;
2016-01-17 00:35:57 +03:00
2016-01-16 01:44:10 +03:00
resolved.title = resolved.title || baseName;
2016-01-17 00:35:57 +03:00
let keysCount = Object.keys(schema).length;
if ( keysCount > 2 || (keysCount === 2 && !schema.description) ) {
// allow only description field on the same level as $ref because it is
// common pattern over specs in the wild
console.warn(`other properties defined at the same level as $ref at '${this.pointer}'.
They are IGNORRED according to JsonSchema spec`);
}
schema = schema.description ? {
description: schema.description
} : {};
2016-01-16 01:44:10 +03:00
Object.assign(schema, resolved);
2015-10-21 17:22:22 +03:00
}
2016-01-16 01:44:10 +03:00
Object.keys(schema).forEach((key) => {
let value = schema[key];
if (value && typeof value === 'object') {
2016-01-17 00:35:57 +03:00
schema[key] = resolve(value);
2016-01-16 01:44:10 +03:00
}
});
if (resolvedRef) dereferencedCache[resolvedRef] = dereferencedCache[resolvedRef] ? dereferencedCache[resolvedRef] - 1 : 0;
2016-01-17 00:35:57 +03:00
return schema;
2016-01-16 01:44:10 +03:00
};
this.componentSchema = snapshot(resolve(schema, 1));
2015-10-21 17:22:22 +03:00
}
static joinAllOf(schema, opts) {
2015-11-25 02:51:54 +03:00
function merge(into, schemas) {
for (let subSchema of schemas) {
2016-01-10 18:29:35 +03:00
if (opts && opts.omitParent && subSchema.discriminator) continue;
// TODO: add support for merge array schemas
2016-03-15 17:09:16 +03:00
if (typeof subSchema !== 'object') {
let errMessage = `Items of allOf should be Object: ${typeof subSchema} found
${subSchema}`;
throw new Error(errMessage);
2015-11-25 02:51:54 +03:00
}
if (into.type && subSchema.type && into.type !== subSchema.type) {
2016-03-15 17:09:16 +03:00
let errMessage = `allOf merging error: schemas with different types can't be merged`;
throw new Error(errMessage);
}
2016-03-15 17:09:42 +03:00
if (into.type === 'array') {
console.warn('allOf: subschemas with type array are not supported yet');
}
// TODO: add check if can be merged correctly (no different properties with the same name)
2016-03-27 13:35:06 +03:00
into.type = into.type || subSchema.type;
if (into.type === 'object' && subSchema.properties) {
2016-03-15 17:09:16 +03:00
into.properties || (into.properties = {});
2015-11-25 02:51:54 +03:00
Object.assign(into.properties, subSchema.properties);
Object.keys(subSchema.properties).forEach(propName => {
if (!subSchema.properties[propName]._pointer) {
subSchema.properties[propName]._pointer = subSchema._pointer ?
JsonPointer.join(subSchema._pointer, ['properties', propName]) : null;
}
});
2015-11-25 02:51:54 +03:00
}
if (into.type === 'object' && subSchema.required) {
2016-03-15 17:09:16 +03:00
into.required || (into.required = []);
2015-11-25 02:51:54 +03:00
into.required.push(...subSchema.required);
}
// don't merge _pointer
subSchema._pointer = null;
2016-03-15 17:09:16 +03:00
defaults(into, subSchema);
2015-11-25 02:51:54 +03:00
}
into.allOf = null;
}
2016-03-15 17:09:42 +03:00
function traverse(obj) {
if (obj === null || typeof(obj) !== 'object') {
return;
}
for(var key in obj) {
if (obj.hasOwnProperty(key)) {
traverse(obj[key]);
}
}
if (obj.allOf) {
merge(obj, obj.allOf);
}
2015-11-25 02:51:54 +03:00
}
2016-03-15 17:09:42 +03:00
traverse(schema);
2015-11-25 02:51:54 +03:00
}
/**
* Used to prepare model based on component schema
* @abstract
*/
prepareModel() {}
2015-10-17 21:12:46 +03:00
/**
* Used to initialize component. Run after prepareModel
* @abstract
*/
init() {}
2015-12-21 22:35:57 +03:00
/**
+ Used to destroy component
* @abstract
*/
destroy() {}
}