Built motion from commit 10af8726.|2.6.34
[motion2.git] / apidoc / main.js
1 require.config({
2     paths: {
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',
18     },
19     shim: {
20         bootstrap: {
21             deps: ['jquery']
22         },
23         diffMatchPatch: {
24             exports: 'diff_match_patch'
25         },
26         handlebars: {
27             exports: 'Handlebars'
28         },
29         handlebarsExtended: {
30             deps: ['jquery', 'handlebars'],
31             exports: 'Handlebars'
32         },
33         prismjs: {
34             exports: 'Prism'
35         },
36     },
37     urlArgs: 'v=' + (new Date()).getTime(),
38     waitSeconds: 150
39 });
40
41 require([
42     'jquery',
43     'lodash',
44     'locales',
45     'handlebarsExtended',
46     'apiProject',
47     'apiData',
48     'prismjs',
49     'utilsSampleRequest',
50     'semver',
51     'webfontloader',
52     'bootstrap',
53     'pathToRegexp',
54     'list'
55 ], function($, _, locale, Handlebars, apiProject, apiData, Prism, sampleRequest, semver, WebFont) {
56
57     // Load google web fonts.
58     WebFont.load({
59         active: function() {
60             // Only init after fonts are loaded.
61             init($, _, locale, Handlebars, apiProject, apiData, Prism, sampleRequest, semver);
62         },
63         inactive: function() {
64             // Run init, even if loading fonts fails
65             init($, _, locale, Handlebars, apiProject, apiData, Prism, sampleRequest, semver);
66         },
67         google: {
68             families: ['Source Code Pro', 'Source Sans Pro:n4,n6,n7']
69         }
70     });
71 });
72
73 function init($, _, locale, Handlebars, apiProject, apiData, Prism, sampleRequest, semver) {
74     var api = apiData.api;
75
76     //
77     // Templates
78     //
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() );
87
88     //
89     // apiProject defaults
90     //
91     if ( ! apiProject.template)
92         apiProject.template = {};
93
94     if (apiProject.template.withCompare == null)
95         apiProject.template.withCompare = true;
96
97     if (apiProject.template.withGenerator == null)
98         apiProject.template.withGenerator = true;
99
100     if (apiProject.template.forceLanguage)
101         locale.setLanguage(apiProject.template.forceLanguage);
102
103     if (apiProject.template.aloneDisplay == null)
104         apiProject.template.aloneDisplay = false;
105
106     // Setup jQuery Ajax
107     $.ajaxSetup(apiProject.template.jQueryAjaxSetup);
108
109     //
110     // Data transform
111     //
112     // grouped by group
113     var apiByGroup = _.groupBy(api, function(entry) {
114         return entry.group;
115     });
116
117     // grouped by group and name
118     var apiByGroupAndName = {};
119     $.each(apiByGroup, function(index, entries) {
120         apiByGroupAndName[index] = _.groupBy(entries, function(entry) {
121             return entry.name;
122         });
123     });
124
125     //
126     // sort api within a group by title ASC and custom order
127     //
128     var newList = [];
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)
132         var titles = [];
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
138             }
139         });
140         // sort by name ASC
141         titles.sort();
142
143         // custom order
144         if (apiProject.order)
145             titles = sortByOrder(titles, apiProject.order, '#~#');
146
147         // add single elements to the new list
148         titles.forEach(function(name) {
149             var values = name.split('#~#');
150             var key = values[1];
151             groupEntries[key].forEach(function(entry) {
152                 newList.push(entry);
153             });
154         });
155     });
156     // api overwrite with ordered list
157     api = newList;
158
159     //
160     // Group- and Versionlists
161     //
162     var apiGroups = {};
163     var apiGroupTitles = {};
164     var apiVersions = {};
165     apiVersions[apiProject.version] = 1;
166
167     $.each(api, function(index, entry) {
168         apiGroups[entry.group] = 1;
169         apiGroupTitles[entry.group] = entry.groupTitle || entry.group;
170         apiVersions[entry.version] = 1;
171     });
172
173     // sort groups
174     apiGroups = Object.keys(apiGroups);
175     apiGroups.sort();
176
177     // custom order
178     if (apiProject.order)
179         apiGroups = sortByOrder(apiGroups, apiProject.order);
180
181     // sort versions DESC
182     apiVersions = Object.keys(apiVersions);
183     apiVersions.sort(semver.compare);
184     apiVersions.reverse();
185
186     //
187     // create Navigationlist
188     //
189     var nav = [];
190     apiGroups.forEach(function(group) {
191         // Mainmenu entry
192         nav.push({
193             group: group,
194             isHeader: true,
195             title: apiGroupTitles[group]
196         });
197
198         // Submenu
199         var oldName = '';
200         api.forEach(function(entry) {
201             if (entry.group === group) {
202                 if (oldName !== entry.name) {
203                     nav.push({
204                         title: entry.title,
205                         group: group,
206                         name: entry.name,
207                         type: entry.type,
208                         version: entry.version,
209                         url: entry.url
210                     });
211                 } else {
212                     nav.push({
213                         title: entry.title,
214                         group: group,
215                         hidden: true,
216                         name: entry.name,
217                         type: entry.type,
218                         version: entry.version,
219                         url: entry.url
220                     });
221                 }
222                 oldName = entry.name;
223             }
224         });
225     });
226
227     /**
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
233      */
234     function add_nav(nav, content, index) {
235         var found_level1 = false;
236         if ( ! content) {
237           return found_level1;
238         }
239         var topics = content.match(/<h(1|2).*?>(.+?)<\/h(1|2)>/gi);
240         if ( topics ) {
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, {
249                       group: group,
250                       isHeader: true,
251                       title: title,
252                       isFixed: true
253                   });
254                   index++;
255                   found_level1 = true;
256               }
257               if (level==2 && title && group && name)    {
258                   nav.splice(index, 0, {
259                       group: group,
260                       name: name,
261                       isHeader: false,
262                       title: title,
263                       isFixed: false,
264                       version: '1.0'
265                   });
266                   index++;
267               }
268           });
269         }
270         return found_level1;
271     }
272
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
277             nav.unshift({
278                 group: '_',
279                 isHeader: true,
280                 title: (apiProject.header.title == null) ? locale.__('General') : apiProject.header.title,
281                 isFixed: true
282             });
283         }
284     }
285
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, {
292                 group: '_footer',
293                 isHeader: true,
294                 title: apiProject.footer.title,
295                 isFixed: true
296             });
297         }
298     }
299
300     // render pagetitle
301     var title = apiProject.title ? apiProject.title : 'apiDoc: ' + apiProject.name + ' - ' + apiProject.version;
302     $(document).attr('title', title);
303
304     // remove loader
305     $('#loader').remove();
306
307     // render sidenav
308     var fields = {
309         nav: nav
310     };
311     $('#sidenav').append( templateSidenav(fields) );
312
313     // render Generator
314     $('#generator').append( templateGenerator(apiProject) );
315
316     // render Project
317     _.extend(apiProject, { versions: apiVersions});
318     $('#project').append( templateProject(apiProject) );
319
320     // render apiDoc, header/footer documentation
321     if (apiProject.header)
322         $('#header').append( templateHeader(apiProject.header) );
323
324     if (apiProject.footer)
325         $('#footer').append( templateFooter(apiProject.footer) );
326
327     //
328     // Render Sections and Articles
329     //
330     var articleVersions = {};
331     var content = '';
332     apiGroups.forEach(function(groupEntry) {
333         var articles = [];
334         var oldName = '';
335         var fields = {};
336         var title = groupEntry;
337         var description = '';
338         articleVersions[groupEntry] = {};
339
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] = [];
349                             }
350                             articleVersions[entry.group][entry.name].push(versionEntry.version);
351                         }
352                     });
353                     fields = {
354                         article: entry,
355                         versions: articleVersions[entry.group][entry.name]
356                     };
357                 } else {
358                     fields = {
359                         article: entry,
360                         hidden: true,
361                         versions: articleVersions[entry.group][entry.name]
362                     };
363                 }
364
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;
369                     }
370                 }
371
372                 addArticleSettings(fields, entry);
373
374                 if (entry.groupTitle)
375                     title = entry.groupTitle;
376
377                 // TODO: make groupDescription compareable with older versions (not important for the moment)
378                 if (entry.groupDescription)
379                     description = entry.groupDescription;
380
381                 articles.push({
382                     article: templateArticle(fields),
383                     group: entry.group,
384                     name: entry.name,
385                     aloneDisplay: apiProject.template.aloneDisplay
386                 });
387                 oldName = entry.name;
388             }
389         });
390
391         // render Section with Articles
392         var fields = {
393             group: groupEntry,
394             title: title,
395             description: description,
396             articles: articles,
397             aloneDisplay: apiProject.template.aloneDisplay
398         };
399         content += templateSections(fields);
400     });
401     $('#sections').append( content );
402
403     // Bootstrap Scrollspy
404     $(this).scrollspy({ target: '#scrollingNav' });
405
406     // Content-Scroll on Navigation click.
407     $('.sidenav').find('a').on('click', function(e) {
408         e.preventDefault();
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');
413     });
414
415     /**
416      * Check if Parameter (sub) List has a type Field.
417      * Example: @apiSuccess          varname1 No type.
418      *          @apiSuccess {String} varname2 With type.
419      *
420      * @param {Object} fields
421      */
422     function _hasTypeInFields(fields) {
423         var result = false;
424         $.each(fields, function(name) {
425             result = result || _.some(fields[name], function(item) { return item.type; });
426         });
427         return result;
428     }
429
430     /**
431      * On Template changes, recall plugins.
432      */
433     function initDynamic() {
434         // Bootstrap popover
435         $('button[data-toggle="popover"]').popover().click(function(e) {
436             e.preventDefault();
437         });
438
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');
449             });
450         }
451
452         // tabs
453         $('.nav-tabs-examples a').click(function (e) {
454             e.preventDefault();
455             $(this).tab('show');
456         });
457         $('.nav-tabs-examples').find('a:first').tab('show');
458
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');
476             } else {
477                 $(this).parent().nextAll(paramName).first().removeClass('hide')
478                 $(this).parent().nextAll(bodyName).first().addClass('hide');
479             }
480             $(this).prev('.sample-request-switch').prop('checked', true);
481         });
482
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');
491                 }else {
492                     $(this).parent().nextAll(paramName).first().removeClass('hide');
493                 }
494             }else {
495                 if (select == 'body-json'){
496                     $(this).parent().nextAll(bodyName).first().addClass('hide');
497                 }else {
498                     $(this).parent().nextAll(paramName).first().addClass('hide');
499                 }
500             }
501         });
502
503         if (apiProject.template.aloneDisplay){
504             //show group
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');
512             });
513
514             //show api
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');
522             });
523         }
524
525         // call scrollspy refresh method
526         $(window).scrollspy('refresh');
527
528         // init modules
529         sampleRequest.initDynamic();
530         Prism.highlightAll()
531     }
532     initDynamic();
533
534     if (apiProject.template.aloneDisplay) {
535         var hashVal = window.location.hash;
536         if (hashVal != null && hashVal.length !== 0) {
537             $("." + hashVal.slice(1) + "-init").click();
538         }
539     }
540
541     //
542     // HTML-Template specific jQuery-Functions
543     //
544     // Change Main Version
545     function setMainVersion(selectedVersion) {
546         if (typeof(selectedVersion) === 'undefined') {
547             selectedVersion = $('#version strong').html();
548         }
549         else {
550             $('#version strong').html(selectedVersion);
551         }
552
553         // hide all
554         $('article').addClass('hide');
555         $('#sidenav li:not(.nav-fixed)').addClass('hide');
556
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');
562
563             if (semver.lte(version, selectedVersion)) {
564                 if ($('article[data-group=\'' + group + '\'][data-name=\'' + name + '\']:visible').length === 0) {
565                     // enable Article
566                     $('article[data-group=\'' + group + '\'][data-name=\'' + name + '\'][data-version=\'' + version + '\']').removeClass('hide');
567                     // enable Navigation
568                     $('#sidenav li[data-group=\'' + group + '\'][data-name=\'' + name + '\'][data-version=\'' + version + '\']').removeClass('hide');
569                     $('#sidenav li.nav-header[data-group=\'' + group + '\']').removeClass('hide');
570                 }
571             }
572         });
573
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');
580             } else {
581                 $('section#api-' + group).removeClass('hide');
582             }
583         });
584
585         initDynamic();
586         return;
587     }
588     setMainVersion();
589
590     $('#versions li.version a').on('click', function(e) {
591         e.preventDefault();
592
593         setMainVersion($(this).html());
594     });
595
596     // compare all article with their predecessor
597     $('#compareAllWithPredecessor').on('click', changeAllVersionCompareTo);
598
599     // change version of an article
600     $('article .versions li.version a').on('click', changeVersionCompareTo);
601
602     // compare url-parameter
603     $.urlParam = function(name) {
604         var results = new RegExp('[\\?&amp;]' + name + '=([^&amp;#]*)').exec(window.location.href);
605         return (results && results[1]) ? results[1] : null;
606     };
607
608     if ($.urlParam('compare')) {
609         // URL Paramter ?compare=1 is set
610         $('#compareAllWithPredecessor').trigger('click');
611     }
612
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);
622     }
623
624     /**
625      * Initialize search
626      */
627     var options = {
628       valueNames: [ 'nav-list-item','nav-list-url-item']
629     };
630     var endpointsList = new List('scrollingNav', options);
631
632     /**
633      * Set initial focus to search input
634      */
635     $('#scrollingNav .sidenav-search input.search').focus();
636
637     /**
638      * Detect ESC key to reset search
639      */
640     $(document).keyup(function(e) {
641       if (e.keyCode === 27) $('span.search-reset').click();
642     });
643
644     /**
645      * Search reset
646      */
647     $('span.search-reset').on('click', function() {
648       $('#scrollingNav .sidenav-search input.search')
649         .val("")
650         .focus()
651       ;
652       endpointsList.search();
653     });
654
655     /**
656      * Change version of an article to compare it to an other version.
657      */
658     function changeVersionCompareTo(e) {
659         e.preventDefault();
660
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);
666
667         var group = $root.data('group');
668         var name = $root.data('name');
669         var version = $root.data('version');
670
671         var compareVersion = $root.data('compare-version');
672
673         if (compareVersion === selectedVersion)
674             return;
675
676         if ( ! compareVersion && version == selectedVersion)
677             return;
678
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);
682         } else {
683             var $compareToArticle = $('article[data-group=\'' + group + '\'][data-name=\'' + name + '\'][data-version=\'' + selectedVersion + '\']');
684
685             var sourceEntry = {};
686             var compareEntry = {};
687             $.each(apiByGroupAndName[group][name], function(index, entry) {
688                 if (entry.version === version)
689                     sourceEntry = entry;
690                 if (entry.version === selectedVersion)
691                     compareEntry = entry;
692             });
693
694             var fields = {
695                 article: sourceEntry,
696                 compare: compareEntry,
697                 versions: articleVersions[group][name]
698             };
699
700             // add unique id
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, '_');
704
705             fields.compare.id = fields.compare.group + '-' + fields.compare.name + '-' + fields.compare.version;
706             fields.compare.id = fields.compare.id.replace(/\./g, '_');
707
708             var entry = sourceEntry;
709             if (entry.parameter && entry.parameter.fields)
710                 fields._hasTypeInParameterFields = _hasTypeInFields(entry.parameter.fields);
711
712             if (entry.error && entry.error.fields)
713                 fields._hasTypeInErrorFields = _hasTypeInFields(entry.error.fields);
714
715             if (entry.success && entry.success.fields)
716                 fields._hasTypeInSuccessFields = _hasTypeInFields(entry.success.fields);
717
718             if (entry.info && entry.info.fields)
719                 fields._hasTypeInInfoFields = _hasTypeInFields(entry.info.fields);
720
721             var entry = compareEntry;
722             if (fields._hasTypeInParameterFields !== true && entry.parameter && entry.parameter.fields)
723                 fields._hasTypeInParameterFields = _hasTypeInFields(entry.parameter.fields);
724
725             if (fields._hasTypeInErrorFields !== true && entry.error && entry.error.fields)
726                 fields._hasTypeInErrorFields = _hasTypeInFields(entry.error.fields);
727
728             if (fields._hasTypeInSuccessFields !== true && entry.success && entry.success.fields)
729                 fields._hasTypeInSuccessFields = _hasTypeInFields(entry.success.fields);
730
731             if (fields._hasTypeInInfoFields !== true && entry.info && entry.info.fields)
732                 fields._hasTypeInInfoFields = _hasTypeInFields(entry.info.fields);
733
734             var content = templateCompareArticle(fields);
735             $root.after(content);
736             var $content = $root.next();
737
738             // Event on.click re-assign
739             $content.find('.versions li.version a').on('click', changeVersionCompareTo);
740
741             // select navigation
742             $('#sidenav li[data-group=\'' + group + '\'][data-name=\'' + name + '\'][data-version=\'' + currentVersion + '\']').addClass('has-modifications');
743
744             $root.remove();
745             // TODO: on change main version or select the highest version re-render
746         }
747
748         initDynamic();
749     }
750
751     /**
752      * Compare all currently selected Versions with their predecessor.
753      */
754     function changeAllVersionCompareTo(e) {
755         e.preventDefault();
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);
764             });
765
766             if($foundElement)
767                 $foundElement.trigger('click');
768         });
769         initDynamic();
770     }
771
772     /**
773      * Sort the fields.
774      */
775     function sortFields(fields_object) {
776         $.each(fields_object, function (key, fields) {
777
778             var reversed = fields.slice().reverse()
779
780             var max_dot_count = Math.max.apply(null, reversed.map(function (item) {
781                 return item.field.split(".").length - 1;
782             }))
783
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);
795                             }
796                         }
797                     }
798                 });
799             }
800         });
801     }
802
803     /**
804      * Add article settings.
805      */
806     function addArticleSettings(fields, entry) {
807         // add unique id
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, '_');
811
812         if (entry.header && entry.header.fields) {
813             sortFields(entry.header.fields);
814             fields._hasTypeInHeaderFields = _hasTypeInFields(entry.header.fields);
815         }
816
817         if (entry.parameter && entry.parameter.fields) {
818             sortFields(entry.parameter.fields);
819             fields._hasTypeInParameterFields = _hasTypeInFields(entry.parameter.fields);
820         }
821
822         if (entry.error && entry.error.fields) {
823             sortFields(entry.error.fields);
824             fields._hasTypeInErrorFields = _hasTypeInFields(entry.error.fields);
825         }
826
827         if (entry.success && entry.success.fields) {
828             sortFields(entry.success.fields);
829             fields._hasTypeInSuccessFields = _hasTypeInFields(entry.success.fields);
830         }
831
832         if (entry.info && entry.info.fields) {
833             sortFields(entry.info.fields);
834             fields._hasTypeInInfoFields = _hasTypeInFields(entry.info.fields);
835         }
836
837         // add template settings
838         fields.template = apiProject.template;
839     }
840
841     /**
842      * Render Article.
843      */
844     function renderArticle(group, name, version) {
845         var entry = {};
846         $.each(apiByGroupAndName[group][name], function(index, currentEntry) {
847             if (currentEntry.version === version)
848                 entry = currentEntry;
849         });
850         var fields = {
851             article: entry,
852             versions: articleVersions[group][name]
853         };
854
855         addArticleSettings(fields, entry);
856
857         return templateArticle(fields);
858     }
859
860     /**
861      * Render original Article and remove the current visible Article.
862      */
863     function resetArticle(group, name, version) {
864         var $root = $('article[data-group=\'' + group + '\'][data-name=\'' + name + '\']:visible');
865         var content = renderArticle(group, name, version);
866
867         $root.after(content);
868         var $content = $root.next();
869
870         // Event on.click needs to be reassigned (should actually work with on ... automatically)
871         $content.find('.versions li.version a').on('click', changeVersionCompareTo);
872
873         $('#sidenav li[data-group=\'' + group + '\'][data-name=\'' + name + '\'][data-version=\'' + version + '\']').removeClass('has-modifications');
874
875         $root.remove();
876         return;
877     }
878
879     /**
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.
885      */
886     function sortByOrder(elements, order, splitBy) {
887         var results = [];
888         order.forEach (function(name) {
889             if (splitBy)
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);
895                 });
896             else
897                 elements.forEach (function(key) {
898                     if (key == name)
899                         results.push(name);
900                 });
901         });
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);
906         });
907         return results;
908     }
909     Prism.highlightAll()
910 }