main.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691
  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.min',
  10. pathToRegexp: './vendor/path-to-regexp/index',
  11. prettify: './vendor/prettify/prettify',
  12. utilsSampleRequest: './utils/send_sample_request_backup',
  13. },
  14. shim: {
  15. bootstrap: {
  16. deps: ['jquery']
  17. },
  18. diffMatchPatch: {
  19. exports: 'diff_match_patch'
  20. },
  21. handlebars: {
  22. exports: 'Handlebars'
  23. },
  24. handlebarsExtended: {
  25. deps: ['jquery', 'handlebars'],
  26. exports: 'Handlebars'
  27. },
  28. prettify: {
  29. exports: 'prettyPrint'
  30. }
  31. },
  32. urlArgs: 'v=' + (new Date()).getTime(),
  33. waitSeconds: 15
  34. });
  35. require([
  36. 'jquery',
  37. 'lodash',
  38. 'locales',
  39. 'handlebarsExtended',
  40. './api_project.js',
  41. './api_data.js',
  42. 'prettify',
  43. 'utilsSampleRequest',
  44. 'bootstrap',
  45. 'pathToRegexp'
  46. ], function($, _, locale, Handlebars, apiProject, apiData, prettyPrint, sampleRequest) {
  47. // load google web fonts
  48. loadGoogleFontCss();
  49. var api = apiData.api;
  50. //
  51. // Templates
  52. //
  53. var templateHeader = Handlebars.compile( $('#template-header').html() );
  54. var templateFooter = Handlebars.compile( $('#template-footer').html() );
  55. var templateArticle = Handlebars.compile( $('#template-article').html() );
  56. var templateCompareArticle = Handlebars.compile( $('#template-compare-article').html() );
  57. var templateGenerator = Handlebars.compile( $('#template-generator').html() );
  58. var templateProject = Handlebars.compile( $('#template-project').html() );
  59. var templateSections = Handlebars.compile( $('#template-sections').html() );
  60. var templateSidenav = Handlebars.compile( $('#template-sidenav').html() );
  61. //
  62. // apiProject defaults
  63. //
  64. if ( ! apiProject.template)
  65. apiProject.template = {};
  66. if (apiProject.template.withCompare == null)
  67. apiProject.template.withCompare = true;
  68. if (apiProject.template.withGenerator == null)
  69. apiProject.template.withGenerator = true;
  70. if (apiProject.template.forceLanguage)
  71. locale.setLanguage(apiProject.template.forceLanguage);
  72. // Setup jQuery Ajax
  73. $.ajaxSetup(apiProject.template.jQueryAjaxSetup);
  74. //
  75. // Data transform
  76. //
  77. // grouped by group
  78. var apiByGroup = _.groupBy(api, function(entry) {
  79. return entry.group;
  80. });
  81. // grouped by group and name
  82. var apiByGroupAndName = {};
  83. $.each(apiByGroup, function(index, entries) {
  84. apiByGroupAndName[index] = _.groupBy(entries, function(entry) {
  85. return entry.name;
  86. });
  87. });
  88. //
  89. // sort api within a group by title ASC and custom order
  90. //
  91. var newList = [];
  92. var umlauts = { 'ä': 'ae', 'ü': 'ue', 'ö': 'oe', 'ß': 'ss' }; // TODO: remove in version 1.0
  93. $.each (apiByGroupAndName, function(index, groupEntries) {
  94. // get titles from the first entry of group[].name[] (name has versioning)
  95. var titles = [];
  96. $.each (groupEntries, function(titleName, entries) {
  97. var title = entries[0].title;
  98. if(title !== undefined) {
  99. title.toLowerCase().replace(/[äöüß]/g, function($0) { return umlauts[$0]; });
  100. titles.push(title + '#~#' + titleName); // '#~#' keep reference to titleName after sorting
  101. }
  102. });
  103. // sort by name ASC
  104. titles.sort();
  105. // custom order
  106. if (apiProject.order)
  107. titles = sortByOrder(titles, apiProject.order, '#~#');
  108. // add single elements to the new list
  109. titles.forEach(function(name) {
  110. var values = name.split('#~#');
  111. var key = values[1];
  112. groupEntries[key].forEach(function(entry) {
  113. newList.push(entry);
  114. });
  115. });
  116. });
  117. // api overwrite with ordered list
  118. api = newList;
  119. //
  120. // Group- and Versionlists
  121. //
  122. var apiGroups = {};
  123. var apiGroupTitles = {};
  124. var apiVersions = {};
  125. apiVersions[apiProject.version] = 1;
  126. $.each(api, function(index, entry) {
  127. apiGroups[entry.group] = 1;
  128. apiGroupTitles[entry.group] = entry.groupTitle || entry.group;
  129. apiVersions[entry.version] = 1;
  130. });
  131. // sort groups
  132. apiGroups = Object.keys(apiGroups);
  133. apiGroups.sort();
  134. // custom order
  135. if (apiProject.order)
  136. apiGroups = sortByOrder(apiGroups, apiProject.order);
  137. // sort versions DESC
  138. apiVersions = Object.keys(apiVersions);
  139. apiVersions.sort();
  140. apiVersions.reverse();
  141. //
  142. // create Navigationlist
  143. //
  144. var nav = [];
  145. apiGroups.forEach(function(group) {
  146. // Mainmenu entry
  147. nav.push({
  148. group: group,
  149. isHeader: true,
  150. title: apiGroupTitles[group]
  151. });
  152. // Submenu
  153. var oldName = '';
  154. api.forEach(function(entry) {
  155. if (entry.group === group) {
  156. if (oldName !== entry.name) {
  157. nav.push({
  158. title: entry.title,
  159. group: group,
  160. name: entry.name,
  161. type: entry.type,
  162. version: entry.version
  163. });
  164. } else {
  165. nav.push({
  166. title: entry.title,
  167. group: group,
  168. hidden: true,
  169. name: entry.name,
  170. type: entry.type,
  171. version: entry.version
  172. });
  173. }
  174. oldName = entry.name;
  175. }
  176. });
  177. });
  178. // Mainmenu Header entry
  179. if (apiProject.header) {
  180. nav.unshift({
  181. group: '_',
  182. isHeader: true,
  183. title: (apiProject.header.title == null) ? locale.__('General') : apiProject.header.title,
  184. isFixed: true
  185. });
  186. }
  187. // Mainmenu Footer entry
  188. if (apiProject.footer && apiProject.footer.title != null) {
  189. nav.push({
  190. group: '_footer',
  191. isHeader: true,
  192. title: apiProject.footer.title,
  193. isFixed: true
  194. });
  195. }
  196. // render pagetitle
  197. var title = apiProject.title ? apiProject.title : 'apiDoc: ' + apiProject.name + ' - ' + apiProject.version;
  198. $(document).attr('title', title);
  199. // remove loader
  200. $('#loader').remove();
  201. // render sidenav
  202. var fields = {
  203. nav: nav
  204. };
  205. $('#sidenav').append( templateSidenav(fields) );
  206. // render Generator
  207. $('#generator').append( templateGenerator(apiProject) );
  208. // render Project
  209. _.extend(apiProject, { versions: apiVersions});
  210. $('#project').append( templateProject(apiProject) );
  211. // render apiDoc, header/footer documentation
  212. if (apiProject.header)
  213. $('#header').append( templateHeader(apiProject.header) );
  214. if (apiProject.footer)
  215. $('#footer').append( templateFooter(apiProject.footer) );
  216. //
  217. // Render Sections and Articles
  218. //
  219. var articleVersions = {};
  220. var content = '';
  221. apiGroups.forEach(function(groupEntry) {
  222. var articles = [];
  223. var oldName = '';
  224. var fields = {};
  225. var title = groupEntry;
  226. var description = '';
  227. articleVersions[groupEntry] = {};
  228. // render all articles of a group
  229. api.forEach(function(entry) {
  230. if(groupEntry === entry.group) {
  231. if (oldName !== entry.name) {
  232. // determine versions
  233. api.forEach(function(versionEntry) {
  234. if (groupEntry === versionEntry.group && entry.name === versionEntry.name) {
  235. if ( ! articleVersions[entry.group][entry.name])
  236. articleVersions[entry.group][entry.name] = [];
  237. articleVersions[entry.group][entry.name].push(versionEntry.version);
  238. }
  239. });
  240. fields = {
  241. article: entry,
  242. versions: articleVersions[entry.group][entry.name]
  243. };
  244. } else {
  245. fields = {
  246. article: entry,
  247. hidden: true,
  248. versions: articleVersions[entry.group][entry.name]
  249. };
  250. }
  251. // add prefix URL for endpoint
  252. if (apiProject.url)
  253. fields.article.url = apiProject.url + fields.article.url;
  254. addArticleSettings(fields, entry);
  255. if (entry.groupTitle)
  256. title = entry.groupTitle;
  257. // TODO: make groupDescription compareable with older versions (not important for the moment)
  258. if (entry.groupDescription)
  259. description = entry.groupDescription;
  260. articles.push({
  261. article: templateArticle(fields),
  262. group: entry.group,
  263. name: entry.name
  264. });
  265. oldName = entry.name;
  266. }
  267. });
  268. // render Section with Articles
  269. var fields = {
  270. group: groupEntry,
  271. title: title,
  272. description: description,
  273. articles: articles
  274. };
  275. content += templateSections(fields);
  276. });
  277. $('#sections').append( content );
  278. // Bootstrap Scrollspy
  279. var $scrollSpy = $(this).scrollspy({ target: '#scrollingNav', offset: 18 });
  280. $('[data-spy="scroll"]').each(function () {
  281. $scrollSpy('refresh');
  282. });
  283. // Content-Scroll on Navigation click.
  284. $('.sidenav').find('a').on('click', function(e) {
  285. e.preventDefault();
  286. var id = $(this).attr('href');
  287. if ($(id).length > 0)
  288. $('html,body').animate({ scrollTop: parseInt($(id).offset().top) }, 400);
  289. window.location.hash = $(this).attr('href');
  290. });
  291. // Quickjump on Pageload to hash position.
  292. if(window.location.hash) {
  293. var id = window.location.hash;
  294. if ($(id).length > 0)
  295. $('html,body').animate({ scrollTop: parseInt($(id).offset().top) }, 0);
  296. }
  297. /**
  298. * Check if Parameter (sub) List has a type Field.
  299. * Example: @apiSuccess varname1 No type.
  300. * @apiSuccess {String} varname2 With type.
  301. *
  302. * @param {Object} fields
  303. */
  304. function _hasTypeInFields(fields) {
  305. var result = false;
  306. $.each(fields, function(name) {
  307. if (_.any(fields[name], function(item) { return item.type; }) )
  308. result = true;
  309. });
  310. return result;
  311. }
  312. /**
  313. * On Template changes, recall plugins.
  314. */
  315. function initDynamic() {
  316. // bootstrap popover
  317. $('a[data-toggle=popover]')
  318. .popover()
  319. .click(function(e) {
  320. e.preventDefault();
  321. })
  322. ;
  323. var version = $('#version strong').html();
  324. $('#sidenav li').removeClass('is-new');
  325. if (apiProject.template.withCompare) {
  326. $('#sidenav li[data-version=\'' + version + '\']').each(function(){
  327. var group = $(this).data('group');
  328. var name = $(this).data('name');
  329. var length = $('#sidenav li[data-group=\'' + group + '\'][data-name=\'' + name + '\']').length;
  330. var index = $('#sidenav li[data-group=\'' + group + '\'][data-name=\'' + name + '\']').index($(this));
  331. if (length === 1 || index === (length - 1))
  332. $(this).addClass('is-new');
  333. });
  334. }
  335. // tabs
  336. $('.nav-tabs-examples a').click(function (e) {
  337. e.preventDefault();
  338. $(this).tab('show');
  339. });
  340. $('.nav-tabs-examples').find('a:first').tab('show');
  341. // sample request switch
  342. $('.sample-request-switch').click(function (e) {
  343. var name = '.' + $(this).attr('name') + '-fields';
  344. $(name).addClass('hide');
  345. $(this).parent().next(name).removeClass('hide');
  346. });
  347. // init modules
  348. sampleRequest.initDynamic();
  349. }
  350. initDynamic();
  351. // Pre- / Code-Format
  352. prettyPrint();
  353. //
  354. // HTML-Template specific jQuery-Functions
  355. //
  356. // Change Main Version
  357. $('#versions li.version a').on('click', function(e) {
  358. e.preventDefault();
  359. var selectedVersion = $(this).html();
  360. $('#version strong').html(selectedVersion);
  361. // hide all
  362. $('article').addClass('hide');
  363. $('#sidenav li:not(.nav-fixed)').addClass('hide');
  364. // show 1st equal or lower Version of each entry
  365. $('article[data-version]').each(function(index) {
  366. var group = $(this).data('group');
  367. var name = $(this).data('name');
  368. var version = $(this).data('version');
  369. if (version <= selectedVersion) {
  370. if($('article[data-group=\'' + group + '\'][data-name=\'' + name + '\']:visible').length === 0) {
  371. // enable Article
  372. $('article[data-group=\'' + group + '\'][data-name=\'' + name + '\'][data-version=\'' + version + '\']').removeClass('hide');
  373. // enable Navigation
  374. $('#sidenav li[data-group=\'' + group + '\'][data-name=\'' + name + '\'][data-version=\'' + version + '\']').removeClass('hide');
  375. $('#sidenav li.nav-header[data-group=\'' + group + '\']').removeClass('hide');
  376. }
  377. }
  378. });
  379. initDynamic();
  380. return;
  381. });
  382. // compare all article with their predecessor
  383. $('#compareAllWithPredecessor').on('click', changeAllVersionCompareTo);
  384. // change version of an article
  385. $('article .versions li.version a').on('click', changeVersionCompareTo);
  386. // compare url-parameter
  387. $.urlParam = function(name) {
  388. var results = new RegExp('[\\?&amp;]' + name + '=([^&amp;#]*)').exec(window.location.href);
  389. return (results && results[1]) ? results[1] : null;
  390. };
  391. if ($.urlParam('compare')) {
  392. // URL Paramter ?compare=1 is set
  393. $('#compareAllWithPredecessor').trigger('click');
  394. if (window.location.hash) {
  395. var id = window.location.hash;
  396. $('html,body').animate({ scrollTop: parseInt($(id).offset().top) - 18 }, 0);
  397. }
  398. }
  399. /**
  400. * Change version of an article to compare it to an other version.
  401. */
  402. function changeVersionCompareTo(e) {
  403. e.preventDefault();
  404. var $root = $(this).parents('article');
  405. var selectedVersion = $(this).html();
  406. var $button = $root.find('.version');
  407. var currentVersion = $button.find('strong').html();
  408. $button.find('strong').html(selectedVersion);
  409. var group = $root.data('group');
  410. var name = $root.data('name');
  411. var version = $root.data('version');
  412. var compareVersion = $root.data('compare-version');
  413. if (compareVersion === selectedVersion)
  414. return;
  415. if ( ! compareVersion && version == selectedVersion)
  416. return;
  417. if (compareVersion && articleVersions[group][name][0] === selectedVersion || version === selectedVersion) {
  418. // the version of the entry is set to the highest version (reset)
  419. resetArticle(group, name, version);
  420. } else {
  421. var $compareToArticle = $('article[data-group=\'' + group + '\'][data-name=\'' + name + '\'][data-version=\'' + selectedVersion + '\']');
  422. var sourceEntry = {};
  423. var compareEntry = {};
  424. $.each(apiByGroupAndName[group][name], function(index, entry) {
  425. if (entry.version === version)
  426. sourceEntry = entry;
  427. if (entry.version === selectedVersion)
  428. compareEntry = entry;
  429. });
  430. var fields = {
  431. article: sourceEntry,
  432. compare: compareEntry,
  433. versions: articleVersions[group][name]
  434. };
  435. // add unique id
  436. // TODO: replace all group-name-version in template with id.
  437. fields.article.id = fields.article.group + '-' + fields.article.name + '-' + fields.article.version;
  438. fields.article.id = fields.article.id.replace(/\./g, '_');
  439. fields.compare.id = fields.compare.group + '-' + fields.compare.name + '-' + fields.compare.version;
  440. fields.compare.id = fields.compare.id.replace(/\./g, '_');
  441. var entry = sourceEntry;
  442. if (entry.parameter && entry.parameter.fields)
  443. fields._hasTypeInParameterFields = _hasTypeInFields(entry.parameter.fields);
  444. if (entry.error && entry.error.fields)
  445. fields._hasTypeInErrorFields = _hasTypeInFields(entry.error.fields);
  446. if (entry.success && entry.success.fields)
  447. fields._hasTypeInSuccessFields = _hasTypeInFields(entry.success.fields);
  448. if (entry.info && entry.info.fields)
  449. fields._hasTypeInInfoFields = _hasTypeInFields(entry.info.fields);
  450. var entry = compareEntry;
  451. if (fields._hasTypeInParameterFields !== true && entry.parameter && entry.parameter.fields)
  452. fields._hasTypeInParameterFields = _hasTypeInFields(entry.parameter.fields);
  453. if (fields._hasTypeInErrorFields !== true && entry.error && entry.error.fields)
  454. fields._hasTypeInErrorFields = _hasTypeInFields(entry.error.fields);
  455. if (fields._hasTypeInSuccessFields !== true && entry.success && entry.success.fields)
  456. fields._hasTypeInSuccessFields = _hasTypeInFields(entry.success.fields);
  457. if (fields._hasTypeInInfoFields !== true && entry.info && entry.info.fields)
  458. fields._hasTypeInInfoFields = _hasTypeInFields(entry.info.fields);
  459. var content = templateCompareArticle(fields);
  460. $root.after(content);
  461. var $content = $root.next();
  462. // Event on.click re-assign
  463. $content.find('.versions li.version a').on('click', changeVersionCompareTo);
  464. // select navigation
  465. $('#sidenav li[data-group=\'' + group + '\'][data-name=\'' + name + '\'][data-version=\'' + currentVersion + '\']').addClass('has-modifications');
  466. $root.remove();
  467. // TODO: on change main version or select the highest version re-render
  468. }
  469. initDynamic();
  470. }
  471. /**
  472. * Compare all currently selected Versions with their predecessor.
  473. */
  474. function changeAllVersionCompareTo(e) {
  475. e.preventDefault();
  476. $('article:visible .versions').each(function(){
  477. var $root = $(this).parents('article');
  478. var currentVersion = $root.data('version');
  479. var $foundElement = null;
  480. $(this).find('li.version a').each(function() {
  481. var selectVersion = $(this).html();
  482. if (selectVersion < currentVersion && ! $foundElement)
  483. $foundElement = $(this);
  484. });
  485. if($foundElement)
  486. $foundElement.trigger('click');
  487. });
  488. initDynamic();
  489. }
  490. /**
  491. * Add article settings.
  492. */
  493. function addArticleSettings(fields, entry) {
  494. // add unique id
  495. // TODO: replace all group-name-version in template with id.
  496. fields.id = fields.article.group + '-' + fields.article.name + '-' + fields.article.version;
  497. fields.id = fields.id.replace(/\./g, '_');
  498. if (entry.header && entry.header.fields)
  499. fields._hasTypeInHeaderFields = _hasTypeInFields(entry.header.fields);
  500. if (entry.parameter && entry.parameter.fields)
  501. fields._hasTypeInParameterFields = _hasTypeInFields(entry.parameter.fields);
  502. if (entry.error && entry.error.fields)
  503. fields._hasTypeInErrorFields = _hasTypeInFields(entry.error.fields);
  504. if (entry.success && entry.success.fields)
  505. fields._hasTypeInSuccessFields = _hasTypeInFields(entry.success.fields);
  506. if (entry.info && entry.info.fields)
  507. fields._hasTypeInInfoFields = _hasTypeInFields(entry.info.fields);
  508. // add template settings
  509. fields.template = apiProject.template;
  510. }
  511. /**
  512. * Render Article.
  513. */
  514. function renderArticle(group, name, version) {
  515. var entry = {};
  516. $.each(apiByGroupAndName[group][name], function(index, currentEntry) {
  517. if (currentEntry.version === version)
  518. entry = currentEntry;
  519. });
  520. var fields = {
  521. article: entry,
  522. versions: articleVersions[group][name]
  523. };
  524. addArticleSettings(fields, entry);
  525. return templateArticle(fields);
  526. }
  527. /**
  528. * Render original Article and remove the current visible Article.
  529. */
  530. function resetArticle(group, name, version) {
  531. var $root = $('article[data-group=\'' + group + '\'][data-name=\'' + name + '\']:visible');
  532. var content = renderArticle(group, name, version);
  533. $root.after(content);
  534. var $content = $root.next();
  535. // Event on.click muss neu zugewiesen werden (sollte eigentlich mit on automatisch funktionieren... sollte)
  536. $content.find('.versions li.version a').on('click', changeVersionCompareTo);
  537. $('#sidenav li[data-group=\'' + group + '\'][data-name=\'' + name + '\'][data-version=\'' + version + '\']').removeClass('has-modifications');
  538. $root.remove();
  539. return;
  540. }
  541. /**
  542. * Load google fonts.
  543. */
  544. function loadGoogleFontCss() {
  545. var host = document.location.hostname.toLowerCase();
  546. var protocol = document.location.protocol.toLowerCase();
  547. var googleCss = '//fonts.googleapis.com/css?family=Source+Code+Pro|Source+Sans+Pro:400,600,700';
  548. if (host == 'localhost' || !host.length || protocol === 'file:')
  549. googleCss = 'http:' + googleCss;
  550. $('<link/>', {
  551. rel: 'stylesheet',
  552. type: 'text/css',
  553. href: googleCss
  554. }).appendTo('head');
  555. }
  556. /**
  557. * Return ordered entries by custom order and append not defined entries to the end.
  558. * @param {String[]} elements
  559. * @param {String[]} order
  560. * @param {String} splitBy
  561. * @return {String[]} Custom ordered list.
  562. */
  563. function sortByOrder(elements, order, splitBy) {
  564. var results = [];
  565. order.forEach (function(name) {
  566. if (splitBy)
  567. elements.forEach (function(element) {
  568. var parts = element.split(splitBy);
  569. var key = parts[1]; // reference keep for sorting
  570. if (key == name)
  571. results.push(element);
  572. });
  573. else
  574. elements.forEach (function(key) {
  575. if (key == name)
  576. results.push(name);
  577. });
  578. });
  579. // Append all other entries that ar not defined in order
  580. elements.forEach(function(element) {
  581. if (results.indexOf(element) === -1)
  582. results.push(element);
  583. });
  584. return results;
  585. }
  586. });