2 * Copyright 2013 Laurent Bovet <laurent.bovet@windmaster.ch>
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 var docson = docson || {};
19 docson.templateBaseUrl="templates";
21 define(["lib/jquery", "lib/handlebars", "lib/highlight", "lib/jsonpointer", "lib/marked", "lib/traverse"], function(jquery, handlebars, highlight, jsonpointer, marked) {
23 var ready = $.Deferred();
25 var signatureTemplate;
30 Handlebars.registerHelper('scope', function(schema, options) {
33 if(schema && (schema.id || schema.root)) {
35 result = options.fn(this);
38 result = options.fn(this);
44 Handlebars.registerHelper('source', function(schema) {
46 delete schema.__boxId;
49 return JSON.stringify(schema, null, 2);
52 Handlebars.registerHelper('desc', function(schema) {
53 var description = schema.description;
55 if( !description ) return "";
56 var text = description;
58 marked.setOptions({gfm: true, breaks: true})
59 return new Handlebars.SafeString(marked(text));
65 Handlebars.registerHelper('equals', function(lvalue, rvalue, options) {
66 if (arguments.length < 3)
67 throw new Error("Handlebars Helper equals needs 2 parameters");
68 if( lvalue!=rvalue ) {
69 return options.inverse(this);
71 return options.fn(this);
75 Handlebars.registerHelper('contains', function(arr, item, options) {;
76 if(arr && arr instanceof Array && arr.indexOf(item) != -1) {
77 return options.fn(this);
81 Handlebars.registerHelper('primitive', function(schema, options) {
82 if(schema.type && schema.type != "object" && schema.type != "array" || schema.enum) {
83 return withType(this, options, true)
87 Handlebars.registerHelper('exists', function(value, options) {
88 if(value !== undefined) {
89 value = value === null ? "null": value;
90 value = value === true ? "true": value;
91 value = value === false ? "false": value;
92 value = typeof value === "object" ? JSON.stringify(value): value;
93 this.__default = value;
94 var result = options.fn(this);
95 delete this.__default;
100 Handlebars.registerHelper('range', function(from, to, replFrom, replTo, exclFrom, exclTo, sep) {
102 if(from !== undefined || to !== undefined) {
103 result += exclFrom ? "]" : "[";
104 result += from !== undefined ? from : replFrom;
105 if( (from || replFrom) !== (to || replTo)) {
106 result += (from !== undefined || replFrom !== null) && (to !== undefined || replTo !== null) ? sep : "";
107 result += to !== undefined ? to : replTo;
109 result += exclTo ? "[" : "]";
114 var sub = function(schema) {
115 return schema.type == "array" || schema.allOf || schema.anyOf || schema.oneOf || schema.not;
118 Handlebars.registerHelper('sub', function(schema, options) {
119 if(sub(schema) || (schema.type && schema.type != "object" && schema.type != "array") || schema.enum) {
120 return options.fn(this);
124 Handlebars.registerHelper('main', function(schema, options) {
126 return options.fn(this);
130 var simpleSchema = function(schema) {
131 var result = schema.description===undefined && schema.title===undefined && schema.id===undefined;
132 result &= schema.properties===undefined;
136 Handlebars.registerHelper('simple', function(schema, options) {
137 if(simpleSchema(schema) && !schema.$ref) {
138 return withType(schema, options, true);
142 var withType = function(schema, options, hideAny) {
143 schema.__type = schema.type;
144 if(!schema.type && !hideAny) {
148 schema.__type=schema.format;
150 if( (schema.__type == "any" || schema.__type == "object") && schema.title) {
151 schema.__type = schema.title;
153 var result = options.fn(schema);
154 delete schema.__type;
158 Handlebars.registerHelper('complex', function(schema, options) {
159 if(!simpleSchema(schema) && !schema.$ref || schema.properties) {
160 return withType(schema, options);
164 Handlebars.registerHelper('enum', function(schema) {
166 return (schema.enum.length > 1) ? "enum": "constant";
170 Handlebars.registerHelper('obj', function(schema, options) {
171 if(schema.properties || schema.type == "object") {
172 return withType(schema, options);
176 var pushBox = function(schema) {
177 boxes[boxes.length-1].push(schema);
180 Handlebars.registerHelper('box', function(schema, options) {
183 return options.fn(schema);
187 Handlebars.registerHelper('boxId', function() {
188 return boxes[boxes.length-1].length
191 Handlebars.registerHelper('boxes', function(options) {
193 $.each(boxes[boxes.length-1], function(k, box) {
195 result=result+options.fn(box);
197 boxes[boxes.length-1] = []
201 var resolveIdRef = function(ref) {
204 for(i=stack.length-1; i>=0; i--) {
206 return stack[i][ref];
213 var resolvePointerRef = function(ref) {
219 return jsonpointer.get(stack[1], ref);
226 var resolveRef = function(ref) {
227 if(ref.indexOf("#") == 0) {
228 return resolvePointerRef(ref);
230 return resolveIdRef(ref);
234 var getName = function(schema) {
238 var name = schema.title;
239 name = !name && schema.id ? schema.id: name;
240 name = !name ? schema.__name: name;
244 Handlebars.registerHelper('name', function(schema, options) {
245 schema.__name = getName(schema);
247 return options.fn(schema);
251 var refName = function(ref) {
252 var name = getName(resolveRef(ref));
257 name = ref.replace("#", "/")
260 var segments = name.split("/");
261 name = segments[segments.length-1];
265 function renderSchema(schema) {
266 if(stack.indexOf(schema) == -1) { // avoid recursion
268 var ret = new Handlebars.SafeString(boxTemplate(schema));
272 return new Handlebars.SafeString(boxTemplate({"description": "_circular reference_"}));
276 Handlebars.registerHelper('ref', function(schema, options) {
278 var target = resolveRef(schema.$ref);
280 target.__name = refName(schema.$ref);
281 target.__ref = schema.$ref.replace("#", "");
285 result = options.fn(target);
287 result = new Handlebars.SafeString("<span class='signature-type-ref'>"+schema.$ref+"</span>");
296 Handlebars.registerHelper('schema', function(schema) {
297 return renderSchema(schema);
300 Handlebars.registerHelper('signature', function(schema, keyword, schemas) {
304 schemas = schemas instanceof Array ? schemas : [schemas];
305 return new Handlebars.SafeString(signatureTemplate({ schema: schema, keyword: keyword, schemas: schemas}));
308 Handlebars.registerHelper('l', function(context) {
309 console.log(context);
313 $.when( $.get(docson.templateBaseUrl+"/box.html").done(function(content) {
315 boxTemplate = Handlebars.compile(source);
316 }), $.get(docson.templateBaseUrl+"/signature.html").done(function(content) {
318 signatureTemplate = Handlebars.compile(source);
319 })).always(function() {
324 docson.doc = function(element, schema, ref, baseUrl) {
325 var d = $.Deferred();
326 if(baseUrl === undefined) baseUrl='';
328 ready.done(function() {
329 if(typeof element == "string") {
330 element = $("#"+element);
332 if(typeof schema == "string") {
333 schema = JSON.parse(schema);
336 var refsPromise = $.Deferred().resolve().promise();
340 var renderBox = function() {
344 ref = ref[0] !== '/' ? '/'+ref : ref;
345 target = jsonpointer.get(schema, ref);
346 stack.push( schema );
349 target.__ref = "<root>";
350 var html = boxTemplate(target);
357 element.addClass("docson").html(html);
359 var resizeHandler = element.get(0).onresize;
362 var box = element.find(".box").first();
363 element.get(0).onresize(box.outerWidth(), box.outerHeight());
366 element.get(0).resized = resized;
370 element.find(".json-schema").each(function(k, schemaElement) {
371 highlight.highlightSchema(schemaElement);
374 element.find(".box-title").each(function() {
375 var ref = $(this).attr("ref");
377 if(window.location.href.indexOf("docson/index.html") > -1) {
378 $(this).find(".box-name").css("cursor", "pointer").attr("title", "Open in new window")
380 function(){ $(this).addClass('link') },
381 function(){ $(this).removeClass('link') })
383 var url = window.location.href+"$$expand";
384 if(ref !=="<root>") {
385 url = url.replace(/(docson\/index.html#[^\$]*).*/, "$1$"+ref+"$$expand");
388 function receiveMessage(event) {
389 if (event.data.id && event.data.id == "docson" && event.data.action == "ready") {
390 w.postMessage({ id: "docson", action: "load", definitions: schema, type: event.data.url.split("$")[1], expand: true}, "*");
393 window.addEventListener("message", receiveMessage, false);
394 w = window.open(url, "_blank");
399 element.find(".box").mouseenter(function() {
400 $(this).children(".source-button").fadeIn(300);
401 $(this).children(".box-body").children(".expand-button").fadeIn(300);
403 element.find(".box").mouseleave(function() {
404 $(this).children(".source-button").fadeOut(300);
405 $(this).children(".box-body").children(".expand-button").fadeOut(300);
407 element.find(".signature-type-expandable").click(function() {
408 var boxId = $(this).attr("boxid");
409 $(this).toggleClass("signature-type-expanded");
410 $(this).parent().parent().parent().children(".signature-box-container").
411 children("[boxid='"+boxId+"']").toggle(resizeHandler ? 0 : 300);
414 element.find(".expand-button").click(function() {
415 if($(this).attr("expanded")) {
416 $(this).parent().parent().find(".expand-button").html(" + ").attr("title", "Expand all");
417 $(this).parent().parent().find(".signature-type-expandable").removeClass("signature-type-expanded");
418 $(this).parent().parent().find(".box-container").hide( resizeHandler ? 0 : 300);
419 $(this).parent().parent().find(".expand-button").removeAttr("expanded");
422 $(this).parent().parent().find(".expand-button").html(" - ").attr("title", "Collapse all");
423 $(this).parent().parent().find(".signature-type-expandable").addClass("signature-type-expanded");
424 $(this).parent().parent().find(".box-container").show(resizeHandler ? 0 : 300);
425 $(this).parent().parent().find(".expand-button").attr("expanded", true);
429 element.find(".source-button").click(function() {
430 $(this).parent().children(".box-body").toggle();
431 $(this).parent().children(".source").toggle();
436 var resolveRefsReentrant = function(schema){
437 traverse(schema).forEach(function(item) {
438 // Fix Swagger weird generation for array.
439 if(item && item.$ref == "array") {
444 // Fetch external schema
445 if(this.key === "$ref") {
446 var external = false;
447 //Local meaning local to this server, but not in this file.
449 if((/^https?:\/\//).test(item)) {
452 else if((/^[^#]/).test(item)) {
454 } else if(item.indexOf('#') > 0) {
456 //Turning relative refs to absolute ones
458 item = baseUrl + item;
462 //External reference, fetch it.
463 var segments = item.split("#");
465 var p = $.get(segments[0]).then(function(content) {
466 if(typeof content != "object") {
468 content = JSON.parse(content);
470 console.error("Unable to parse "+segments[0], e);
474 refs[item] = content;
476 resolveRefsReentrant(content);
481 //Local to this server, fetch relative
482 var segments = item.split("#");
484 var p = $.get(baseUrl + segments[0]).then(function(content) {
485 if(typeof content != "object") {
487 content = JSON.parse(content);
489 console.error("Unable to parse "+segments[0], e);
493 refs[item] = content;
495 resolveRefsReentrant(content);
503 resolveRefsReentrant(schema);