JSON Editor feature

This commit is contained in:
laurent lepinay
2015-03-09 07:06:24 -07:00
parent 5eaea861a0
commit eb390ffca2
16 changed files with 174 additions and 29 deletions

1
dist/css/print.css vendored
View File

@@ -433,6 +433,7 @@
width: 300px;
height: 100px;
border: 1px solid #aaa;
display: none;
}
.swagger-section .swagger-ui-wrap .markdown p code,
.swagger-section .swagger-ui-wrap .markdown li code {

1
dist/index.html vendored
View File

@@ -18,6 +18,7 @@
<script src='lib/swagger-client.js' type='text/javascript'></script>
<script src='swagger-ui.js' type='text/javascript'></script>
<script src='lib/highlight.7.3.pack.js' type='text/javascript'></script>
<script src='lib/jsoneditor.min.js' type='text/javascript'></script>
<script src='lib/marked.js' type='text/javascript'></script>
<!-- enabling this will enable oauth2 implicit scope support -->

10
dist/lib/jsoneditor.min.js vendored Normal file

File diff suppressed because one or more lines are too long

104
dist/swagger-ui.js vendored
View File

@@ -627,17 +627,12 @@ this["Handlebars"]["templates"]["param"] = Handlebars.template({"1":function(dep
if (stack1 != null) { buffer += stack1; }
return buffer;
},"5":function(depth0,helpers,partials,data) {
return " <div class=\"editor_holder\"></div>\n <br />\n <div class=\"parameter-content-type\" />\n";
},"7":function(depth0,helpers,partials,data) {
var helper, functionType="function", helperMissing=helpers.helperMissing, escapeExpression=this.escapeExpression;
return " <textarea class='body-textarea' name='"
+ escapeExpression(((helper = (helper = helpers.name || (depth0 != null ? depth0.name : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"name","hash":{},"data":data}) : helper)))
+ "'>"
+ escapeExpression(((helper = (helper = helpers['default'] || (depth0 != null ? depth0['default'] : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"default","hash":{},"data":data}) : helper)))
+ "</textarea>\n <br />\n <div class=\"parameter-content-type\" />\n";
},"7":function(depth0,helpers,partials,data) {
var helper, functionType="function", helperMissing=helpers.helperMissing, escapeExpression=this.escapeExpression;
return " <textarea class='body-textarea' name='"
+ escapeExpression(((helper = (helper = helpers.name || (depth0 != null ? depth0.name : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"name","hash":{},"data":data}) : helper)))
+ "'></textarea>\n <br />\n <div class=\"parameter-content-type\" />\n";
+ "'></textarea>\n <div class=\"editor_holder\"></div>\n <br />\n <div class=\"parameter-content-type\" />\n";
},"9":function(depth0,helpers,partials,data) {
var stack1, buffer = "";
stack1 = helpers['if'].call(depth0, (depth0 != null ? depth0.isFile : depth0), {"name":"if","hash":{},"fn":this.program(2, data),"inverse":this.program(10, data),"data":data});
@@ -1193,7 +1188,7 @@ MainView = (function(superClass) {
};
MainView.prototype.initialize = function(opts) {
var auth, key, ref, value;
var auth, def, key, ref, results, value;
if (opts == null) {
opts = {};
}
@@ -1210,13 +1205,18 @@ MainView = (function(superClass) {
}
if (this.model.swaggerVersion === "2.0") {
if ("validatorUrl" in opts.swaggerOptions) {
return this.model.validatorUrl = opts.swaggerOptions.validatorUrl;
this.model.validatorUrl = opts.swaggerOptions.validatorUrl;
} else if (this.model.url.indexOf("localhost") > 0) {
return this.model.validatorUrl = null;
this.model.validatorUrl = null;
} else {
return this.model.validatorUrl = "http://online.swagger.io/validator";
this.model.validatorUrl = "http://online.swagger.io/validator";
}
}
results = [];
for (def in this.model.definitions) {
results.push(this.model.definitions[def].type = 'object');
}
return results;
};
MainView.prototype.render = function() {
@@ -1264,6 +1264,7 @@ MainView = (function(superClass) {
MainView.prototype.addResource = function(resource, auths) {
var resourceView;
resource.id = resource.id.replace(/\s/g, '_');
resource.definitions = this.model.definitions;
resourceView = new ResourceView({
model: resource,
tagName: 'li',
@@ -1350,7 +1351,7 @@ OperationView = (function(superClass) {
};
OperationView.prototype.render = function() {
var a, auth, auths, code, contentTypeModel, isMethodSubmissionSupported, k, key, l, len, len1, len2, len3, len4, m, modelAuths, n, o, p, param, q, ref, ref1, ref2, ref3, ref4, ref5, responseContentTypeView, responseSignatureView, schema, schemaObj, scopeIndex, signatureModel, statusCode, successResponse, type, v, value;
var a, auth, auths, code, contentTypeModel, isMethodSubmissionSupported, k, key, l, len, len1, len2, len3, len4, m, modelAuths, n, o, param, q, r, ref, ref1, ref2, ref3, ref4, ref5, responseContentTypeView, responseSignatureView, schema, schemaObj, scopeIndex, signatureModel, statusCode, successResponse, type, v, value;
isMethodSubmissionSupported = jQuery.inArray(this.model.method, this.model.supportedSubmitMethods()) >= 0;
if (!isMethodSubmissionSupported) {
this.model.isReadOnly = true;
@@ -1493,21 +1494,36 @@ OperationView = (function(superClass) {
});
$('.response-content-type', $(this.el)).append(responseContentTypeView.render().el);
ref4 = this.model.parameters;
for (p = 0, len3 = ref4.length; p < len3; p++) {
param = ref4[p];
for (q = 0, len3 = ref4.length; q < len3; q++) {
param = ref4[q];
this.addParameter(param, contentTypeModel.consumes);
}
ref5 = this.model.responseMessages;
for (q = 0, len4 = ref5.length; q < len4; q++) {
statusCode = ref5[q];
for (r = 0, len4 = ref5.length; r < len4; r++) {
statusCode = ref5[r];
this.addStatusCode(statusCode);
}
return this;
};
OperationView.prototype.extend = function(object, properties) {
var key, val;
for (key in properties) {
val = properties[key];
object[key] = val;
}
return object;
};
OperationView.prototype.addParameter = function(param, consumes) {
var paramView;
param.consumes = consumes;
if (param.schema) {
this.extend(param.schema, this.model.definitions[param.type]);
param.schema.definitions = this.model.definitions;
param.schema.type = "object";
param.schema.title = " ";
}
paramView = new ParameterView({
model: param,
tagName: 'tr',
@@ -1526,7 +1542,7 @@ OperationView = (function(superClass) {
};
OperationView.prototype.submitOperation = function(e) {
var error_free, form, isFileUpload, l, len, len1, len2, m, map, n, o, opts, ref1, ref2, ref3, val;
var error_free, form, isFileUpload, json, l, len, len1, len2, len3, m, map, n, o, opts, p, q, ref1, ref2, ref3, ref4, val;
if (e != null) {
e.preventDefault();
}
@@ -1592,6 +1608,14 @@ OperationView = (function(superClass) {
map[o.name] = val;
}
}
ref4 = this.model.parameters;
for (q = 0, len3 = ref4.length; q < len3; q++) {
p = ref4[q];
if ((p.jsonEditor != null) && p.jsonEditor.isEnabled()) {
json = p.jsonEditor.getValue();
map[p.name] = JSON.stringify(json);
}
}
opts.responseContentType = $("div select[name=responseContentType]", $(this.el)).val();
opts.requestContentType = $("div select[name=parameterContentType]", $(this.el)).val();
$(".response_throbber", $(this.el)).show();
@@ -1608,7 +1632,7 @@ OperationView = (function(superClass) {
};
OperationView.prototype.handleFileUpload = function(map, form) {
var bodyParam, el, headerParams, l, len, len1, len2, len3, m, n, o, obj, p, param, params, ref1, ref2, ref3, ref4;
var bodyParam, el, headerParams, l, len, len1, len2, len3, m, n, o, obj, param, params, q, ref1, ref2, ref3, ref4;
ref1 = form.serializeArray();
for (l = 0, len = ref1.length; l < len; l++) {
o = ref1[l];
@@ -1636,8 +1660,8 @@ OperationView = (function(superClass) {
}
}
ref4 = form.find('input[type~="file"]');
for (p = 0, len3 = ref4.length; p < len3; p++) {
el = ref4[p];
for (q = 0, len3 = ref4.length; q < len3; q++) {
el = ref4[q];
if (typeof el.files[0] !== 'undefined') {
bodyParam.append($(el).attr('name'), el.files[0]);
params += 1;
@@ -1822,7 +1846,7 @@ OperationView = (function(superClass) {
};
OperationView.prototype.showStatus = function(response) {
var code, content, contentType, e, headers, json, opts, pre, response_body, response_body_el, url;
var code, content, contentType, e, headers, json, opts, pre, response_body, response_body_el, supportsAudioPlayback, url;
if (response.content === void 0) {
content = response.data;
url = response.url;
@@ -1840,6 +1864,11 @@ OperationView = (function(superClass) {
}
$(".response_body", $(this.el)).removeClass('json');
$(".response_body", $(this.el)).removeClass('xml');
supportsAudioPlayback = function(contentType) {
var audioElement;
audioElement = document.createElement('audio');
return !!(audioElement.canPlayType && audioElement.canPlayType(contentType).replace(/no/, ''));
};
if (!content) {
code = $('<code />').text("no content");
pre = $('<pre class="json" />').append(code);
@@ -1861,6 +1890,8 @@ OperationView = (function(superClass) {
pre = $('<pre class="xml" />').append(code);
} else if (/^image\//.test(contentType)) {
pre = $('<img>').attr('src', url);
} else if (/^audio\//.test(contentType) && supportsAudioPlayback(contentType)) {
pre = $('<audio controls>').append($('<source>').attr('src', url).attr('type', contentType));
} else {
code = $('<code />').text(content);
pre = $('<pre class="json" />').append(code);
@@ -1948,7 +1979,7 @@ ParameterView = (function(superClass) {
};
ParameterView.prototype.render = function() {
var contentTypeModel, isParam, parameterContentTypeView, ref, responseContentTypeView, schema, signatureModel, signatureView, template, type;
var $self, contentTypeModel, isParam, parameterContentTypeView, ref, responseContentTypeView, schema, signatureModel, signatureView, template, type;
type = this.model.type || this.model.dataType;
if (typeof type === 'undefined') {
schema = this.model.schema;
@@ -1990,6 +2021,27 @@ ParameterView = (function(superClass) {
$('.model-signature', $(this.el)).html(this.model.signature);
}
isParam = false;
if (this.model.isBody && this.model.schema) {
$self = $(this.el);
this.model.jsonEditor = new JSONEditor($('.editor_holder', $self)[0], {
schema: this.model.schema,
startval: this.model["default"],
ajax: true
});
signatureModel.jsonEditor = this.model.jsonEditor;
$('.parameter-content-type', $self).change(function(e) {
if (e.target.value === "application/xml") {
$('.body-textarea', $self).show();
$('.editor_holder', $self).hide();
return this.model.jsonEditor.disable();
} else {
$('.body-textarea', $self).hide();
$('.editor_holder', $self).show();
return this.model.jsonEditor.enable();
}
});
isParam = true;
}
if (this.model.isBody) {
isParam = true;
}
@@ -2075,6 +2127,7 @@ ResourceView = (function(superClass) {
methods[id] = operation;
operation.nickname = id;
operation.parentId = this.model.id;
operation.definitions = this.model.definitions;
this.addOperation(operation);
}
$('.toggleEndpointList', this.el).click(this.callDocs.bind(this, 'toggleEndpointListForResource'));
@@ -2198,7 +2251,10 @@ SignatureView = (function(superClass) {
}
textArea = $('textarea', $(this.el.parentNode.parentNode.parentNode));
if ($.trim(textArea.val()) === '') {
return textArea.val(this.model.sampleJSON);
textArea.val(this.model.sampleJSON);
if (this.model.jsonEditor && this.model.jsonEditor.isEnabled()) {
return this.model.jsonEditor.setValue(JSON.parse(this.model.sampleJSON));
}
}
}
};

File diff suppressed because one or more lines are too long

10
lib/jsoneditor.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -18,7 +18,8 @@
"dependencies": {
"shred": "0.8.10",
"btoa": "1.1.1",
"swagger-client": "2.1.9-M1"
"swagger-client": "2.1.9-M1",
"json-editor": "~0.7.15"
},
"devDependencies": {
"chai": "^2.1.0",

View File

@@ -21,6 +21,11 @@ class MainView extends Backbone.View
else
# Default validator
@model.validatorUrl = "http://online.swagger.io/validator"
# JSonEditor requires type='object' to be present on defined types, we add it if it's missing
# is there any valid case were it should not be added ?
for def of @model.definitions
@model.definitions[def].type = 'object'
render: ->
if @model.securityDefinitions
@@ -60,6 +65,11 @@ class MainView extends Backbone.View
addResource: (resource, auths) ->
# Render a resource and add it to resources li
resource.id = resource.id.replace(/\s/g, '_')
# Make all definitions available at the root of the resource so that they can
# be loaded by the JSonEditor
resource.definitions = @model.definitions
resourceView = new ResourceView({
model: resource,
tagName: 'li',

View File

@@ -152,10 +152,29 @@ class OperationView extends Backbone.View
@addStatusCode statusCode for statusCode in @model.responseMessages
@
# Is this already available somewhere else ?
extend : (object, properties) ->
for key, val of properties
object[key] = val
object
addParameter: (param, consumes) ->
# Render a parameter
param.consumes = consumes
# Copy this param JSON spec so that it will be available for JsonEditor
if param.schema
@extend param.schema, @model.definitions[param.type]
param.schema.definitions = @model.definitions
# This is required for JsonEditor to display the root properly
param.schema.type = "object"
# This is the title that will be used by JsonEditor for the root
# Since we already display the parameter's name in the Parameter column
# We set this to space, we can't set it to null or space otherwise JsonEditor
# will replace it with the text "root" which won't look good on screen
param.schema.title = " "
paramView = new ParameterView({model: param, tagName: 'tr', readOnly: @model.isReadOnly})
$('.operation-params', $(@el)).append paramView.render().el
@@ -207,6 +226,11 @@ class OperationView extends Backbone.View
if(val? && jQuery.trim(val).length > 0)
map[o.name] = val
for p in @model.parameters
if p.jsonEditor? && p.jsonEditor.isEnabled()
json = p.jsonEditor.getValue()
map[p.name] = JSON.stringify(json)
opts.responseContentType = $("div select[name=responseContentType]", $(@el)).val()
opts.requestContentType = $("div select[name=parameterContentType]", $(@el)).val()

View File

@@ -44,6 +44,28 @@ class ParameterView extends Backbone.View
isParam = false
if @model.isBody && @model.schema
$self = $(@el)
@model.jsonEditor =
new JSONEditor($('.editor_holder', $self)[0],
{schema: @model.schema, startval : @model.default, ajax:true })
# This is so that the signature can send back the sample to the json editor
# TODO: SignatureView should expose an event "onSampleClicked" instead
signatureModel.jsonEditor = @model.jsonEditor
$('.parameter-content-type', $self)
.change(
(e) ->
if e.target.value == "application/xml"
$('.body-textarea', $self).show()
$('.editor_holder', $self).hide()
@model.jsonEditor.disable()
else
$('.body-textarea', $self).hide()
$('.editor_holder', $self).show()
@model.jsonEditor.enable())
isParam = true
if @model.isBody
isParam = true

View File

@@ -25,6 +25,7 @@ class ResourceView extends Backbone.View
operation.nickname = id
operation.parentId = @model.id
operation.definitions = @model.definitions # make Json Schema available for JSonEditor in this operation
@addOperation operation
$('.toggleEndpointList', @el).click(this.callDocs.bind(this, 'toggleEndpointListForResource'))

View File

@@ -46,6 +46,10 @@ class SignatureView extends Backbone.View
textArea = $('textarea', $(@el.parentNode.parentNode.parentNode))
if $.trim(textArea.val()) == ''
textArea.val(@model.sampleJSON)
# TODO move this code outside of the view and expose an event instead
if @model.jsonEditor && @model.jsonEditor.isEnabled()
@model.jsonEditor.setValue(JSON.parse(this.model.sampleJSON))

View File

@@ -433,6 +433,7 @@
width: 300px;
height: 100px;
border: 1px solid #aaa;
display: none;
}
.swagger-section .swagger-ui-wrap .markdown p code,
.swagger-section .swagger-ui-wrap .markdown li code {

View File

@@ -18,6 +18,7 @@
<script src='lib/swagger-client.js' type='text/javascript'></script>
<script src='swagger-ui.js' type='text/javascript'></script>
<script src='lib/highlight.7.3.pack.js' type='text/javascript'></script>
<script src='lib/jsoneditor.min.js' type='text/javascript'></script>
<script src='lib/marked.js' type='text/javascript'></script>
<!-- enabling this will enable oauth2 implicit scope support -->

View File

@@ -330,6 +330,7 @@
width: 300px;
height: 100px;
border: 1px solid #aaa;
display: none;
}
.markdown p code, .markdown li code {

View File

@@ -7,11 +7,12 @@
<div class="parameter-content-type" />
{{else}}
{{#if default}}
<textarea class='body-textarea' name='{{name}}'>{{default}}</textarea>
<div class="editor_holder"></div>
<br />
<div class="parameter-content-type" />
{{else}}
<textarea class='body-textarea' name='{{name}}'></textarea>
<div class="editor_holder"></div>
<br />
<div class="parameter-content-type" />
{{/if}}