navigation.js 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349
  1. /*
  2. * @Copyright 2014 Vincent Petry <pvince81@owncloud.com>
  3. *
  4. * @author Vincent Petry
  5. * @author Felix Nüsse <felix.nuesse@t-online.de>
  6. *
  7. *
  8. * This file is licensed under the Affero General Public License version 3
  9. * or later.
  10. *
  11. * See the COPYING-README file.
  12. *
  13. */
  14. (function () {
  15. /**
  16. * @class OCA.Files.Navigation
  17. * @classdesc Navigation control for the files app sidebar.
  18. *
  19. * @param $el element containing the navigation
  20. */
  21. var Navigation = function ($el) {
  22. this.initialize($el);
  23. };
  24. /**
  25. * @memberof OCA.Files
  26. */
  27. Navigation.prototype = {
  28. /**
  29. * Currently selected item in the list
  30. */
  31. _activeItem: null,
  32. /**
  33. * Currently selected container
  34. */
  35. $currentContent: null,
  36. /**
  37. * Key for the quick-acces-list
  38. */
  39. $quickAccessListKey: 'sublist-favorites',
  40. /**
  41. * Initializes the navigation from the given container
  42. *
  43. * @private
  44. * @param $el element containing the navigation
  45. */
  46. initialize: function ($el) {
  47. this.$el = $el;
  48. this._activeItem = null;
  49. this.$currentContent = null;
  50. this._setupEvents();
  51. this.setInitialQuickaccessSettings();
  52. },
  53. /**
  54. * Setup UI events
  55. */
  56. _setupEvents: function () {
  57. this.$el.on('click', 'li a', _.bind(this._onClickItem, this));
  58. this.$el.on('click', 'li button', _.bind(this._onClickMenuButton, this));
  59. var trashBinElement = $('.nav-trashbin');
  60. trashBinElement.droppable({
  61. over: function (event, ui) {
  62. trashBinElement.addClass('dropzone-background');
  63. },
  64. out: function (event, ui) {
  65. trashBinElement.removeClass('dropzone-background');
  66. },
  67. activate: function (event, ui) {
  68. var element = trashBinElement.find('a').first();
  69. element.addClass('nav-icon-trashbin-starred').removeClass('nav-icon-trashbin');
  70. },
  71. deactivate: function (event, ui) {
  72. var element = trashBinElement.find('a').first();
  73. element.addClass('nav-icon-trashbin').removeClass('nav-icon-trashbin-starred');
  74. },
  75. drop: function (event, ui) {
  76. trashBinElement.removeClass('dropzone-background');
  77. var $selectedFiles = $(ui.draggable);
  78. // FIXME: when there are a lot of selected files the helper
  79. // contains only a subset of them; the list of selected
  80. // files should be gotten from the file list instead to
  81. // ensure that all of them are removed.
  82. var item = ui.helper.find('tr');
  83. for (var i = 0; i < item.length; i++) {
  84. $selectedFiles.trigger('droppedOnTrash', item[i].getAttribute('data-file'), item[i].getAttribute('data-dir'));
  85. }
  86. }
  87. });
  88. },
  89. /**
  90. * Returns the container of the currently active app.
  91. *
  92. * @return app container
  93. */
  94. getActiveContainer: function () {
  95. return this.$currentContent;
  96. },
  97. /**
  98. * Returns the currently active item
  99. *
  100. * @return item ID
  101. */
  102. getActiveItem: function () {
  103. return this._activeItem;
  104. },
  105. /**
  106. * Switch the currently selected item, mark it as selected and
  107. * make the content container visible, if any.
  108. *
  109. * @param string itemId id of the navigation item to select
  110. * @param array options "silent" to not trigger event
  111. */
  112. setActiveItem: function (itemId, options) {
  113. var currentItem = this.$el.find('li[data-id="' + itemId + '"]');
  114. var itemDir = currentItem.data('dir');
  115. var itemView = currentItem.data('view');
  116. var oldItemId = this._activeItem;
  117. if (itemId === this._activeItem) {
  118. if (!options || !options.silent) {
  119. this.$el.trigger(
  120. new $.Event('itemChanged', {
  121. itemId: itemId,
  122. previousItemId: oldItemId,
  123. dir: itemDir,
  124. view: itemView
  125. })
  126. );
  127. }
  128. return;
  129. }
  130. this.$el.find('li a').removeClass('active').removeAttr('aria-current');
  131. if (this.$currentContent) {
  132. this.$currentContent.addClass('hidden');
  133. this.$currentContent.trigger(jQuery.Event('hide'));
  134. }
  135. this._activeItem = itemId;
  136. currentItem.children('a').addClass('active').attr('aria-current', 'page');
  137. this.$currentContent = $('#app-content-' + (typeof itemView === 'string' && itemView !== '' ? itemView : itemId));
  138. this.$currentContent.removeClass('hidden');
  139. if (!options || !options.silent) {
  140. this.$currentContent.trigger(jQuery.Event('show', {
  141. itemId: itemId,
  142. previousItemId: oldItemId,
  143. dir: itemDir,
  144. view: itemView
  145. }));
  146. this.$el.trigger(
  147. new $.Event('itemChanged', {
  148. itemId: itemId,
  149. previousItemId: oldItemId,
  150. dir: itemDir,
  151. view: itemView
  152. })
  153. );
  154. }
  155. var currentItemName = this.$el.find('li[data-id="' + itemId + '"] > a').text();
  156. window.OCP.Accessibility.setPageHeading(currentItemName);
  157. },
  158. /**
  159. * Returns whether a given item exists
  160. */
  161. itemExists: function (itemId) {
  162. return this.$el.find('li[data-id="' + itemId + '"]').length;
  163. },
  164. /**
  165. * Event handler for when clicking on an item.
  166. */
  167. _onClickItem: function (ev) {
  168. var $target = $(ev.target);
  169. var itemId = $target.closest('li').attr('data-id');
  170. if (!_.isUndefined(itemId)) {
  171. this.setActiveItem(itemId);
  172. }
  173. ev.preventDefault();
  174. },
  175. /**
  176. * Event handler for clicking a button
  177. */
  178. _onClickMenuButton: function (ev) {
  179. var $target = $(ev.target);
  180. var $menu = $target.parent('li');
  181. var itemId = $target.closest('button').attr('id');
  182. var collapsibleToggles = [];
  183. var dotmenuToggles = [];
  184. if ($menu.hasClass('collapsible') && $menu.data('expandedstate')) {
  185. $menu.toggleClass('open');
  186. var targetAriaExpanded = $target.attr('aria-expanded');
  187. if (targetAriaExpanded === 'false') {
  188. $target.attr('aria-expanded', 'true');
  189. } else if (targetAriaExpanded === 'true') {
  190. $target.attr('aria-expanded', 'false');
  191. }
  192. $menu.toggleAttr('data-expanded', 'true', 'false');
  193. var show = $menu.hasClass('open') ? 1 : 0;
  194. var key = $menu.data('expandedstate');
  195. $.post(OC.generateUrl("/apps/files/api/v1/toggleShowFolder/" + key), {show: show});
  196. }
  197. dotmenuToggles.forEach(function foundToggle (item) {
  198. if (item[0] === ("#" + itemId)) {
  199. document.getElementById(item[1]).classList.toggle('open');
  200. }
  201. });
  202. ev.preventDefault();
  203. },
  204. /**
  205. * Sort initially as setup of sidebar for QuickAccess
  206. */
  207. setInitialQuickaccessSettings: function () {
  208. var quickAccessKey = this.$quickAccessListKey;
  209. var quickAccessMenu = document.getElementById(quickAccessKey);
  210. if (quickAccessMenu) {
  211. var list = quickAccessMenu.getElementsByTagName('li');
  212. this.QuickSort(list, 0, list.length - 1);
  213. }
  214. var favoritesListElement = $(quickAccessMenu).parent();
  215. favoritesListElement.droppable({
  216. over: function (event, ui) {
  217. favoritesListElement.addClass('dropzone-background');
  218. },
  219. out: function (event, ui) {
  220. favoritesListElement.removeClass('dropzone-background');
  221. },
  222. activate: function (event, ui) {
  223. var element = favoritesListElement.find('a').first();
  224. element.addClass('nav-icon-favorites-starred').removeClass('nav-icon-favorites');
  225. },
  226. deactivate: function (event, ui) {
  227. var element = favoritesListElement.find('a').first();
  228. element.addClass('nav-icon-favorites').removeClass('nav-icon-favorites-starred');
  229. },
  230. drop: function (event, ui) {
  231. favoritesListElement.removeClass('dropzone-background');
  232. var $selectedFiles = $(ui.draggable);
  233. if (ui.helper.find('tr').size() === 1) {
  234. var $tr = $selectedFiles.closest('tr');
  235. if ($tr.attr("data-favorite")) {
  236. return;
  237. }
  238. $selectedFiles.trigger('droppedOnFavorites', $tr.attr('data-file'));
  239. } else {
  240. // FIXME: besides the issue described for dropping on
  241. // the trash bin, for favoriting it is not possible to
  242. // use the data from the helper; due to some bugs the
  243. // tags are not always added to the selected files, and
  244. // thus that data can not be accessed through the helper
  245. // to prevent triggering the favorite action on an
  246. // already favorited file (which would remove it from
  247. // favorites).
  248. OC.Notification.showTemporary(t('files', 'You can only favorite a single file or folder at a time'));
  249. }
  250. }
  251. });
  252. },
  253. /**
  254. * Sorting-Algorithm for QuickAccess
  255. */
  256. QuickSort: function (list, start, end) {
  257. var lastMatch;
  258. if (list.length > 1) {
  259. lastMatch = this.quicksort_helper(list, start, end);
  260. if (start < lastMatch - 1) {
  261. this.QuickSort(list, start, lastMatch - 1);
  262. }
  263. if (lastMatch < end) {
  264. this.QuickSort(list, lastMatch, end);
  265. }
  266. }
  267. },
  268. /**
  269. * Sorting-Algorithm-Helper for QuickAccess
  270. */
  271. quicksort_helper: function (list, start, end) {
  272. var pivot = Math.floor((end + start) / 2);
  273. var pivotElement = this.getCompareValue(list, pivot);
  274. var i = start;
  275. var j = end;
  276. while (i <= j) {
  277. while (this.getCompareValue(list, i) < pivotElement) {
  278. i++;
  279. }
  280. while (this.getCompareValue(list, j) > pivotElement) {
  281. j--;
  282. }
  283. if (i <= j) {
  284. this.swap(list, i, j);
  285. i++;
  286. j--;
  287. }
  288. }
  289. return i;
  290. },
  291. /**
  292. * Sorting-Algorithm-Helper for QuickAccess
  293. * This method allows easy access to the element which is sorted by.
  294. */
  295. getCompareValue: function (nodes, int, strategy) {
  296. return nodes[int].getElementsByTagName('a')[0].innerHTML.toLowerCase();
  297. },
  298. /**
  299. * Sorting-Algorithm-Helper for QuickAccess
  300. * This method allows easy swapping of elements.
  301. */
  302. swap: function (list, j, i) {
  303. var before = function(node, insertNode) {
  304. node.parentNode.insertBefore(insertNode, node);
  305. }
  306. before(list[i], list[j]);
  307. before(list[j], list[i]);
  308. }
  309. };
  310. OCA.Files.Navigation = Navigation;
  311. })();