Built motion from commit (unavailable).|2.5.14
[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         prettify: './vendor/prettify/prettify',
12         semver: './vendor/semver.min',
13         utilsSampleRequest: './utils/send_sample_request',
14         webfontloader: './vendor/webfontloader',
15         list: './vendor/list.min'
16     },
17     shim: {
18         bootstrap: {
19             deps: ['jquery']
20         },
21         diffMatchPatch: {
22             exports: 'diff_match_patch'
23         },
24         handlebars: {
25             exports: 'Handlebars'
26         },
27         handlebarsExtended: {
28             deps: ['jquery', 'handlebars'],
29             exports: 'Handlebars'
30         },
31         prettify: {
32             exports: 'prettyPrint'
33         }
34     },
35     urlArgs: 'v=' + (new Date()).getTime(),
36     waitSeconds: 15
37 });
38
39 require([
40     'jquery',
41     'lodash',
42     'locales',
43     'handlebarsExtended',
44     './api_project.js',
45     './api_data.js',
46     'prettify',
47     'utilsSampleRequest',
48     'semver',
49     'webfontloader',
50     'bootstrap',
51     'pathToRegexp',
52     'list'
53 ], function($, _, locale, Handlebars, apiProject, apiData, prettyPrint, sampleRequest, semver, WebFont) {
54
55     // load google web fonts
56     loadGoogleFontCss();
57
58     var api = apiData.api;
59
60     //
61     // Templates
62     //
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() );
71
72     //
73     // apiProject defaults
74     //
75     if ( ! apiProject.template)
76         apiProject.template = {};
77
78     if (apiProject.template.withCompare == null)
79         apiProject.template.withCompare = true;
80
81     if (apiProject.template.withGenerator == null)
82         apiProject.template.withGenerator = true;
83
84     if (apiProject.template.forceLanguage)
85         locale.setLanguage(apiProject.template.forceLanguage);
86
87     if (apiProject.template.aloneDisplay == null)
88         apiProject.template.aloneDisplay = false;
89
90     // Setup jQuery Ajax
91     $.ajaxSetup(apiProject.template.jQueryAjaxSetup);
92
93     //
94     // Data transform
95     //
96     // grouped by group
97     var apiByGroup = _.groupBy(api, function(entry) {
98         return entry.group;
99     });
100
101     // grouped by group and name
102     var apiByGroupAndName = {};
103     $.each(apiByGroup, function(index, entries) {
104         apiByGroupAndName[index] = _.groupBy(entries, function(entry) {
105             return entry.name;
106         });
107     });
108
109     //
110     // sort api within a group by title ASC and custom order
111     //
112     var newList = [];
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)
116         var titles = [];
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
122             }
123         });
124         // sort by name ASC
125         titles.sort();
126
127         // custom order
128         if (apiProject.order)
129             titles = sortByOrder(titles, apiProject.order, '#~#');
130
131         // add single elements to the new list
132         titles.forEach(function(name) {
133             var values = name.split('#~#');
134             var key = values[1];
135             groupEntries[key].forEach(function(entry) {
136                 newList.push(entry);
137             });
138         });
139     });
140     // api overwrite with ordered list
141     api = newList;
142
143     //
144     // Group- and Versionlists
145     //
146     var apiGroups = {};
147     var apiGroupTitles = {};
148     var apiVersions = {};
149     apiVersions[apiProject.version] = 1;
150
151     $.each(api, function(index, entry) {
152         apiGroups[entry.group] = 1;
153         apiGroupTitles[entry.group] = entry.groupTitle || entry.group;
154         apiVersions[entry.version] = 1;
155     });
156
157     // sort groups
158     apiGroups = Object.keys(apiGroups);
159     apiGroups.sort();
160
161     // custom order
162     if (apiProject.order)
163         apiGroups = sortByOrder(apiGroups, apiProject.order);
164
165     // sort versions DESC
166     apiVersions = Object.keys(apiVersions);
167     apiVersions.sort(semver.compare);
168     apiVersions.reverse();
169
170     //
171     // create Navigationlist
172     //
173     var nav = [];
174     apiGroups.forEach(function(group) {
175         // Mainmenu entry
176         nav.push({
177             group: group,
178             isHeader: true,
179             title: apiGroupTitles[group]
180         });
181
182         // Submenu
183         var oldName = '';
184         api.forEach(function(entry) {
185             if (entry.group === group) {
186                 if (oldName !== entry.name) {
187                     nav.push({
188                         title: entry.title,
189                         group: group,
190                         name: entry.name,
191                         type: entry.type,
192                         version: entry.version,
193                         url: entry.url
194                     });
195                 } else {
196                     nav.push({
197                         title: entry.title,
198                         group: group,
199                         hidden: true,
200                         name: entry.name,
201                         type: entry.type,
202                         version: entry.version,
203                         url: entry.url
204                     });
205                 }
206                 oldName = entry.name;
207             }
208         });
209     });
210
211     /**
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
217      */
218     function add_nav(nav, content, index) {
219         var found_level1 = false;
220         if ( ! content) {
221           return found_level1;
222         }
223         var topics = content.match(/<h(1|2).*?>(.+?)<\/h(1|2)>/gi);
224         if ( topics ) {
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, {
233                       group: group,
234                       isHeader: true,
235                       title: title,
236                       isFixed: true
237                   });
238                   index++;
239                   found_level1 = true;
240               }
241               if (level==2 && title && group && name)    {
242                   nav.splice(index, 0, {
243                       group: group,
244                       name: name,
245                       isHeader: false,
246                       title: title,
247                       isFixed: false,
248                       version: '1.0'
249                   });
250                   index++;
251               }
252           });
253         }
254         return found_level1;
255     }
256
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
261             nav.unshift({
262                 group: '_',
263                 isHeader: true,
264                 title: (apiProject.header.title == null) ? locale.__('General') : apiProject.header.title,
265                 isFixed: true
266             });
267         }
268     }
269
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, {
276                 group: '_footer',
277                 isHeader: true,
278                 title: apiProject.footer.title,
279                 isFixed: true
280             });
281         }
282     }
283
284     // render pagetitle
285     var title = apiProject.title ? apiProject.title : 'apiDoc: ' + apiProject.name + ' - ' + apiProject.version;
286     $(document).attr('title', title);
287
288     // remove loader
289     $('#loader').remove();
290
291     // render sidenav
292     var fields = {
293         nav: nav
294     };
295     $('#sidenav').append( templateSidenav(fields) );
296
297     // render Generator
298     $('#generator').append( templateGenerator(apiProject) );
299
300     // render Project
301     _.extend(apiProject, { versions: apiVersions});
302     $('#project').append( templateProject(apiProject) );
303
304     // render apiDoc, header/footer documentation
305     if (apiProject.header)
306         $('#header').append( templateHeader(apiProject.header) );
307
308     if (apiProject.footer)
309         $('#footer').append( templateFooter(apiProject.footer) );
310
311     //
312     // Render Sections and Articles
313     //
314     var articleVersions = {};
315     var content = '';
316     apiGroups.forEach(function(groupEntry) {
317         var articles = [];
318         var oldName = '';
319         var fields = {};
320         var title = groupEntry;
321         var description = '';
322         articleVersions[groupEntry] = {};
323
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] = [];
333                             }
334                             articleVersions[entry.group][entry.name].push(versionEntry.version);
335                         }
336                     });
337                     fields = {
338                         article: entry,
339                         versions: articleVersions[entry.group][entry.name]
340                     };
341                 } else {
342                     fields = {
343                         article: entry,
344                         hidden: true,
345                         versions: articleVersions[entry.group][entry.name]
346                     };
347                 }
348
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;
353                     }
354                 }
355
356                 addArticleSettings(fields, entry);
357
358                 if (entry.groupTitle)
359                     title = entry.groupTitle;
360
361                 // TODO: make groupDescription compareable with older versions (not important for the moment)
362                 if (entry.groupDescription)
363                     description = entry.groupDescription;
364
365                 articles.push({
366                     article: templateArticle(fields),
367                     group: entry.group,
368                     name: entry.name,
369                     aloneDisplay: apiProject.template.aloneDisplay
370                 });
371                 oldName = entry.name;
372             }
373         });
374
375         // render Section with Articles
376         var fields = {
377             group: groupEntry,
378             title: title,
379             description: description,
380             articles: articles,
381             aloneDisplay: apiProject.template.aloneDisplay
382         };
383         content += templateSections(fields);
384     });
385     $('#sections').append( content );
386
387     // Bootstrap Scrollspy
388     $(this).scrollspy({ target: '#scrollingNav', offset: 18 });
389
390     // Content-Scroll on Navigation click.
391     $('.sidenav').find('a').on('click', function(e) {
392         e.preventDefault();
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');
397     });
398
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);
404     }
405
406     /**
407      * Check if Parameter (sub) List has a type Field.
408      * Example: @apiSuccess          varname1 No type.
409      *          @apiSuccess {String} varname2 With type.
410      *
411      * @param {Object} fields
412      */
413     function _hasTypeInFields(fields) {
414         var result = false;
415         $.each(fields, function(name) {
416             result = result || _.some(fields[name], function(item) { return item.type; });
417         });
418         return result;
419     }
420
421     /**
422      * On Template changes, recall plugins.
423      */
424     function initDynamic() {
425         // Bootstrap popover
426         $('button[data-toggle="popover"]').popover().click(function(e) {
427             e.preventDefault();
428         });
429
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');
440             });
441         }
442
443         // tabs
444         $('.nav-tabs-examples a').click(function (e) {
445             e.preventDefault();
446             $(this).tab('show');
447         });
448         $('.nav-tabs-examples').find('a:first').tab('show');
449
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');
467             } else {
468                 $(this).parent().nextAll(paramName).first().removeClass('hide')
469                 $(this).parent().nextAll(bodyName).first().addClass('hide');
470             }
471             $(this).prev('.sample-request-switch').prop('checked', true);
472         });
473
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');
482                 }else {
483                     $(this).parent().nextAll(paramName).first().removeClass('hide');
484                 }
485             }else {
486                 if (select == 'body-json'){
487                     $(this).parent().nextAll(bodyName).first().addClass('hide');
488                 }else {
489                     $(this).parent().nextAll(paramName).first().addClass('hide');
490                 }
491             }
492         });
493
494         if (apiProject.template.aloneDisplay){
495             //show group
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');
503             });
504
505             //show api
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');
513             });
514         }
515
516         // call scrollspy refresh method
517         $(window).scrollspy('refresh');
518
519         // init modules
520         sampleRequest.initDynamic();
521     }
522     initDynamic();
523
524     if (apiProject.template.aloneDisplay) {
525         var hashVal = window.location.hash;
526         if (hashVal != null && hashVal.length !== 0) {
527             $("." + hashVal.slice(1) + "-init").click();
528         }
529     }
530
531     // Pre- / Code-Format
532     prettyPrint();
533
534     //
535     // HTML-Template specific jQuery-Functions
536     //
537     // Change Main Version
538     $('#versions li.version a').on('click', function(e) {
539         e.preventDefault();
540
541         var selectedVersion = $(this).html();
542         $('#version strong').html(selectedVersion);
543
544         // hide all
545         $('article').addClass('hide');
546         $('#sidenav li:not(.nav-fixed)').addClass('hide');
547
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');
553
554             if (semver.lte(version, selectedVersion)) {
555                 if ($('article[data-group=\'' + group + '\'][data-name=\'' + name + '\']:visible').length === 0) {
556                     // enable Article
557                     $('article[data-group=\'' + group + '\'][data-name=\'' + name + '\'][data-version=\'' + version + '\']').removeClass('hide');
558                     // enable Navigation
559                     $('#sidenav li[data-group=\'' + group + '\'][data-name=\'' + name + '\'][data-version=\'' + version + '\']').removeClass('hide');
560                     $('#sidenav li.nav-header[data-group=\'' + group + '\']').removeClass('hide');
561                 }
562             }
563         });
564
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');
571             } else {
572                 $('section#api-' + group).removeClass('hide');
573             }
574         });
575
576         initDynamic();
577         return;
578     });
579
580     // compare all article with their predecessor
581     $('#compareAllWithPredecessor').on('click', changeAllVersionCompareTo);
582
583     // change version of an article
584     $('article .versions li.version a').on('click', changeVersionCompareTo);
585
586     // compare url-parameter
587     $.urlParam = function(name) {
588         var results = new RegExp('[\\?&amp;]' + name + '=([^&amp;#]*)').exec(window.location.href);
589         return (results && results[1]) ? results[1] : null;
590     };
591
592     if ($.urlParam('compare')) {
593         // URL Paramter ?compare=1 is set
594         $('#compareAllWithPredecessor').trigger('click');
595
596         if (window.location.hash) {
597             var id = window.location.hash;
598             $('html,body').animate({ scrollTop: parseInt($(id).offset().top) - 18 }, 0);
599         }
600     }
601
602     /**
603      * Initialize search
604      */
605     var options = {
606       valueNames: [ 'nav-list-item','nav-list-url-item']
607     };
608     var endpointsList = new List('scrollingNav', options);
609
610     /**
611      * Set initial focus to search input
612      */
613     $('#scrollingNav .sidenav-search input.search').focus();
614
615     /**
616      * Detect ESC key to reset search
617      */
618     $(document).keyup(function(e) {
619       if (e.keyCode === 27) $('span.search-reset').click();
620     });
621
622     /**
623      * Search reset
624      */
625     $('span.search-reset').on('click', function() {
626       $('#scrollingNav .sidenav-search input.search')
627         .val("")
628         .focus()
629       ;
630       endpointsList.search();
631     });
632
633     /**
634      * Change version of an article to compare it to an other version.
635      */
636     function changeVersionCompareTo(e) {
637         e.preventDefault();
638
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);
644
645         var group = $root.data('group');
646         var name = $root.data('name');
647         var version = $root.data('version');
648
649         var compareVersion = $root.data('compare-version');
650
651         if (compareVersion === selectedVersion)
652             return;
653
654         if ( ! compareVersion && version == selectedVersion)
655             return;
656
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);
660         } else {
661             var $compareToArticle = $('article[data-group=\'' + group + '\'][data-name=\'' + name + '\'][data-version=\'' + selectedVersion + '\']');
662
663             var sourceEntry = {};
664             var compareEntry = {};
665             $.each(apiByGroupAndName[group][name], function(index, entry) {
666                 if (entry.version === version)
667                     sourceEntry = entry;
668                 if (entry.version === selectedVersion)
669                     compareEntry = entry;
670             });
671
672             var fields = {
673                 article: sourceEntry,
674                 compare: compareEntry,
675                 versions: articleVersions[group][name]
676             };
677
678             // add unique id
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, '_');
682
683             fields.compare.id = fields.compare.group + '-' + fields.compare.name + '-' + fields.compare.version;
684             fields.compare.id = fields.compare.id.replace(/\./g, '_');
685
686             var entry = sourceEntry;
687             if (entry.parameter && entry.parameter.fields)
688                 fields._hasTypeInParameterFields = _hasTypeInFields(entry.parameter.fields);
689
690             if (entry.error && entry.error.fields)
691                 fields._hasTypeInErrorFields = _hasTypeInFields(entry.error.fields);
692
693             if (entry.success && entry.success.fields)
694                 fields._hasTypeInSuccessFields = _hasTypeInFields(entry.success.fields);
695
696             if (entry.info && entry.info.fields)
697                 fields._hasTypeInInfoFields = _hasTypeInFields(entry.info.fields);
698
699             var entry = compareEntry;
700             if (fields._hasTypeInParameterFields !== true && entry.parameter && entry.parameter.fields)
701                 fields._hasTypeInParameterFields = _hasTypeInFields(entry.parameter.fields);
702
703             if (fields._hasTypeInErrorFields !== true && entry.error && entry.error.fields)
704                 fields._hasTypeInErrorFields = _hasTypeInFields(entry.error.fields);
705
706             if (fields._hasTypeInSuccessFields !== true && entry.success && entry.success.fields)
707                 fields._hasTypeInSuccessFields = _hasTypeInFields(entry.success.fields);
708
709             if (fields._hasTypeInInfoFields !== true && entry.info && entry.info.fields)
710                 fields._hasTypeInInfoFields = _hasTypeInFields(entry.info.fields);
711
712             var content = templateCompareArticle(fields);
713             $root.after(content);
714             var $content = $root.next();
715
716             // Event on.click re-assign
717             $content.find('.versions li.version a').on('click', changeVersionCompareTo);
718
719             // select navigation
720             $('#sidenav li[data-group=\'' + group + '\'][data-name=\'' + name + '\'][data-version=\'' + currentVersion + '\']').addClass('has-modifications');
721
722             $root.remove();
723             // TODO: on change main version or select the highest version re-render
724         }
725
726         initDynamic();
727     }
728
729     /**
730      * Compare all currently selected Versions with their predecessor.
731      */
732     function changeAllVersionCompareTo(e) {
733         e.preventDefault();
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);
742             });
743
744             if($foundElement)
745                 $foundElement.trigger('click');
746         });
747         initDynamic();
748     }
749
750     /**
751      * Sort the fields.
752      */
753     function sortFields(fields_object) {
754         $.each(fields_object, function (key, fields) {
755
756             var reversed = fields.slice().reverse()
757
758             var max_dot_count = Math.max.apply(null, reversed.map(function (item) {
759                 return item.field.split(".").length - 1;
760             }))
761
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);
773                             }
774                         }
775                     }
776                 });
777             }
778         });
779     }
780
781     /**
782      * Add article settings.
783      */
784     function addArticleSettings(fields, entry) {
785         // add unique id
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, '_');
789
790         if (entry.header && entry.header.fields) {
791             sortFields(entry.header.fields);
792             fields._hasTypeInHeaderFields = _hasTypeInFields(entry.header.fields);
793         }
794
795         if (entry.parameter && entry.parameter.fields) {
796             sortFields(entry.parameter.fields);
797             fields._hasTypeInParameterFields = _hasTypeInFields(entry.parameter.fields);
798         }
799
800         if (entry.error && entry.error.fields) {
801             sortFields(entry.error.fields);
802             fields._hasTypeInErrorFields = _hasTypeInFields(entry.error.fields);
803         }
804
805         if (entry.success && entry.success.fields) {
806             sortFields(entry.success.fields);
807             fields._hasTypeInSuccessFields = _hasTypeInFields(entry.success.fields);
808         }
809
810         if (entry.info && entry.info.fields) {
811             sortFields(entry.info.fields);
812             fields._hasTypeInInfoFields = _hasTypeInFields(entry.info.fields);
813         }
814
815         // add template settings
816         fields.template = apiProject.template;
817     }
818
819     /**
820      * Render Article.
821      */
822     function renderArticle(group, name, version) {
823         var entry = {};
824         $.each(apiByGroupAndName[group][name], function(index, currentEntry) {
825             if (currentEntry.version === version)
826                 entry = currentEntry;
827         });
828         var fields = {
829             article: entry,
830             versions: articleVersions[group][name]
831         };
832
833         addArticleSettings(fields, entry);
834
835         return templateArticle(fields);
836     }
837
838     /**
839      * Render original Article and remove the current visible Article.
840      */
841     function resetArticle(group, name, version) {
842         var $root = $('article[data-group=\'' + group + '\'][data-name=\'' + name + '\']:visible');
843         var content = renderArticle(group, name, version);
844
845         $root.after(content);
846         var $content = $root.next();
847
848         // Event on.click needs to be reassigned (should actually work with on ... automatically)
849         $content.find('.versions li.version a').on('click', changeVersionCompareTo);
850
851         $('#sidenav li[data-group=\'' + group + '\'][data-name=\'' + name + '\'][data-version=\'' + version + '\']').removeClass('has-modifications');
852
853         $root.remove();
854         return;
855     }
856
857     /**
858      * Load google fonts.
859      */
860     function loadGoogleFontCss() {
861         WebFont.load({
862             active: function() {
863                 // Update scrollspy
864                 $(window).scrollspy('refresh')
865             },
866             google: {
867                 families: ['Source Code Pro', 'Source Sans Pro:n4,n6,n7']
868             }
869         });
870     }
871
872     /**
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.
878      */
879     function sortByOrder(elements, order, splitBy) {
880         var results = [];
881         order.forEach (function(name) {
882             if (splitBy)
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);
888                 });
889             else
890                 elements.forEach (function(key) {
891                     if (key == name)
892                         results.push(name);
893                 });
894         });
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);
899         });
900         return results;
901     }
902
903 });