Built motion from commit 7e022ab.|2.0.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     // Setup jQuery Ajax
88     $.ajaxSetup(apiProject.template.jQueryAjaxSetup);
89
90     //
91     // Data transform
92     //
93     // grouped by group
94     var apiByGroup = _.groupBy(api, function(entry) {
95         return entry.group;
96     });
97
98     // grouped by group and name
99     var apiByGroupAndName = {};
100     $.each(apiByGroup, function(index, entries) {
101         apiByGroupAndName[index] = _.groupBy(entries, function(entry) {
102             return entry.name;
103         });
104     });
105
106     //
107     // sort api within a group by title ASC and custom order
108     //
109     var newList = [];
110     var umlauts = { 'ä': 'ae', 'ü': 'ue', 'ö': 'oe', 'ß': 'ss' }; // TODO: remove in version 1.0
111     $.each (apiByGroupAndName, function(index, groupEntries) {
112         // get titles from the first entry of group[].name[] (name has versioning)
113         var titles = [];
114         $.each (groupEntries, function(titleName, entries) {
115             var title = entries[0].title;
116             if(title !== undefined) {
117                 title.toLowerCase().replace(/[äöüß]/g, function($0) { return umlauts[$0]; });
118                 titles.push(title + '#~#' + titleName); // '#~#' keep reference to titleName after sorting
119             }
120         });
121         // sort by name ASC
122         titles.sort();
123
124         // custom order
125         if (apiProject.order)
126             titles = sortByOrder(titles, apiProject.order, '#~#');
127
128         // add single elements to the new list
129         titles.forEach(function(name) {
130             var values = name.split('#~#');
131             var key = values[1];
132             groupEntries[key].forEach(function(entry) {
133                 newList.push(entry);
134             });
135         });
136     });
137     // api overwrite with ordered list
138     api = newList;
139
140     //
141     // Group- and Versionlists
142     //
143     var apiGroups = {};
144     var apiGroupTitles = {};
145     var apiVersions = {};
146     apiVersions[apiProject.version] = 1;
147
148     $.each(api, function(index, entry) {
149         apiGroups[entry.group] = 1;
150         apiGroupTitles[entry.group] = entry.groupTitle || entry.group;
151         apiVersions[entry.version] = 1;
152     });
153
154     // sort groups
155     apiGroups = Object.keys(apiGroups);
156     apiGroups.sort();
157
158     // custom order
159     if (apiProject.order)
160         apiGroups = sortByOrder(apiGroups, apiProject.order);
161
162     // sort versions DESC
163     apiVersions = Object.keys(apiVersions);
164     apiVersions.sort(semver.compare);
165     apiVersions.reverse();
166
167     //
168     // create Navigationlist
169     //
170     var nav = [];
171     apiGroups.forEach(function(group) {
172         // Mainmenu entry
173         nav.push({
174             group: group,
175             isHeader: true,
176             title: apiGroupTitles[group]
177         });
178
179         // Submenu
180         var oldName = '';
181         api.forEach(function(entry) {
182             if (entry.group === group) {
183                 if (oldName !== entry.name) {
184                     nav.push({
185                         title: entry.title,
186                         group: group,
187                         name: entry.name,
188                         type: entry.type,
189                         version: entry.version
190                     });
191                 } else {
192                     nav.push({
193                         title: entry.title,
194                         group: group,
195                         hidden: true,
196                         name: entry.name,
197                         type: entry.type,
198                         version: entry.version
199                     });
200                 }
201                 oldName = entry.name;
202             }
203         });
204     });
205
206     /**
207      * Add navigation items by analyzing the HTML content and searching for h1 and h2 tags
208      * @param nav Object the navigation array
209      * @param content string the compiled HTML content
210      * @param index where to insert items
211      * @return boolean true if any good-looking (i.e. with a group identifier) <h1> tag was found
212      */
213     function add_nav(nav, content, index) {
214         var found_level1 = false;
215         if ( ! content) {
216           return found_level1;
217         }
218         var topics = content.match(/<h(1|2).*?>(.+?)<\/h(1|2)>/gi);
219         if ( topics ) {
220           topics.forEach(function(entry) {
221               var level = entry.substring(2,3);
222               var title = entry.replace(/<.+?>/g, '');    // Remove all HTML tags for the title
223               var entry_tags = entry.match(/id="api-([^\-]+)(?:-(.+))?"/);    // Find the group and name in the id property
224               var group = (entry_tags ? entry_tags[1] : null);
225               var name = (entry_tags ? entry_tags[2] : null);
226               if (level==1 && title && group)  {
227                   nav.splice(index, 0, {
228                       group: group,
229                       isHeader: true,
230                       title: title,
231                       isFixed: true
232                   });
233                   index++;
234                   found_level1 = true;
235               }
236               if (level==2 && title && group && name)    {
237                   nav.splice(index, 0, {
238                       group: group,
239                       name: name,
240                       isHeader: false,
241                       title: title,
242                       isFixed: false,
243                       version: '1.0'
244                   });
245                   index++;
246               }
247           });
248         }
249         return found_level1;
250     }
251
252     // Mainmenu Header entry
253     if (apiProject.header) {
254         var found_level1 = add_nav(nav, apiProject.header.content, 0); // Add level 1 and 2 titles
255         if (!found_level1) {    // If no Level 1 tags were found, make a title
256             nav.unshift({
257                 group: '_',
258                 isHeader: true,
259                 title: (apiProject.header.title == null) ? locale.__('General') : apiProject.header.title,
260                 isFixed: true
261             });
262         }
263     }
264
265     // Mainmenu Footer entry
266     if (apiProject.footer) {
267         var last_nav_index = nav.length;
268         var found_level1 = add_nav(nav, apiProject.footer.content, nav.length); // Add level 1 and 2 titles
269         if (!found_level1 && apiProject.footer.title != null) {    // If no Level 1 tags were found, make a title
270             nav.splice(last_nav_index, 0, {
271                 group: '_footer',
272                 isHeader: true,
273                 title: apiProject.footer.title,
274                 isFixed: true
275             });
276         }
277     }
278
279     // render pagetitle
280     var title = apiProject.title ? apiProject.title : 'apiDoc: ' + apiProject.name + ' - ' + apiProject.version;
281     $(document).attr('title', title);
282
283     // remove loader
284     $('#loader').remove();
285
286     // render sidenav
287     var fields = {
288         nav: nav
289     };
290     $('#sidenav').append( templateSidenav(fields) );
291
292     // render Generator
293     $('#generator').append( templateGenerator(apiProject) );
294
295     // render Project
296     _.extend(apiProject, { versions: apiVersions});
297     $('#project').append( templateProject(apiProject) );
298
299     // render apiDoc, header/footer documentation
300     if (apiProject.header)
301         $('#header').append( templateHeader(apiProject.header) );
302
303     if (apiProject.footer)
304         $('#footer').append( templateFooter(apiProject.footer) );
305
306     //
307     // Render Sections and Articles
308     //
309     var articleVersions = {};
310     var content = '';
311     apiGroups.forEach(function(groupEntry) {
312         var articles = [];
313         var oldName = '';
314         var fields = {};
315         var title = groupEntry;
316         var description = '';
317         articleVersions[groupEntry] = {};
318
319         // render all articles of a group
320         api.forEach(function(entry) {
321             if(groupEntry === entry.group) {
322                 if (oldName !== entry.name) {
323                     // determine versions
324                     api.forEach(function(versionEntry) {
325                         if (groupEntry === versionEntry.group && entry.name === versionEntry.name) {
326                             if ( ! articleVersions[entry.group].hasOwnProperty(entry.name) ) {
327                                 articleVersions[entry.group][entry.name] = [];
328                             }
329                             articleVersions[entry.group][entry.name].push(versionEntry.version);
330                         }
331                     });
332                     fields = {
333                         article: entry,
334                         versions: articleVersions[entry.group][entry.name]
335                     };
336                 } else {
337                     fields = {
338                         article: entry,
339                         hidden: true,
340                         versions: articleVersions[entry.group][entry.name]
341                     };
342                 }
343
344                 // add prefix URL for endpoint
345                 if (apiProject.url)
346                     fields.article.url = apiProject.url + fields.article.url;
347
348                 addArticleSettings(fields, entry);
349
350                 if (entry.groupTitle)
351                     title = entry.groupTitle;
352
353                 // TODO: make groupDescription compareable with older versions (not important for the moment)
354                 if (entry.groupDescription)
355                     description = entry.groupDescription;
356
357                 articles.push({
358                     article: templateArticle(fields),
359                     group: entry.group,
360                     name: entry.name
361                 });
362                 oldName = entry.name;
363             }
364         });
365
366         // render Section with Articles
367         var fields = {
368             group: groupEntry,
369             title: title,
370             description: description,
371             articles: articles
372         };
373         content += templateSections(fields);
374     });
375     $('#sections').append( content );
376
377     // Bootstrap Scrollspy
378     $(this).scrollspy({ target: '#scrollingNav', offset: 18 });
379
380     // Content-Scroll on Navigation click.
381     $('.sidenav').find('a').on('click', function(e) {
382         e.preventDefault();
383         var id = $(this).attr('href');
384         if ($(id).length > 0)
385             $('html,body').animate({ scrollTop: parseInt($(id).offset().top) }, 400);
386         window.location.hash = $(this).attr('href');
387     });
388
389     // Quickjump on Pageload to hash position.
390     if(window.location.hash) {
391         var id = window.location.hash;
392         if ($(id).length > 0)
393             $('html,body').animate({ scrollTop: parseInt($(id).offset().top) }, 0);
394     }
395
396     /**
397      * Check if Parameter (sub) List has a type Field.
398      * Example: @apiSuccess          varname1 No type.
399      *          @apiSuccess {String} varname2 With type.
400      *
401      * @param {Object} fields
402      */
403     function _hasTypeInFields(fields) {
404         var result = false;
405         $.each(fields, function(name) {
406             result = result || _.some(fields[name], function(item) { return item.type; });
407         });
408         return result;
409     }
410
411     /**
412      * On Template changes, recall plugins.
413      */
414     function initDynamic() {
415         // Bootstrap popover
416         $('button[data-toggle="popover"]').popover().click(function(e) {
417             e.preventDefault();
418         });
419
420         var version = $('#version strong').html();
421         $('#sidenav li').removeClass('is-new');
422         if (apiProject.template.withCompare) {
423             $('#sidenav li[data-version=\'' + version + '\']').each(function(){
424                 var group = $(this).data('group');
425                 var name = $(this).data('name');
426                 var length = $('#sidenav li[data-group=\'' + group + '\'][data-name=\'' + name + '\']').length;
427                 var index  = $('#sidenav li[data-group=\'' + group + '\'][data-name=\'' + name + '\']').index($(this));
428                 if (length === 1 || index === (length - 1))
429                     $(this).addClass('is-new');
430             });
431         }
432
433         // tabs
434         $('.nav-tabs-examples a').click(function (e) {
435             e.preventDefault();
436             $(this).tab('show');
437         });
438         $('.nav-tabs-examples').find('a:first').tab('show');
439
440         // sample request switch
441         $('.sample-request-switch').click(function (e) {
442             var name = '.' + $(this).attr('name') + '-fields';
443             $(name).addClass('hide');
444             $(this).parent().next(name).removeClass('hide');
445         });
446
447         // call scrollspy refresh method
448         $(window).scrollspy('refresh');
449
450         // init modules
451         sampleRequest.initDynamic();
452     }
453     initDynamic();
454
455     // Pre- / Code-Format
456     prettyPrint();
457
458     //
459     // HTML-Template specific jQuery-Functions
460     //
461     // Change Main Version
462     $('#versions li.version a').on('click', function(e) {
463         e.preventDefault();
464
465         var selectedVersion = $(this).html();
466         $('#version strong').html(selectedVersion);
467
468         // hide all
469         $('article').addClass('hide');
470         $('#sidenav li:not(.nav-fixed)').addClass('hide');
471
472         // show 1st equal or lower Version of each entry
473         $('article[data-version]').each(function(index) {
474             var group = $(this).data('group');
475             var name = $(this).data('name');
476             var version = $(this).data('version');
477
478             if (semver.lte(version, selectedVersion)) {
479                 if ($('article[data-group=\'' + group + '\'][data-name=\'' + name + '\']:visible').length === 0) {
480                     // enable Article
481                     $('article[data-group=\'' + group + '\'][data-name=\'' + name + '\'][data-version=\'' + version + '\']').removeClass('hide');
482                     // enable Navigation
483                     $('#sidenav li[data-group=\'' + group + '\'][data-name=\'' + name + '\'][data-version=\'' + version + '\']').removeClass('hide');
484                     $('#sidenav li.nav-header[data-group=\'' + group + '\']').removeClass('hide');
485                 }
486             }
487         });
488
489         // show 1st equal or lower Version of each entry
490         $('article[data-version]').each(function(index) {
491             var group = $(this).data('group');
492             $('section#api-' + group).removeClass('hide');
493             if ($('section#api-' + group + ' article:visible').length === 0) {
494                 $('section#api-' + group).addClass('hide');
495             } else {
496                 $('section#api-' + group).removeClass('hide');
497             }
498         });
499
500         initDynamic();
501         return;
502     });
503
504     // compare all article with their predecessor
505     $('#compareAllWithPredecessor').on('click', changeAllVersionCompareTo);
506
507     // change version of an article
508     $('article .versions li.version a').on('click', changeVersionCompareTo);
509
510     // compare url-parameter
511     $.urlParam = function(name) {
512         var results = new RegExp('[\\?&amp;]' + name + '=([^&amp;#]*)').exec(window.location.href);
513         return (results && results[1]) ? results[1] : null;
514     };
515
516     if ($.urlParam('compare')) {
517         // URL Paramter ?compare=1 is set
518         $('#compareAllWithPredecessor').trigger('click');
519
520         if (window.location.hash) {
521             var id = window.location.hash;
522             $('html,body').animate({ scrollTop: parseInt($(id).offset().top) - 18 }, 0);
523         }
524     }
525
526     /**
527      * Initialize search
528      */
529     var options = {
530       valueNames: [ 'nav-list-item' ]
531     };
532     var endpointsList = new List('scrollingNav', options);
533
534     /**
535      * Set initial focus to search input
536      */
537     $('#scrollingNav .sidenav-search input.search').focus();
538
539     /**
540      * Detect ESC key to reset search
541      */
542     $(document).keyup(function(e) {
543       if (e.keyCode === 27) $('span.search-reset').click();
544     });
545
546     /**
547      * Search reset
548      */
549     $('span.search-reset').on('click', function() {
550       $('#scrollingNav .sidenav-search input.search')
551         .val("")
552         .focus()
553       ;
554       endpointsList.search();
555     });
556
557     /**
558      * Change version of an article to compare it to an other version.
559      */
560     function changeVersionCompareTo(e) {
561         e.preventDefault();
562
563         var $root = $(this).parents('article');
564         var selectedVersion = $(this).html();
565         var $button = $root.find('.version');
566         var currentVersion = $button.find('strong').html();
567         $button.find('strong').html(selectedVersion);
568
569         var group = $root.data('group');
570         var name = $root.data('name');
571         var version = $root.data('version');
572
573         var compareVersion = $root.data('compare-version');
574
575         if (compareVersion === selectedVersion)
576             return;
577
578         if ( ! compareVersion && version == selectedVersion)
579             return;
580
581         if (compareVersion && articleVersions[group][name][0] === selectedVersion || version === selectedVersion) {
582             // the version of the entry is set to the highest version (reset)
583             resetArticle(group, name, version);
584         } else {
585             var $compareToArticle = $('article[data-group=\'' + group + '\'][data-name=\'' + name + '\'][data-version=\'' + selectedVersion + '\']');
586
587             var sourceEntry = {};
588             var compareEntry = {};
589             $.each(apiByGroupAndName[group][name], function(index, entry) {
590                 if (entry.version === version)
591                     sourceEntry = entry;
592                 if (entry.version === selectedVersion)
593                     compareEntry = entry;
594             });
595
596             var fields = {
597                 article: sourceEntry,
598                 compare: compareEntry,
599                 versions: articleVersions[group][name]
600             };
601
602             // add unique id
603             // TODO: replace all group-name-version in template with id.
604             fields.article.id = fields.article.group + '-' + fields.article.name + '-' + fields.article.version;
605             fields.article.id = fields.article.id.replace(/\./g, '_');
606
607             fields.compare.id = fields.compare.group + '-' + fields.compare.name + '-' + fields.compare.version;
608             fields.compare.id = fields.compare.id.replace(/\./g, '_');
609
610             var entry = sourceEntry;
611             if (entry.parameter && entry.parameter.fields)
612                 fields._hasTypeInParameterFields = _hasTypeInFields(entry.parameter.fields);
613
614             if (entry.error && entry.error.fields)
615                 fields._hasTypeInErrorFields = _hasTypeInFields(entry.error.fields);
616
617             if (entry.success && entry.success.fields)
618                 fields._hasTypeInSuccessFields = _hasTypeInFields(entry.success.fields);
619
620             if (entry.info && entry.info.fields)
621                 fields._hasTypeInInfoFields = _hasTypeInFields(entry.info.fields);
622
623             var entry = compareEntry;
624             if (fields._hasTypeInParameterFields !== true && entry.parameter && entry.parameter.fields)
625                 fields._hasTypeInParameterFields = _hasTypeInFields(entry.parameter.fields);
626
627             if (fields._hasTypeInErrorFields !== true && entry.error && entry.error.fields)
628                 fields._hasTypeInErrorFields = _hasTypeInFields(entry.error.fields);
629
630             if (fields._hasTypeInSuccessFields !== true && entry.success && entry.success.fields)
631                 fields._hasTypeInSuccessFields = _hasTypeInFields(entry.success.fields);
632
633             if (fields._hasTypeInInfoFields !== true && entry.info && entry.info.fields)
634                 fields._hasTypeInInfoFields = _hasTypeInFields(entry.info.fields);
635
636             var content = templateCompareArticle(fields);
637             $root.after(content);
638             var $content = $root.next();
639
640             // Event on.click re-assign
641             $content.find('.versions li.version a').on('click', changeVersionCompareTo);
642
643             // select navigation
644             $('#sidenav li[data-group=\'' + group + '\'][data-name=\'' + name + '\'][data-version=\'' + currentVersion + '\']').addClass('has-modifications');
645
646             $root.remove();
647             // TODO: on change main version or select the highest version re-render
648         }
649
650         initDynamic();
651     }
652
653     /**
654      * Compare all currently selected Versions with their predecessor.
655      */
656     function changeAllVersionCompareTo(e) {
657         e.preventDefault();
658         $('article:visible .versions').each(function(){
659             var $root = $(this).parents('article');
660             var currentVersion = $root.data('version');
661             var $foundElement = null;
662             $(this).find('li.version a').each(function() {
663                 var selectVersion = $(this).html();
664                 if (selectVersion < currentVersion && ! $foundElement)
665                     $foundElement = $(this);
666             });
667
668             if($foundElement)
669                 $foundElement.trigger('click');
670         });
671         initDynamic();
672     }
673
674     /**
675      * Sort the fields.
676      */
677     function sortFields(fields_object) {
678         $.each(fields_object, function (key, fields) {
679
680             var reversed = fields.slice().reverse()
681
682             var max_dot_count = Math.max.apply(null, reversed.map(function (item) {
683                 return item.field.split(".").length - 1;
684             }))
685
686             for (var dot_count = 1; dot_count <= max_dot_count; dot_count++) {
687                 reversed.forEach(function (item, index) {
688                     var parts = item.field.split(".");
689                     if (parts.length - 1 == dot_count) {
690                         var fields_names = fields.map(function (item) { return item.field; });
691                         if (parts.slice(1).length  >= 1) {
692                             var prefix = parts.slice(0, parts.length - 1).join(".");
693                             var prefix_index = fields_names.indexOf(prefix);
694                             if (prefix_index > -1) {
695                                 fields.splice(fields_names.indexOf(item.field), 1);
696                                 fields.splice(prefix_index + 1, 0, item);
697                             }
698                         }
699                     }
700                 });
701             }
702         });
703     }
704
705     /**
706      * Add article settings.
707      */
708     function addArticleSettings(fields, entry) {
709         // add unique id
710         // TODO: replace all group-name-version in template with id.
711         fields.id = fields.article.group + '-' + fields.article.name + '-' + fields.article.version;
712         fields.id = fields.id.replace(/\./g, '_');
713
714         if (entry.header && entry.header.fields) {
715             sortFields(entry.header.fields);
716             fields._hasTypeInHeaderFields = _hasTypeInFields(entry.header.fields);
717         }
718
719         if (entry.parameter && entry.parameter.fields) {
720             sortFields(entry.parameter.fields);
721             fields._hasTypeInParameterFields = _hasTypeInFields(entry.parameter.fields);
722         }
723
724         if (entry.error && entry.error.fields) {
725             sortFields(entry.error.fields);
726             fields._hasTypeInErrorFields = _hasTypeInFields(entry.error.fields);
727         }
728
729         if (entry.success && entry.success.fields) {
730             sortFields(entry.success.fields);
731             fields._hasTypeInSuccessFields = _hasTypeInFields(entry.success.fields);
732         }
733
734         if (entry.info && entry.info.fields) {
735             sortFields(entry.info.fields);
736             fields._hasTypeInInfoFields = _hasTypeInFields(entry.info.fields);
737         }
738
739         // add template settings
740         fields.template = apiProject.template;
741     }
742
743     /**
744      * Render Article.
745      */
746     function renderArticle(group, name, version) {
747         var entry = {};
748         $.each(apiByGroupAndName[group][name], function(index, currentEntry) {
749             if (currentEntry.version === version)
750                 entry = currentEntry;
751         });
752         var fields = {
753             article: entry,
754             versions: articleVersions[group][name]
755         };
756
757         addArticleSettings(fields, entry);
758
759         return templateArticle(fields);
760     }
761
762     /**
763      * Render original Article and remove the current visible Article.
764      */
765     function resetArticle(group, name, version) {
766         var $root = $('article[data-group=\'' + group + '\'][data-name=\'' + name + '\']:visible');
767         var content = renderArticle(group, name, version);
768
769         $root.after(content);
770         var $content = $root.next();
771
772         // Event on.click muss neu zugewiesen werden (sollte eigentlich mit on automatisch funktionieren... sollte)
773         $content.find('.versions li.version a').on('click', changeVersionCompareTo);
774
775         $('#sidenav li[data-group=\'' + group + '\'][data-name=\'' + name + '\'][data-version=\'' + version + '\']').removeClass('has-modifications');
776
777         $root.remove();
778         return;
779     }
780
781     /**
782      * Load google fonts.
783      */
784     function loadGoogleFontCss() {
785         WebFont.load({
786             active: function() {
787                 // Update scrollspy
788                 $(window).scrollspy('refresh')
789             },
790             google: {
791                 families: ['Source Code Pro', 'Source Sans Pro:n4,n6,n7']
792             }
793         });
794     }
795
796     /**
797      * Return ordered entries by custom order and append not defined entries to the end.
798      * @param  {String[]} elements
799      * @param  {String[]} order
800      * @param  {String}   splitBy
801      * @return {String[]} Custom ordered list.
802      */
803     function sortByOrder(elements, order, splitBy) {
804         var results = [];
805         order.forEach (function(name) {
806             if (splitBy)
807                 elements.forEach (function(element) {
808                     var parts = element.split(splitBy);
809                     var key = parts[1]; // reference keep for sorting
810                     if (key == name)
811                         results.push(element);
812                 });
813             else
814                 elements.forEach (function(key) {
815                     if (key == name)
816                         results.push(name);
817                 });
818         });
819         // Append all other entries that ar not defined in order
820         elements.forEach(function(element) {
821             if (results.indexOf(element) === -1)
822                 results.push(element);
823         });
824         return results;
825     }
826
827 });