3 bootstrap: './vendor/bootstrap.min',
4 diffMatchPatch: './vendor/diff_match_patch.min',
5 handlebars: './vendor/handlebars.min',
6 handlebarsExtended: './utils/handlebars_helper',
7 jquery: './vendor/jquery.min',
8 locales: './locales/locale',
9 lodash: './vendor/lodash.custom.min',
10 pathToRegexp: './vendor/path-to-regexp/index',
11 prismjs: './vendor/prism',
12 semver: './vendor/semver.min',
13 utilsSampleRequest: './utils/send_sample_request',
14 webfontloader: './vendor/webfontloader',
15 list: './vendor/list.min',
16 apiData: './api_data',
17 apiProject: './api_project',
24 exports: 'diff_match_patch'
30 deps: ['jquery', 'handlebars'],
37 urlArgs: 'v=' + (new Date()).getTime(),
55 ], function($, _, locale, Handlebars, apiProject, apiData, Prism, sampleRequest, semver, WebFont) {
57 // Load google web fonts.
60 // Only init after fonts are loaded.
61 init($, _, locale, Handlebars, apiProject, apiData, Prism, sampleRequest, semver);
63 inactive: function() {
64 // Run init, even if loading fonts fails
65 init($, _, locale, Handlebars, apiProject, apiData, Prism, sampleRequest, semver);
68 families: ['Source Code Pro', 'Source Sans Pro:n4,n6,n7']
73 function init($, _, locale, Handlebars, apiProject, apiData, Prism, sampleRequest, semver) {
74 var api = apiData.api;
79 var templateHeader = Handlebars.compile( $('#template-header').html() );
80 var templateFooter = Handlebars.compile( $('#template-footer').html() );
81 var templateArticle = Handlebars.compile( $('#template-article').html() );
82 var templateCompareArticle = Handlebars.compile( $('#template-compare-article').html() );
83 var templateGenerator = Handlebars.compile( $('#template-generator').html() );
84 var templateProject = Handlebars.compile( $('#template-project').html() );
85 var templateSections = Handlebars.compile( $('#template-sections').html() );
86 var templateSidenav = Handlebars.compile( $('#template-sidenav').html() );
89 // apiProject defaults
91 if ( ! apiProject.template)
92 apiProject.template = {};
94 if (apiProject.template.withCompare == null)
95 apiProject.template.withCompare = true;
97 if (apiProject.template.withGenerator == null)
98 apiProject.template.withGenerator = true;
100 if (apiProject.template.forceLanguage)
101 locale.setLanguage(apiProject.template.forceLanguage);
103 if (apiProject.template.aloneDisplay == null)
104 apiProject.template.aloneDisplay = false;
107 $.ajaxSetup(apiProject.template.jQueryAjaxSetup);
113 var apiByGroup = _.groupBy(api, function(entry) {
117 // grouped by group and name
118 var apiByGroupAndName = {};
119 $.each(apiByGroup, function(index, entries) {
120 apiByGroupAndName[index] = _.groupBy(entries, function(entry) {
126 // sort api within a group by title ASC and custom order
129 var umlauts = { 'ä': 'ae', 'ü': 'ue', 'ö': 'oe', 'ß': 'ss' }; // TODO: remove in version 1.0
130 $.each (apiByGroupAndName, function(index, groupEntries) {
131 // get titles from the first entry of group[].name[] (name has versioning)
133 $.each (groupEntries, function(titleName, entries) {
134 var title = entries[0].title;
135 if(title !== undefined) {
136 title.toLowerCase().replace(/[äöüß]/g, function($0) { return umlauts[$0]; });
137 titles.push(title + '#~#' + titleName); // '#~#' keep reference to titleName after sorting
144 if (apiProject.order)
145 titles = sortByOrder(titles, apiProject.order, '#~#');
147 // add single elements to the new list
148 titles.forEach(function(name) {
149 var values = name.split('#~#');
151 groupEntries[key].forEach(function(entry) {
156 // api overwrite with ordered list
160 // Group- and Versionlists
163 var apiGroupTitles = {};
164 var apiVersions = {};
165 apiVersions[apiProject.version] = 1;
167 $.each(api, function(index, entry) {
168 apiGroups[entry.group] = 1;
169 apiGroupTitles[entry.group] = entry.groupTitle || entry.group;
170 apiVersions[entry.version] = 1;
174 apiGroups = Object.keys(apiGroups);
178 if (apiProject.order)
179 apiGroups = sortByOrder(apiGroups, apiProject.order);
181 // sort versions DESC
182 apiVersions = Object.keys(apiVersions);
183 apiVersions.sort(semver.compare);
184 apiVersions.reverse();
187 // create Navigationlist
190 apiGroups.forEach(function(group) {
195 title: apiGroupTitles[group]
200 api.forEach(function(entry) {
201 if (entry.group === group) {
202 if (oldName !== entry.name) {
208 version: entry.version,
218 version: entry.version,
222 oldName = entry.name;
228 * Add navigation items by analyzing the HTML content and searching for h1 and h2 tags
229 * @param nav Object the navigation array
230 * @param content string the compiled HTML content
231 * @param index where to insert items
232 * @return boolean true if any good-looking (i.e. with a group identifier) <h1> tag was found
234 function add_nav(nav, content, index) {
235 var found_level1 = false;
239 var topics = content.match(/<h(1|2).*?>(.+?)<\/h(1|2)>/gi);
241 topics.forEach(function(entry) {
242 var level = entry.substring(2,3);
243 var title = entry.replace(/<.+?>/g, ''); // Remove all HTML tags for the title
244 var entry_tags = entry.match(/id="api-([^\-]+)(?:-(.+))?"/); // Find the group and name in the id property
245 var group = (entry_tags ? entry_tags[1] : null);
246 var name = (entry_tags ? entry_tags[2] : null);
247 if (level==1 && title && group) {
248 nav.splice(index, 0, {
257 if (level==2 && title && group && name) {
258 nav.splice(index, 0, {
273 // Mainmenu Header entry
274 if (apiProject.header) {
275 var found_level1 = add_nav(nav, apiProject.header.content, 0); // Add level 1 and 2 titles
276 if (!found_level1) { // If no Level 1 tags were found, make a title
280 title: (apiProject.header.title == null) ? locale.__('General') : apiProject.header.title,
286 // Mainmenu Footer entry
287 if (apiProject.footer) {
288 var last_nav_index = nav.length;
289 var found_level1 = add_nav(nav, apiProject.footer.content, nav.length); // Add level 1 and 2 titles
290 if (!found_level1 && apiProject.footer.title != null) { // If no Level 1 tags were found, make a title
291 nav.splice(last_nav_index, 0, {
294 title: apiProject.footer.title,
301 var title = apiProject.title ? apiProject.title : 'apiDoc: ' + apiProject.name + ' - ' + apiProject.version;
302 $(document).attr('title', title);
305 $('#loader').remove();
311 $('#sidenav').append( templateSidenav(fields) );
314 $('#generator').append( templateGenerator(apiProject) );
317 _.extend(apiProject, { versions: apiVersions});
318 $('#project').append( templateProject(apiProject) );
320 // render apiDoc, header/footer documentation
321 if (apiProject.header)
322 $('#header').append( templateHeader(apiProject.header) );
324 if (apiProject.footer)
325 $('#footer').append( templateFooter(apiProject.footer) );
328 // Render Sections and Articles
330 var articleVersions = {};
332 apiGroups.forEach(function(groupEntry) {
336 var title = groupEntry;
337 var description = '';
338 articleVersions[groupEntry] = {};
340 // render all articles of a group
341 api.forEach(function(entry) {
342 if(groupEntry === entry.group) {
343 if (oldName !== entry.name) {
344 // determine versions
345 api.forEach(function(versionEntry) {
346 if (groupEntry === versionEntry.group && entry.name === versionEntry.name) {
347 if ( ! articleVersions[entry.group].hasOwnProperty(entry.name) ) {
348 articleVersions[entry.group][entry.name] = [];
350 articleVersions[entry.group][entry.name].push(versionEntry.version);
355 versions: articleVersions[entry.group][entry.name]
361 versions: articleVersions[entry.group][entry.name]
365 // add prefix URL for endpoint unless it's already absolute
366 if (apiProject.url) {
367 if (fields.article.url.substr(0, 4).toLowerCase() !== 'http') {
368 fields.article.url = apiProject.url + fields.article.url;
372 addArticleSettings(fields, entry);
374 if (entry.groupTitle)
375 title = entry.groupTitle;
377 // TODO: make groupDescription compareable with older versions (not important for the moment)
378 if (entry.groupDescription)
379 description = entry.groupDescription;
382 article: templateArticle(fields),
385 aloneDisplay: apiProject.template.aloneDisplay
387 oldName = entry.name;
391 // render Section with Articles
395 description: description,
397 aloneDisplay: apiProject.template.aloneDisplay
399 content += templateSections(fields);
401 $('#sections').append( content );
403 // Bootstrap Scrollspy
404 $(this).scrollspy({ target: '#scrollingNav' });
406 // Content-Scroll on Navigation click.
407 $('.sidenav').find('a').on('click', function(e) {
409 var id = $(this).attr('href');
410 if ($(id).length > 0)
411 $('html,body').animate({ scrollTop: parseInt($(id).offset().top) }, 400);
412 window.location.hash = $(this).attr('href');
416 * Check if Parameter (sub) List has a type Field.
417 * Example: @apiSuccess varname1 No type.
418 * @apiSuccess {String} varname2 With type.
420 * @param {Object} fields
422 function _hasTypeInFields(fields) {
424 $.each(fields, function(name) {
425 result = result || _.some(fields[name], function(item) { return item.type; });
431 * On Template changes, recall plugins.
433 function initDynamic() {
435 $('button[data-toggle="popover"]').popover().click(function(e) {
439 var version = $('#version strong').html();
440 $('#sidenav li').removeClass('is-new');
441 if (apiProject.template.withCompare) {
442 $('#sidenav li[data-version=\'' + version + '\']').each(function(){
443 var group = $(this).data('group');
444 var name = $(this).data('name');
445 var length = $('#sidenav li[data-group=\'' + group + '\'][data-name=\'' + name + '\']').length;
446 var index = $('#sidenav li[data-group=\'' + group + '\'][data-name=\'' + name + '\']').index($(this));
447 if (length === 1 || index === (length - 1))
448 $(this).addClass('is-new');
453 $('.nav-tabs-examples a').click(function (e) {
457 $('.nav-tabs-examples').find('a:first').tab('show');
459 // sample header-content-type switch
460 $('.sample-header-content-type-switch').change(function () {
461 var paramName = '.' + $(this).attr('name') + '-fields';
462 var bodyName = '.' + $(this).attr('name') + '-body';
463 var selectName = 'select[name=' + $(this).attr('name') + ']';
464 if ($(this).val() == 'body-json') {
465 $(selectName).val('undefined');
466 $(this).val('body-json');
467 $(paramName).removeClass('hide');
468 $(this).parent().nextAll(paramName).first().addClass('hide');
469 $(bodyName).addClass('hide');
470 $(this).parent().nextAll(bodyName).first().removeClass('hide');
471 } else if ($(this).val() == "body-form-data") {
472 $(selectName).val('undefined');
473 $(this).val('body-form-data');
474 $(bodyName).addClass('hide');
475 $(paramName).removeClass('hide');
477 $(this).parent().nextAll(paramName).first().removeClass('hide')
478 $(this).parent().nextAll(bodyName).first().addClass('hide');
480 $(this).prev('.sample-request-switch').prop('checked', true);
483 // sample request switch
484 $('.sample-request-switch').click(function (e) {
485 var paramName = '.' + $(this).attr('name') + '-fields';
486 var bodyName = '.' + $(this).attr('name') + '-body';
487 var select = $(this).next('.' + $(this).attr('name') + '-select').val();
488 if($(this).prop("checked")){
489 if (select == 'body-json'){
490 $(this).parent().nextAll(bodyName).first().removeClass('hide');
492 $(this).parent().nextAll(paramName).first().removeClass('hide');
495 if (select == 'body-json'){
496 $(this).parent().nextAll(bodyName).first().addClass('hide');
498 $(this).parent().nextAll(paramName).first().addClass('hide');
503 if (apiProject.template.aloneDisplay){
505 $('.show-group').click(function () {
506 var apiGroup = '.' + $(this).attr('data-group') + '-group';
507 var apiGroupArticle = '.' + $(this).attr('data-group') + '-article';
508 $(".show-api-group").addClass('hide');
509 $(apiGroup).removeClass('hide');
510 $(".show-api-article").addClass('hide');
511 $(apiGroupArticle).removeClass('hide');
515 $('.show-api').click(function () {
516 var apiName = '.' + $(this).attr('data-name') + '-article';
517 var apiGroup = '.' + $(this).attr('data-group') + '-group';
518 $(".show-api-group").addClass('hide');
519 $(apiGroup).removeClass('hide');
520 $(".show-api-article").addClass('hide');
521 $(apiName).removeClass('hide');
525 // call scrollspy refresh method
526 $(window).scrollspy('refresh');
529 sampleRequest.initDynamic();
534 if (apiProject.template.aloneDisplay) {
535 var hashVal = window.location.hash;
536 if (hashVal != null && hashVal.length !== 0) {
537 $("." + hashVal.slice(1) + "-init").click();
542 // HTML-Template specific jQuery-Functions
544 // Change Main Version
545 function setMainVersion(selectedVersion) {
546 if (typeof(selectedVersion) === 'undefined') {
547 selectedVersion = $('#version strong').html();
550 $('#version strong').html(selectedVersion);
554 $('article').addClass('hide');
555 $('#sidenav li:not(.nav-fixed)').addClass('hide');
557 // show 1st equal or lower Version of each entry
558 $('article[data-version]').each(function(index) {
559 var group = $(this).data('group');
560 var name = $(this).data('name');
561 var version = $(this).data('version');
563 if (semver.lte(version, selectedVersion)) {
564 if ($('article[data-group=\'' + group + '\'][data-name=\'' + name + '\']:visible').length === 0) {
566 $('article[data-group=\'' + group + '\'][data-name=\'' + name + '\'][data-version=\'' + version + '\']').removeClass('hide');
568 $('#sidenav li[data-group=\'' + group + '\'][data-name=\'' + name + '\'][data-version=\'' + version + '\']').removeClass('hide');
569 $('#sidenav li.nav-header[data-group=\'' + group + '\']').removeClass('hide');
574 // show 1st equal or lower Version of each entry
575 $('article[data-version]').each(function(index) {
576 var group = $(this).data('group');
577 $('section#api-' + group).removeClass('hide');
578 if ($('section#api-' + group + ' article:visible').length === 0) {
579 $('section#api-' + group).addClass('hide');
581 $('section#api-' + group).removeClass('hide');
590 $('#versions li.version a').on('click', function(e) {
593 setMainVersion($(this).html());
596 // compare all article with their predecessor
597 $('#compareAllWithPredecessor').on('click', changeAllVersionCompareTo);
599 // change version of an article
600 $('article .versions li.version a').on('click', changeVersionCompareTo);
602 // compare url-parameter
603 $.urlParam = function(name) {
604 var results = new RegExp('[\\?&]' + name + '=([^&#]*)').exec(window.location.href);
605 return (results && results[1]) ? results[1] : null;
608 if ($.urlParam('compare')) {
609 // URL Paramter ?compare=1 is set
610 $('#compareAllWithPredecessor').trigger('click');
613 // Quick jump on page load to hash position.
614 // Should happen after setting the main version
615 // and after triggering the click on the compare button,
616 // as these actions modify the content
617 // and would make it jump to the wrong position or not jump at all.
618 if (window.location.hash) {
619 var id = window.location.hash;
620 if ($(id).length > 0)
621 $('html,body').animate({ scrollTop: parseInt($(id).offset().top) }, 0);
628 valueNames: [ 'nav-list-item','nav-list-url-item']
630 var endpointsList = new List('scrollingNav', options);
633 * Set initial focus to search input
635 $('#scrollingNav .sidenav-search input.search').focus();
638 * Detect ESC key to reset search
640 $(document).keyup(function(e) {
641 if (e.keyCode === 27) $('span.search-reset').click();
647 $('span.search-reset').on('click', function() {
648 $('#scrollingNav .sidenav-search input.search')
652 endpointsList.search();
656 * Change version of an article to compare it to an other version.
658 function changeVersionCompareTo(e) {
661 var $root = $(this).parents('article');
662 var selectedVersion = $(this).html();
663 var $button = $root.find('.version');
664 var currentVersion = $button.find('strong').html();
665 $button.find('strong').html(selectedVersion);
667 var group = $root.data('group');
668 var name = $root.data('name');
669 var version = $root.data('version');
671 var compareVersion = $root.data('compare-version');
673 if (compareVersion === selectedVersion)
676 if ( ! compareVersion && version == selectedVersion)
679 if (compareVersion && articleVersions[group][name][0] === selectedVersion || version === selectedVersion) {
680 // the version of the entry is set to the highest version (reset)
681 resetArticle(group, name, version);
683 var $compareToArticle = $('article[data-group=\'' + group + '\'][data-name=\'' + name + '\'][data-version=\'' + selectedVersion + '\']');
685 var sourceEntry = {};
686 var compareEntry = {};
687 $.each(apiByGroupAndName[group][name], function(index, entry) {
688 if (entry.version === version)
690 if (entry.version === selectedVersion)
691 compareEntry = entry;
695 article: sourceEntry,
696 compare: compareEntry,
697 versions: articleVersions[group][name]
701 // TODO: replace all group-name-version in template with id.
702 fields.article.id = fields.article.group + '-' + fields.article.name + '-' + fields.article.version;
703 fields.article.id = fields.article.id.replace(/\./g, '_');
705 fields.compare.id = fields.compare.group + '-' + fields.compare.name + '-' + fields.compare.version;
706 fields.compare.id = fields.compare.id.replace(/\./g, '_');
708 var entry = sourceEntry;
709 if (entry.parameter && entry.parameter.fields)
710 fields._hasTypeInParameterFields = _hasTypeInFields(entry.parameter.fields);
712 if (entry.error && entry.error.fields)
713 fields._hasTypeInErrorFields = _hasTypeInFields(entry.error.fields);
715 if (entry.success && entry.success.fields)
716 fields._hasTypeInSuccessFields = _hasTypeInFields(entry.success.fields);
718 if (entry.info && entry.info.fields)
719 fields._hasTypeInInfoFields = _hasTypeInFields(entry.info.fields);
721 var entry = compareEntry;
722 if (fields._hasTypeInParameterFields !== true && entry.parameter && entry.parameter.fields)
723 fields._hasTypeInParameterFields = _hasTypeInFields(entry.parameter.fields);
725 if (fields._hasTypeInErrorFields !== true && entry.error && entry.error.fields)
726 fields._hasTypeInErrorFields = _hasTypeInFields(entry.error.fields);
728 if (fields._hasTypeInSuccessFields !== true && entry.success && entry.success.fields)
729 fields._hasTypeInSuccessFields = _hasTypeInFields(entry.success.fields);
731 if (fields._hasTypeInInfoFields !== true && entry.info && entry.info.fields)
732 fields._hasTypeInInfoFields = _hasTypeInFields(entry.info.fields);
734 var content = templateCompareArticle(fields);
735 $root.after(content);
736 var $content = $root.next();
738 // Event on.click re-assign
739 $content.find('.versions li.version a').on('click', changeVersionCompareTo);
742 $('#sidenav li[data-group=\'' + group + '\'][data-name=\'' + name + '\'][data-version=\'' + currentVersion + '\']').addClass('has-modifications');
745 // TODO: on change main version or select the highest version re-render
752 * Compare all currently selected Versions with their predecessor.
754 function changeAllVersionCompareTo(e) {
756 $('article:visible .versions').each(function(){
757 var $root = $(this).parents('article');
758 var currentVersion = $root.data('version');
759 var $foundElement = null;
760 $(this).find('li.version a').each(function() {
761 var selectVersion = $(this).html();
762 if (selectVersion < currentVersion && ! $foundElement)
763 $foundElement = $(this);
767 $foundElement.trigger('click');
775 function sortFields(fields_object) {
776 $.each(fields_object, function (key, fields) {
778 var reversed = fields.slice().reverse()
780 var max_dot_count = Math.max.apply(null, reversed.map(function (item) {
781 return item.field.split(".").length - 1;
784 for (var dot_count = 1; dot_count <= max_dot_count; dot_count++) {
785 reversed.forEach(function (item, index) {
786 var parts = item.field.split(".");
787 if (parts.length - 1 == dot_count) {
788 var fields_names = fields.map(function (item) { return item.field; });
789 if (parts.slice(1).length >= 1) {
790 var prefix = parts.slice(0, parts.length - 1).join(".");
791 var prefix_index = fields_names.indexOf(prefix);
792 if (prefix_index > -1) {
793 fields.splice(fields_names.indexOf(item.field), 1);
794 fields.splice(prefix_index + 1, 0, item);
804 * Add article settings.
806 function addArticleSettings(fields, entry) {
808 // TODO: replace all group-name-version in template with id.
809 fields.id = fields.article.group + '-' + fields.article.name + '-' + fields.article.version;
810 fields.id = fields.id.replace(/\./g, '_');
812 if (entry.header && entry.header.fields) {
813 sortFields(entry.header.fields);
814 fields._hasTypeInHeaderFields = _hasTypeInFields(entry.header.fields);
817 if (entry.parameter && entry.parameter.fields) {
818 sortFields(entry.parameter.fields);
819 fields._hasTypeInParameterFields = _hasTypeInFields(entry.parameter.fields);
822 if (entry.error && entry.error.fields) {
823 sortFields(entry.error.fields);
824 fields._hasTypeInErrorFields = _hasTypeInFields(entry.error.fields);
827 if (entry.success && entry.success.fields) {
828 sortFields(entry.success.fields);
829 fields._hasTypeInSuccessFields = _hasTypeInFields(entry.success.fields);
832 if (entry.info && entry.info.fields) {
833 sortFields(entry.info.fields);
834 fields._hasTypeInInfoFields = _hasTypeInFields(entry.info.fields);
837 // add template settings
838 fields.template = apiProject.template;
844 function renderArticle(group, name, version) {
846 $.each(apiByGroupAndName[group][name], function(index, currentEntry) {
847 if (currentEntry.version === version)
848 entry = currentEntry;
852 versions: articleVersions[group][name]
855 addArticleSettings(fields, entry);
857 return templateArticle(fields);
861 * Render original Article and remove the current visible Article.
863 function resetArticle(group, name, version) {
864 var $root = $('article[data-group=\'' + group + '\'][data-name=\'' + name + '\']:visible');
865 var content = renderArticle(group, name, version);
867 $root.after(content);
868 var $content = $root.next();
870 // Event on.click needs to be reassigned (should actually work with on ... automatically)
871 $content.find('.versions li.version a').on('click', changeVersionCompareTo);
873 $('#sidenav li[data-group=\'' + group + '\'][data-name=\'' + name + '\'][data-version=\'' + version + '\']').removeClass('has-modifications');
880 * Return ordered entries by custom order and append not defined entries to the end.
881 * @param {String[]} elements
882 * @param {String[]} order
883 * @param {String} splitBy
884 * @return {String[]} Custom ordered list.
886 function sortByOrder(elements, order, splitBy) {
888 order.forEach (function(name) {
890 elements.forEach (function(element) {
891 var parts = element.split(splitBy);
892 var key = parts[0]; // reference keep for sorting
893 if (key == name || parts[1] == name)
894 results.push(element);
897 elements.forEach (function(key) {
902 // Append all other entries that ar not defined in order
903 elements.forEach(function(element) {
904 if (results.indexOf(element) === -1)
905 results.push(element);