navigation.js 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347
  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. },
  156. /**
  157. * Returns whether a given item exists
  158. */
  159. itemExists: function (itemId) {
  160. return this.$el.find('li[data-id="' + itemId + '"]').length;
  161. },
  162. /**
  163. * Event handler for when clicking on an item.
  164. */
  165. _onClickItem: function (ev) {
  166. var $target = $(ev.target);
  167. var itemId = $target.closest('li').attr('data-id');
  168. if (!_.isUndefined(itemId)) {
  169. this.setActiveItem(itemId);
  170. }
  171. ev.preventDefault();
  172. },
  173. /**
  174. * Event handler for clicking a button
  175. */
  176. _onClickMenuButton: function (ev) {
  177. var $target = $(ev.target);
  178. var $menu = $target.parent('li');
  179. var itemId = $target.closest('button').attr('id');
  180. var collapsibleToggles = [];
  181. var dotmenuToggles = [];
  182. if ($menu.hasClass('collapsible') && $menu.data('expandedstate')) {
  183. $menu.toggleClass('open');
  184. var targetAriaExpanded = $target.attr('aria-expanded');
  185. if (targetAriaExpanded === 'false') {
  186. $target.attr('aria-expanded', 'true');
  187. } else if (targetAriaExpanded === 'true') {
  188. $target.attr('aria-expanded', 'false');
  189. }
  190. $menu.toggleAttr('data-expanded', 'true', 'false');
  191. var show = $menu.hasClass('open') ? 1 : 0;
  192. var key = $menu.data('expandedstate');
  193. $.post(OC.generateUrl("/apps/files/api/v1/toggleShowFolder/" + key), {show: show});
  194. }
  195. dotmenuToggles.forEach(function foundToggle (item) {
  196. if (item[0] === ("#" + itemId)) {
  197. document.getElementById(item[1]).classList.toggle('open');
  198. }
  199. });
  200. ev.preventDefault();
  201. },
  202. /**
  203. * Sort initially as setup of sidebar for QuickAccess
  204. */
  205. setInitialQuickaccessSettings: function () {
  206. var quickAccessKey = this.$quickAccessListKey;
  207. var quickAccessMenu = document.getElementById(quickAccessKey);
  208. if (quickAccessMenu) {
  209. var list = quickAccessMenu.getElementsByTagName('li');
  210. this.QuickSort(list, 0, list.length - 1);
  211. }
  212. var favoritesListElement = $(quickAccessMenu).parent();
  213. favoritesListElement.droppable({
  214. over: function (event, ui) {
  215. favoritesListElement.addClass('dropzone-background');
  216. },
  217. out: function (event, ui) {
  218. favoritesListElement.removeClass('dropzone-background');
  219. },
  220. activate: function (event, ui) {
  221. var element = favoritesListElement.find('a').first();
  222. element.addClass('nav-icon-favorites-starred').removeClass('nav-icon-favorites');
  223. },
  224. deactivate: function (event, ui) {
  225. var element = favoritesListElement.find('a').first();
  226. element.addClass('nav-icon-favorites').removeClass('nav-icon-favorites-starred');
  227. },
  228. drop: function (event, ui) {
  229. favoritesListElement.removeClass('dropzone-background');
  230. var $selectedFiles = $(ui.draggable);
  231. if (ui.helper.find('tr').size() === 1) {
  232. var $tr = $selectedFiles.closest('tr');
  233. if ($tr.attr("data-favorite")) {
  234. return;
  235. }
  236. $selectedFiles.trigger('droppedOnFavorites', $tr.attr('data-file'));
  237. } else {
  238. // FIXME: besides the issue described for dropping on
  239. // the trash bin, for favoriting it is not possible to
  240. // use the data from the helper; due to some bugs the
  241. // tags are not always added to the selected files, and
  242. // thus that data can not be accessed through the helper
  243. // to prevent triggering the favorite action on an
  244. // already favorited file (which would remove it from
  245. // favorites).
  246. OC.Notification.showTemporary(t('files', 'You can only favorite a single file or folder at a time'));
  247. }
  248. }
  249. });
  250. },
  251. /**
  252. * Sorting-Algorithm for QuickAccess
  253. */
  254. QuickSort: function (list, start, end) {
  255. var lastMatch;
  256. if (list.length > 1) {
  257. lastMatch = this.quicksort_helper(list, start, end);
  258. if (start < lastMatch - 1) {
  259. this.QuickSort(list, start, lastMatch - 1);
  260. }
  261. if (lastMatch < end) {
  262. this.QuickSort(list, lastMatch, end);
  263. }
  264. }
  265. },
  266. /**
  267. * Sorting-Algorithm-Helper for QuickAccess
  268. */
  269. quicksort_helper: function (list, start, end) {
  270. var pivot = Math.floor((end + start) / 2);
  271. var pivotElement = this.getCompareValue(list, pivot);
  272. var i = start;
  273. var j = end;
  274. while (i <= j) {
  275. while (this.getCompareValue(list, i) < pivotElement) {
  276. i++;
  277. }
  278. while (this.getCompareValue(list, j) > pivotElement) {
  279. j--;
  280. }
  281. if (i <= j) {
  282. this.swap(list, i, j);
  283. i++;
  284. j--;
  285. }
  286. }
  287. return i;
  288. },
  289. /**
  290. * Sorting-Algorithm-Helper for QuickAccess
  291. * This method allows easy access to the element which is sorted by.
  292. */
  293. getCompareValue: function (nodes, int, strategy) {
  294. return nodes[int].getElementsByTagName('a')[0].innerHTML.toLowerCase();
  295. },
  296. /**
  297. * Sorting-Algorithm-Helper for QuickAccess
  298. * This method allows easy swapping of elements.
  299. */
  300. swap: function (list, j, i) {
  301. var before = function(node, insertNode) {
  302. node.parentNode.insertBefore(insertNode, node);
  303. }
  304. before(list[i], list[j]);
  305. before(list[j], list[i]);
  306. }
  307. };
  308. OCA.Files.Navigation = Navigation;
  309. })();