You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

breadcrumb.js 7.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  1. /**
  2. * ownCloud
  3. *
  4. * @author Vincent Petry
  5. * @copyright 2014 Vincent Petry <pvince81@owncloud.com>
  6. *
  7. * This library is free software; you can redistribute it and/or
  8. * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
  9. * License as published by the Free Software Foundation; either
  10. * version 3 of the License, or any later version.
  11. *
  12. * This library is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
  16. *
  17. * You should have received a copy of the GNU Affero General Public
  18. * License along with this library. If not, see <http://www.gnu.org/licenses/>.
  19. *
  20. */
  21. (function() {
  22. /**
  23. * @class BreadCrumb
  24. * @memberof OCA.Files
  25. * @classdesc Breadcrumbs that represent the current path.
  26. *
  27. * @param {Object} [options] options
  28. * @param {Function} [options.onClick] click event handler
  29. * @param {Function} [options.onDrop] drop event handler
  30. * @param {Function} [options.getCrumbUrl] callback that returns
  31. * the URL of a given breadcrumb
  32. */
  33. var BreadCrumb = function(options){
  34. this.$el = $('<div class="breadcrumb"></div>');
  35. options = options || {};
  36. if (options.onClick) {
  37. this.onClick = options.onClick;
  38. }
  39. if (options.onDrop) {
  40. this.onDrop = options.onDrop;
  41. this.onOver = options.onOver;
  42. this.onOut = options.onOut;
  43. }
  44. if (options.getCrumbUrl) {
  45. this.getCrumbUrl = options.getCrumbUrl;
  46. }
  47. this._detailViews = [];
  48. };
  49. /**
  50. * @memberof OCA.Files
  51. */
  52. BreadCrumb.prototype = {
  53. $el: null,
  54. dir: null,
  55. dirInfo: null,
  56. /**
  57. * Total width of all breadcrumbs
  58. * @type int
  59. * @private
  60. */
  61. totalWidth: 0,
  62. breadcrumbs: [],
  63. onClick: null,
  64. onDrop: null,
  65. onOver: null,
  66. onOut: null,
  67. /**
  68. * Sets the directory to be displayed as breadcrumb.
  69. * This will re-render the breadcrumb.
  70. * @param dir path to be displayed as breadcrumb
  71. */
  72. setDirectory: function(dir) {
  73. dir = dir.replace(/\\/g, '/');
  74. dir = dir || '/';
  75. if (dir !== this.dir) {
  76. this.dir = dir;
  77. this.render();
  78. }
  79. },
  80. setDirectoryInfo: function(dirInfo) {
  81. if (dirInfo !== this.dirInfo) {
  82. this.dirInfo = dirInfo;
  83. this.render();
  84. }
  85. },
  86. /**
  87. * @param {Backbone.View} detailView
  88. */
  89. addDetailView: function(detailView) {
  90. this._detailViews.push(detailView);
  91. },
  92. /**
  93. * Returns the full URL to the given directory
  94. *
  95. * @param {Object.<String, String>} part crumb data as map
  96. * @param {int} index crumb index
  97. * @return full URL
  98. */
  99. getCrumbUrl: function(part, index) {
  100. return '#';
  101. },
  102. /**
  103. * Renders the breadcrumb elements
  104. */
  105. render: function() {
  106. var parts = this._makeCrumbs(this.dir || '/');
  107. var $crumb;
  108. this.$el.empty();
  109. this.breadcrumbs = [];
  110. for (var i = 0; i < parts.length; i++) {
  111. var part = parts[i];
  112. var $image;
  113. var $link = $('<a></a>').attr('href', this.getCrumbUrl(part, i));
  114. $link.text(part.name);
  115. $crumb = $('<div class="crumb svg"></div>');
  116. $crumb.append($link);
  117. $crumb.attr('data-dir', part.dir);
  118. if (part.img) {
  119. $image = $('<img class="svg"></img>');
  120. $image.attr('src', part.img);
  121. $image.attr('alt', part.alt);
  122. $link.append($image);
  123. }
  124. this.breadcrumbs.push($crumb);
  125. this.$el.append($crumb);
  126. if (this.onClick) {
  127. $crumb.on('click', this.onClick);
  128. }
  129. }
  130. $crumb.addClass('last');
  131. _.each(this._detailViews, function(view) {
  132. view.render({
  133. dirInfo: this.dirInfo
  134. });
  135. $crumb.append(view.$el);
  136. }, this);
  137. // in case svg is not supported by the browser we need to execute the fallback mechanism
  138. if (!OC.Util.hasSVGSupport()) {
  139. OC.Util.replaceSVG(this.$el);
  140. }
  141. // setup drag and drop
  142. if (this.onDrop) {
  143. this.$el.find('.crumb:not(.last)').droppable({
  144. drop: this.onDrop,
  145. over: this.onOver,
  146. out: this.onOut,
  147. tolerance: 'pointer',
  148. hoverClass: 'canDrop'
  149. });
  150. }
  151. this._updateTotalWidth();
  152. },
  153. /**
  154. * Makes a breadcrumb structure based on the given path
  155. *
  156. * @param {String} dir path to split into a breadcrumb structure
  157. * @return {Object.<String, String>} map of {dir: path, name: displayName}
  158. */
  159. _makeCrumbs: function(dir) {
  160. var crumbs = [];
  161. var pathToHere = '';
  162. // trim leading and trailing slashes
  163. dir = dir.replace(/^\/+|\/+$/g, '');
  164. var parts = dir.split('/');
  165. if (dir === '') {
  166. parts = [];
  167. }
  168. // root part
  169. crumbs.push({
  170. dir: '/',
  171. name: '',
  172. alt: t('files', 'Home'),
  173. img: OC.imagePath('core', 'places/home.svg')
  174. });
  175. for (var i = 0; i < parts.length; i++) {
  176. var part = parts[i];
  177. pathToHere = pathToHere + '/' + part;
  178. crumbs.push({
  179. dir: pathToHere,
  180. name: part
  181. });
  182. }
  183. return crumbs;
  184. },
  185. /**
  186. * Calculate the total breadcrumb width when
  187. * all crumbs are expanded
  188. */
  189. _updateTotalWidth: function () {
  190. this.totalWidth = 0;
  191. for (var i = 0; i < this.breadcrumbs.length; i++ ) {
  192. var $crumb = $(this.breadcrumbs[i]);
  193. $crumb.data('real-width', $crumb.width());
  194. this.totalWidth += $crumb.width();
  195. }
  196. this._resize();
  197. },
  198. /**
  199. * Show/hide breadcrumbs to fit the given width
  200. *
  201. * @param {int} availableWidth available width
  202. */
  203. setMaxWidth: function (availableWidth) {
  204. if (this.availableWidth !== availableWidth) {
  205. this.availableWidth = availableWidth;
  206. this._resize();
  207. }
  208. },
  209. _resize: function() {
  210. var i, $crumb, $ellipsisCrumb;
  211. if (!this.availableWidth) {
  212. this.availableWidth = this.$el.width();
  213. }
  214. if (this.breadcrumbs.length <= 1) {
  215. return;
  216. }
  217. // reset crumbs
  218. this.$el.find('.crumb.ellipsized').remove();
  219. // unhide all
  220. this.$el.find('.crumb.hidden').removeClass('hidden');
  221. if (this.totalWidth <= this.availableWidth) {
  222. // no need to compute breadcrumbs, there is enough space
  223. return;
  224. }
  225. // running width, considering the hidden crumbs
  226. var currentTotalWidth = $(this.breadcrumbs[0]).data('real-width');
  227. var firstHidden = true;
  228. // insert ellipsis after root part (root part is always visible)
  229. $ellipsisCrumb = $('<div class="crumb ellipsized svg"><span class="ellipsis">...</span></div>');
  230. $(this.breadcrumbs[0]).after($ellipsisCrumb);
  231. currentTotalWidth += $ellipsisCrumb.width();
  232. i = this.breadcrumbs.length - 1;
  233. // find the first section that would cause the overflow
  234. // then hide everything in front of that
  235. //
  236. // this ensures that the last crumb section stays visible
  237. // for most of the cases and is always the last one to be
  238. // hidden when the screen becomes very narrow
  239. while (i > 0) {
  240. $crumb = $(this.breadcrumbs[i]);
  241. // if the current breadcrumb would cause overflow
  242. if (!firstHidden || currentTotalWidth + $crumb.data('real-width') > this.availableWidth) {
  243. // hide it
  244. $crumb.addClass('hidden');
  245. if (firstHidden) {
  246. // set the path of this one as title for the ellipsis
  247. this.$el.find('.crumb.ellipsized')
  248. .attr('title', $crumb.attr('data-dir'))
  249. .tooltip();
  250. this.$el.find('.ellipsis')
  251. .wrap('<a class="ellipsislink" href="' + encodeURI(OC.generateUrl('apps/files/?dir=' + $crumb.attr('data-dir'))) + '"></a>');
  252. }
  253. // and all the previous ones (going backwards)
  254. firstHidden = false;
  255. } else {
  256. // add to total width
  257. currentTotalWidth += $crumb.data('real-width');
  258. }
  259. i--;
  260. }
  261. if (!OC.Util.hasSVGSupport()) {
  262. OC.Util.replaceSVG(this.$el);
  263. }
  264. }
  265. };
  266. OCA.Files.BreadCrumb = BreadCrumb;
  267. })();