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 prettify: './vendor/prettify/prettify',
12 semver: './vendor/semver.min',
13 utilsSampleRequest: './utils/send_sample_request',
14 webfontloader: './vendor/webfontloader',
15 list: './vendor/list.min'
22 exports: 'diff_match_patch'
28 deps: ['jquery', 'handlebars'],
32 exports: 'prettyPrint'
35 urlArgs: 'v=' + (new Date()).getTime(),
53 ], function($, _, locale, Handlebars, apiProject, apiData, prettyPrint, sampleRequest, semver, WebFont) {
55 // load google web fonts
58 var api = apiData.api;
63 var templateHeader = Handlebars.compile( $('#template-header').html() );
64 var templateFooter = Handlebars.compile( $('#template-footer').html() );
65 var templateArticle = Handlebars.compile( $('#template-article').html() );
66 var templateCompareArticle = Handlebars.compile( $('#template-compare-article').html() );
67 var templateGenerator = Handlebars.compile( $('#template-generator').html() );
68 var templateProject = Handlebars.compile( $('#template-project').html() );
69 var templateSections = Handlebars.compile( $('#template-sections').html() );
70 var templateSidenav = Handlebars.compile( $('#template-sidenav').html() );
73 // apiProject defaults
75 if ( ! apiProject.template)
76 apiProject.template = {};
78 if (apiProject.template.withCompare == null)
79 apiProject.template.withCompare = true;
81 if (apiProject.template.withGenerator == null)
82 apiProject.template.withGenerator = true;
84 if (apiProject.template.forceLanguage)
85 locale.setLanguage(apiProject.template.forceLanguage);
87 if (apiProject.template.aloneDisplay == null)
88 apiProject.template.aloneDisplay = false;
91 $.ajaxSetup(apiProject.template.jQueryAjaxSetup);
97 var apiByGroup = _.groupBy(api, function(entry) {
101 // grouped by group and name
102 var apiByGroupAndName = {};
103 $.each(apiByGroup, function(index, entries) {
104 apiByGroupAndName[index] = _.groupBy(entries, function(entry) {
110 // sort api within a group by title ASC and custom order
113 var umlauts = { 'ä': 'ae', 'ü': 'ue', 'ö': 'oe', 'ß': 'ss' }; // TODO: remove in version 1.0
114 $.each (apiByGroupAndName, function(index, groupEntries) {
115 // get titles from the first entry of group[].name[] (name has versioning)
117 $.each (groupEntries, function(titleName, entries) {
118 var title = entries[0].title;
119 if(title !== undefined) {
120 title.toLowerCase().replace(/[äöüß]/g, function($0) { return umlauts[$0]; });
121 titles.push(title + '#~#' + titleName); // '#~#' keep reference to titleName after sorting
128 if (apiProject.order)
129 titles = sortByOrder(titles, apiProject.order, '#~#');
131 // add single elements to the new list
132 titles.forEach(function(name) {
133 var values = name.split('#~#');
135 groupEntries[key].forEach(function(entry) {
140 // api overwrite with ordered list
144 // Group- and Versionlists
147 var apiGroupTitles = {};
148 var apiVersions = {};
149 apiVersions[apiProject.version] = 1;
151 $.each(api, function(index, entry) {
152 apiGroups[entry.group] = 1;
153 apiGroupTitles[entry.group] = entry.groupTitle || entry.group;
154 apiVersions[entry.version] = 1;
158 apiGroups = Object.keys(apiGroups);
162 if (apiProject.order)
163 apiGroups = sortByOrder(apiGroups, apiProject.order);
165 // sort versions DESC
166 apiVersions = Object.keys(apiVersions);
167 apiVersions.sort(semver.compare);
168 apiVersions.reverse();
171 // create Navigationlist
174 apiGroups.forEach(function(group) {
179 title: apiGroupTitles[group]
184 api.forEach(function(entry) {
185 if (entry.group === group) {
186 if (oldName !== entry.name) {
192 version: entry.version,
202 version: entry.version,
206 oldName = entry.name;
212 * Add navigation items by analyzing the HTML content and searching for h1 and h2 tags
213 * @param nav Object the navigation array
214 * @param content string the compiled HTML content
215 * @param index where to insert items
216 * @return boolean true if any good-looking (i.e. with a group identifier) <h1> tag was found
218 function add_nav(nav, content, index) {
219 var found_level1 = false;
223 var topics = content.match(/<h(1|2).*?>(.+?)<\/h(1|2)>/gi);
225 topics.forEach(function(entry) {
226 var level = entry.substring(2,3);
227 var title = entry.replace(/<.+?>/g, ''); // Remove all HTML tags for the title
228 var entry_tags = entry.match(/id="api-([^\-]+)(?:-(.+))?"/); // Find the group and name in the id property
229 var group = (entry_tags ? entry_tags[1] : null);
230 var name = (entry_tags ? entry_tags[2] : null);
231 if (level==1 && title && group) {
232 nav.splice(index, 0, {
241 if (level==2 && title && group && name) {
242 nav.splice(index, 0, {
257 // Mainmenu Header entry
258 if (apiProject.header) {
259 var found_level1 = add_nav(nav, apiProject.header.content, 0); // Add level 1 and 2 titles
260 if (!found_level1) { // If no Level 1 tags were found, make a title
264 title: (apiProject.header.title == null) ? locale.__('General') : apiProject.header.title,
270 // Mainmenu Footer entry
271 if (apiProject.footer) {
272 var last_nav_index = nav.length;
273 var found_level1 = add_nav(nav, apiProject.footer.content, nav.length); // Add level 1 and 2 titles
274 if (!found_level1 && apiProject.footer.title != null) { // If no Level 1 tags were found, make a title
275 nav.splice(last_nav_index, 0, {
278 title: apiProject.footer.title,
285 var title = apiProject.title ? apiProject.title : 'apiDoc: ' + apiProject.name + ' - ' + apiProject.version;
286 $(document).attr('title', title);
289 $('#loader').remove();
295 $('#sidenav').append( templateSidenav(fields) );
298 $('#generator').append( templateGenerator(apiProject) );
301 _.extend(apiProject, { versions: apiVersions});
302 $('#project').append( templateProject(apiProject) );
304 // render apiDoc, header/footer documentation
305 if (apiProject.header)
306 $('#header').append( templateHeader(apiProject.header) );
308 if (apiProject.footer)
309 $('#footer').append( templateFooter(apiProject.footer) );
312 // Render Sections and Articles
314 var articleVersions = {};
316 apiGroups.forEach(function(groupEntry) {
320 var title = groupEntry;
321 var description = '';
322 articleVersions[groupEntry] = {};
324 // render all articles of a group
325 api.forEach(function(entry) {
326 if(groupEntry === entry.group) {
327 if (oldName !== entry.name) {
328 // determine versions
329 api.forEach(function(versionEntry) {
330 if (groupEntry === versionEntry.group && entry.name === versionEntry.name) {
331 if ( ! articleVersions[entry.group].hasOwnProperty(entry.name) ) {
332 articleVersions[entry.group][entry.name] = [];
334 articleVersions[entry.group][entry.name].push(versionEntry.version);
339 versions: articleVersions[entry.group][entry.name]
345 versions: articleVersions[entry.group][entry.name]
349 // add prefix URL for endpoint unless it's already absolute
350 if (apiProject.url) {
351 if (fields.article.url.substr(0, 4).toLowerCase() !== 'http') {
352 fields.article.url = apiProject.url + fields.article.url;
356 addArticleSettings(fields, entry);
358 if (entry.groupTitle)
359 title = entry.groupTitle;
361 // TODO: make groupDescription compareable with older versions (not important for the moment)
362 if (entry.groupDescription)
363 description = entry.groupDescription;
366 article: templateArticle(fields),
369 aloneDisplay: apiProject.template.aloneDisplay
371 oldName = entry.name;
375 // render Section with Articles
379 description: description,
381 aloneDisplay: apiProject.template.aloneDisplay
383 content += templateSections(fields);
385 $('#sections').append( content );
387 // Bootstrap Scrollspy
388 $(this).scrollspy({ target: '#scrollingNav', offset: 18 });
390 // Content-Scroll on Navigation click.
391 $('.sidenav').find('a').on('click', function(e) {
393 var id = $(this).attr('href');
394 if ($(id).length > 0)
395 $('html,body').animate({ scrollTop: parseInt($(id).offset().top) }, 400);
396 window.location.hash = $(this).attr('href');
399 // Quickjump on Pageload to hash position.
400 if(window.location.hash) {
401 var id = window.location.hash;
402 if ($(id).length > 0)
403 $('html,body').animate({ scrollTop: parseInt($(id).offset().top) }, 0);
407 * Check if Parameter (sub) List has a type Field.
408 * Example: @apiSuccess varname1 No type.
409 * @apiSuccess {String} varname2 With type.
411 * @param {Object} fields
413 function _hasTypeInFields(fields) {
415 $.each(fields, function(name) {
416 result = result || _.some(fields[name], function(item) { return item.type; });
422 * On Template changes, recall plugins.
424 function initDynamic() {
426 $('button[data-toggle="popover"]').popover().click(function(e) {
430 var version = $('#version strong').html();
431 $('#sidenav li').removeClass('is-new');
432 if (apiProject.template.withCompare) {
433 $('#sidenav li[data-version=\'' + version + '\']').each(function(){
434 var group = $(this).data('group');
435 var name = $(this).data('name');
436 var length = $('#sidenav li[data-group=\'' + group + '\'][data-name=\'' + name + '\']').length;
437 var index = $('#sidenav li[data-group=\'' + group + '\'][data-name=\'' + name + '\']').index($(this));
438 if (length === 1 || index === (length - 1))
439 $(this).addClass('is-new');
444 $('.nav-tabs-examples a').click(function (e) {
448 $('.nav-tabs-examples').find('a:first').tab('show');
450 // sample header-content-type switch
451 $('.sample-header-content-type-switch').change(function () {
452 var paramName = '.' + $(this).attr('name') + '-fields';
453 var bodyName = '.' + $(this).attr('name') + '-body';
454 var selectName = 'select[name=' + $(this).attr('name') + ']';
455 if ($(this).val() == 'body-json') {
456 $(selectName).val('undefined');
457 $(this).val('body-json');
458 $(paramName).removeClass('hide');
459 $(this).parent().nextAll(paramName).first().addClass('hide');
460 $(bodyName).addClass('hide');
461 $(this).parent().nextAll(bodyName).first().removeClass('hide');
462 } else if ($(this).val() == "body-form-data") {
463 $(selectName).val('undefined');
464 $(this).val('body-form-data');
465 $(bodyName).addClass('hide');
466 $(paramName).removeClass('hide');
468 $(this).parent().nextAll(paramName).first().removeClass('hide')
469 $(this).parent().nextAll(bodyName).first().addClass('hide');
471 $(this).prev('.sample-request-switch').prop('checked', true);
474 // sample request switch
475 $('.sample-request-switch').click(function (e) {
476 var paramName = '.' + $(this).attr('name') + '-fields';
477 var bodyName = '.' + $(this).attr('name') + '-body';
478 var select = $(this).next('.' + $(this).attr('name') + '-select').val();
479 if($(this).prop("checked")){
480 if (select == 'body-json'){
481 $(this).parent().nextAll(bodyName).first().removeClass('hide');
483 $(this).parent().nextAll(paramName).first().removeClass('hide');
486 if (select == 'body-json'){
487 $(this).parent().nextAll(bodyName).first().addClass('hide');
489 $(this).parent().nextAll(paramName).first().addClass('hide');
494 if (apiProject.template.aloneDisplay){
496 $('.show-group').click(function () {
497 var apiGroup = '.' + $(this).attr('data-group') + '-group';
498 var apiGroupArticle = '.' + $(this).attr('data-group') + '-article';
499 $(".show-api-group").addClass('hide');
500 $(apiGroup).removeClass('hide');
501 $(".show-api-article").addClass('hide');
502 $(apiGroupArticle).removeClass('hide');
506 $('.show-api').click(function () {
507 var apiName = '.' + $(this).attr('data-name') + '-article';
508 var apiGroup = '.' + $(this).attr('data-group') + '-group';
509 $(".show-api-group").addClass('hide');
510 $(apiGroup).removeClass('hide');
511 $(".show-api-article").addClass('hide');
512 $(apiName).removeClass('hide');
516 // call scrollspy refresh method
517 $(window).scrollspy('refresh');
520 sampleRequest.initDynamic();
524 if (apiProject.template.aloneDisplay) {
525 var hashVal = window.location.hash;
526 if (hashVal != null && hashVal.length !== 0) {
527 $("." + hashVal.slice(1) + "-init").click();
531 // Pre- / Code-Format
535 // HTML-Template specific jQuery-Functions
537 // Change Main Version
538 $('#versions li.version a').on('click', function(e) {
541 var selectedVersion = $(this).html();
542 $('#version strong').html(selectedVersion);
545 $('article').addClass('hide');
546 $('#sidenav li:not(.nav-fixed)').addClass('hide');
548 // show 1st equal or lower Version of each entry
549 $('article[data-version]').each(function(index) {
550 var group = $(this).data('group');
551 var name = $(this).data('name');
552 var version = $(this).data('version');
554 if (semver.lte(version, selectedVersion)) {
555 if ($('article[data-group=\'' + group + '\'][data-name=\'' + name + '\']:visible').length === 0) {
557 $('article[data-group=\'' + group + '\'][data-name=\'' + name + '\'][data-version=\'' + version + '\']').removeClass('hide');
559 $('#sidenav li[data-group=\'' + group + '\'][data-name=\'' + name + '\'][data-version=\'' + version + '\']').removeClass('hide');
560 $('#sidenav li.nav-header[data-group=\'' + group + '\']').removeClass('hide');
565 // show 1st equal or lower Version of each entry
566 $('article[data-version]').each(function(index) {
567 var group = $(this).data('group');
568 $('section#api-' + group).removeClass('hide');
569 if ($('section#api-' + group + ' article:visible').length === 0) {
570 $('section#api-' + group).addClass('hide');
572 $('section#api-' + group).removeClass('hide');
580 // compare all article with their predecessor
581 $('#compareAllWithPredecessor').on('click', changeAllVersionCompareTo);
583 // change version of an article
584 $('article .versions li.version a').on('click', changeVersionCompareTo);
586 // compare url-parameter
587 $.urlParam = function(name) {
588 var results = new RegExp('[\\?&]' + name + '=([^&#]*)').exec(window.location.href);
589 return (results && results[1]) ? results[1] : null;
592 if ($.urlParam('compare')) {
593 // URL Paramter ?compare=1 is set
594 $('#compareAllWithPredecessor').trigger('click');
596 if (window.location.hash) {
597 var id = window.location.hash;
598 $('html,body').animate({ scrollTop: parseInt($(id).offset().top) - 18 }, 0);
606 valueNames: [ 'nav-list-item','nav-list-url-item']
608 var endpointsList = new List('scrollingNav', options);
611 * Set initial focus to search input
613 $('#scrollingNav .sidenav-search input.search').focus();
616 * Detect ESC key to reset search
618 $(document).keyup(function(e) {
619 if (e.keyCode === 27) $('span.search-reset').click();
625 $('span.search-reset').on('click', function() {
626 $('#scrollingNav .sidenav-search input.search')
630 endpointsList.search();
634 * Change version of an article to compare it to an other version.
636 function changeVersionCompareTo(e) {
639 var $root = $(this).parents('article');
640 var selectedVersion = $(this).html();
641 var $button = $root.find('.version');
642 var currentVersion = $button.find('strong').html();
643 $button.find('strong').html(selectedVersion);
645 var group = $root.data('group');
646 var name = $root.data('name');
647 var version = $root.data('version');
649 var compareVersion = $root.data('compare-version');
651 if (compareVersion === selectedVersion)
654 if ( ! compareVersion && version == selectedVersion)
657 if (compareVersion && articleVersions[group][name][0] === selectedVersion || version === selectedVersion) {
658 // the version of the entry is set to the highest version (reset)
659 resetArticle(group, name, version);
661 var $compareToArticle = $('article[data-group=\'' + group + '\'][data-name=\'' + name + '\'][data-version=\'' + selectedVersion + '\']');
663 var sourceEntry = {};
664 var compareEntry = {};
665 $.each(apiByGroupAndName[group][name], function(index, entry) {
666 if (entry.version === version)
668 if (entry.version === selectedVersion)
669 compareEntry = entry;
673 article: sourceEntry,
674 compare: compareEntry,
675 versions: articleVersions[group][name]
679 // TODO: replace all group-name-version in template with id.
680 fields.article.id = fields.article.group + '-' + fields.article.name + '-' + fields.article.version;
681 fields.article.id = fields.article.id.replace(/\./g, '_');
683 fields.compare.id = fields.compare.group + '-' + fields.compare.name + '-' + fields.compare.version;
684 fields.compare.id = fields.compare.id.replace(/\./g, '_');
686 var entry = sourceEntry;
687 if (entry.parameter && entry.parameter.fields)
688 fields._hasTypeInParameterFields = _hasTypeInFields(entry.parameter.fields);
690 if (entry.error && entry.error.fields)
691 fields._hasTypeInErrorFields = _hasTypeInFields(entry.error.fields);
693 if (entry.success && entry.success.fields)
694 fields._hasTypeInSuccessFields = _hasTypeInFields(entry.success.fields);
696 if (entry.info && entry.info.fields)
697 fields._hasTypeInInfoFields = _hasTypeInFields(entry.info.fields);
699 var entry = compareEntry;
700 if (fields._hasTypeInParameterFields !== true && entry.parameter && entry.parameter.fields)
701 fields._hasTypeInParameterFields = _hasTypeInFields(entry.parameter.fields);
703 if (fields._hasTypeInErrorFields !== true && entry.error && entry.error.fields)
704 fields._hasTypeInErrorFields = _hasTypeInFields(entry.error.fields);
706 if (fields._hasTypeInSuccessFields !== true && entry.success && entry.success.fields)
707 fields._hasTypeInSuccessFields = _hasTypeInFields(entry.success.fields);
709 if (fields._hasTypeInInfoFields !== true && entry.info && entry.info.fields)
710 fields._hasTypeInInfoFields = _hasTypeInFields(entry.info.fields);
712 var content = templateCompareArticle(fields);
713 $root.after(content);
714 var $content = $root.next();
716 // Event on.click re-assign
717 $content.find('.versions li.version a').on('click', changeVersionCompareTo);
720 $('#sidenav li[data-group=\'' + group + '\'][data-name=\'' + name + '\'][data-version=\'' + currentVersion + '\']').addClass('has-modifications');
723 // TODO: on change main version or select the highest version re-render
730 * Compare all currently selected Versions with their predecessor.
732 function changeAllVersionCompareTo(e) {
734 $('article:visible .versions').each(function(){
735 var $root = $(this).parents('article');
736 var currentVersion = $root.data('version');
737 var $foundElement = null;
738 $(this).find('li.version a').each(function() {
739 var selectVersion = $(this).html();
740 if (selectVersion < currentVersion && ! $foundElement)
741 $foundElement = $(this);
745 $foundElement.trigger('click');
753 function sortFields(fields_object) {
754 $.each(fields_object, function (key, fields) {
756 var reversed = fields.slice().reverse()
758 var max_dot_count = Math.max.apply(null, reversed.map(function (item) {
759 return item.field.split(".").length - 1;
762 for (var dot_count = 1; dot_count <= max_dot_count; dot_count++) {
763 reversed.forEach(function (item, index) {
764 var parts = item.field.split(".");
765 if (parts.length - 1 == dot_count) {
766 var fields_names = fields.map(function (item) { return item.field; });
767 if (parts.slice(1).length >= 1) {
768 var prefix = parts.slice(0, parts.length - 1).join(".");
769 var prefix_index = fields_names.indexOf(prefix);
770 if (prefix_index > -1) {
771 fields.splice(fields_names.indexOf(item.field), 1);
772 fields.splice(prefix_index + 1, 0, item);
782 * Add article settings.
784 function addArticleSettings(fields, entry) {
786 // TODO: replace all group-name-version in template with id.
787 fields.id = fields.article.group + '-' + fields.article.name + '-' + fields.article.version;
788 fields.id = fields.id.replace(/\./g, '_');
790 if (entry.header && entry.header.fields) {
791 sortFields(entry.header.fields);
792 fields._hasTypeInHeaderFields = _hasTypeInFields(entry.header.fields);
795 if (entry.parameter && entry.parameter.fields) {
796 sortFields(entry.parameter.fields);
797 fields._hasTypeInParameterFields = _hasTypeInFields(entry.parameter.fields);
800 if (entry.error && entry.error.fields) {
801 sortFields(entry.error.fields);
802 fields._hasTypeInErrorFields = _hasTypeInFields(entry.error.fields);
805 if (entry.success && entry.success.fields) {
806 sortFields(entry.success.fields);
807 fields._hasTypeInSuccessFields = _hasTypeInFields(entry.success.fields);
810 if (entry.info && entry.info.fields) {
811 sortFields(entry.info.fields);
812 fields._hasTypeInInfoFields = _hasTypeInFields(entry.info.fields);
815 // add template settings
816 fields.template = apiProject.template;
822 function renderArticle(group, name, version) {
824 $.each(apiByGroupAndName[group][name], function(index, currentEntry) {
825 if (currentEntry.version === version)
826 entry = currentEntry;
830 versions: articleVersions[group][name]
833 addArticleSettings(fields, entry);
835 return templateArticle(fields);
839 * Render original Article and remove the current visible Article.
841 function resetArticle(group, name, version) {
842 var $root = $('article[data-group=\'' + group + '\'][data-name=\'' + name + '\']:visible');
843 var content = renderArticle(group, name, version);
845 $root.after(content);
846 var $content = $root.next();
848 // Event on.click needs to be reassigned (should actually work with on ... automatically)
849 $content.find('.versions li.version a').on('click', changeVersionCompareTo);
851 $('#sidenav li[data-group=\'' + group + '\'][data-name=\'' + name + '\'][data-version=\'' + version + '\']').removeClass('has-modifications');
860 function loadGoogleFontCss() {
864 $(window).scrollspy('refresh')
867 families: ['Source Code Pro', 'Source Sans Pro:n4,n6,n7']
873 * Return ordered entries by custom order and append not defined entries to the end.
874 * @param {String[]} elements
875 * @param {String[]} order
876 * @param {String} splitBy
877 * @return {String[]} Custom ordered list.
879 function sortByOrder(elements, order, splitBy) {
881 order.forEach (function(name) {
883 elements.forEach (function(element) {
884 var parts = element.split(splitBy);
885 var key = parts[0]; // reference keep for sorting
886 if (key == name || parts[1] == name)
887 results.push(element);
890 elements.forEach (function(key) {
895 // Append all other entries that ar not defined in order
896 elements.forEach(function(element) {
897 if (results.indexOf(element) === -1)
898 results.push(element);