/** * ownCloud * * @author Vincent Petry * @copyright 2014 Vincent Petry * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE * License as published by the Free Software Foundation; either * version 3 of the License, or any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU AFFERO GENERAL PUBLIC LICENSE for more details. * * You should have received a copy of the GNU Affero General Public * License along with this library. If not, see . * */ (function() { /** * @class BreadCrumb * @memberof OCA.Files * @classdesc Breadcrumbs that represent the current path. * * @param {Object} [options] options * @param {Function} [options.onClick] click event handler * @param {Function} [options.onDrop] drop event handler * @param {Function} [options.getCrumbUrl] callback that returns * the URL of a given breadcrumb */ var BreadCrumb = function(options){ this.$el = $(''); this.$menu = $(''); this.crumbSelector = '.crumb:not(.hidden):not(.crumbhome):not(.crumbmenu)'; this.hiddenCrumbSelector = '.crumb.hidden:not(.crumbhome):not(.crumbmenu)'; options = options || {}; if (options.onClick) { this.onClick = options.onClick; } if (options.onDrop) { this.onDrop = options.onDrop; this.onOver = options.onOver; this.onOut = options.onOut; } if (options.getCrumbUrl) { this.getCrumbUrl = options.getCrumbUrl; } this._detailViews = []; }; /** * @memberof OCA.Files */ BreadCrumb.prototype = { $el: null, dir: null, dirInfo: null, /** * Total width of all breadcrumbs * @type int * @private */ totalWidth: 0, breadcrumbs: [], onClick: null, onDrop: null, onOver: null, onOut: null, /** * Sets the directory to be displayed as breadcrumb. * This will re-render the breadcrumb. * @param dir path to be displayed as breadcrumb */ setDirectory: function(dir) { dir = dir.replace(/\\/g, '/'); dir = dir || '/'; if (dir !== this.dir) { this.dir = dir; this.render(); } }, setDirectoryInfo: function(dirInfo) { if (dirInfo !== this.dirInfo) { this.dirInfo = dirInfo; this.render(); } }, /** * @param {Backbone.View} detailView */ addDetailView: function(detailView) { this._detailViews.push(detailView); }, /** * Returns the full URL to the given directory * * @param {Object.} part crumb data as map * @param {int} index crumb index * @return full URL */ getCrumbUrl: function(part, index) { return '#'; }, /** * Renders the breadcrumb elements */ render: function() { // Menu is destroyed on every change, we need to init it OC.unregisterMenu($('.crumbmenu'), $('.crumbmenu > .popovermenu')); var parts = this._makeCrumbs(this.dir || '/'); var $crumb; var $menuItem; this.$el.empty(); this.breadcrumbs = []; for (var i = 0; i < parts.length; i++) { var part = parts[i]; var $image; var $link = $(''); $crumb = $('
'); if(part.dir) { $link.attr('href', this.getCrumbUrl(part, i)); } if(part.name) { $link.text(part.name); } $link.addClass(part.linkclass); $crumb.append($link); $crumb.data('dir', part.dir); // Ignore menu button $crumb.data('crumb-id', i - 1); $crumb.addClass(part.class); if (part.img) { $image = $(''); $image.attr('src', part.img); $image.attr('alt', part.alt); $link.append($image); } this.breadcrumbs.push($crumb); this.$el.append($crumb); // Only add feedback if not menu if (this.onClick && i !== 0) { $link.on('click', this.onClick); } } // Menu creation this._createMenu(); for (var j = 0; j < parts.length; j++) { var menuPart = parts[j]; if(menuPart.dir) { $menuItem = $('
  • '); $menuItem.data('dir', menuPart.dir); $menuItem.find('a').attr('href', this.getCrumbUrl(part, j)); $menuItem.find('span:eq(1)').text(menuPart.name); this.$menu.children('ul').append($menuItem); if (this.onClick) { $menuItem.on('click', this.onClick); } } } _.each(this._detailViews, function(view) { view.render({ dirInfo: this.dirInfo }); $crumb.append(view.$el); }, this); // in case svg is not supported by the browser we need to execute the fallback mechanism if (!OC.Util.hasSVGSupport()) { OC.Util.replaceSVG(this.$el); } // setup drag and drop if (this.onDrop) { this.$el.find('.crumb:not(:last-child):not(.crumbmenu), .crumblist:not(:last-child)').droppable({ drop: this.onDrop, over: this.onOver, out: this.onOut, tolerance: 'pointer', hoverClass: 'canDrop', greedy: true }); } // Menu is destroyed on every change, we need to init it OC.registerMenu($('.crumbmenu'), $('.crumbmenu > .popovermenu')); this._resize(); }, /** * Makes a breadcrumb structure based on the given path * * @param {String} dir path to split into a breadcrumb structure * @return {Object.} map of {dir: path, name: displayName} */ _makeCrumbs: function(dir) { var crumbs = []; var pathToHere = ''; // trim leading and trailing slashes dir = dir.replace(/^\/+|\/+$/g, ''); var parts = dir.split('/'); if (dir === '') { parts = []; } // menu part crumbs.push({ class: 'crumbmenu hidden', linkclass: 'icon-more' }); // root part crumbs.push({ name: t('core', 'Home'), dir: '/', class: 'crumbhome', linkclass: 'icon-home' }); for (var i = 0; i < parts.length; i++) { var part = parts[i]; pathToHere = pathToHere + '/' + part; crumbs.push({ dir: pathToHere, name: part }); } return crumbs; }, /** * Calculate real width based on individual crumbs * * @param {boolean} ignoreHidden ignore hidden crumbs */ getTotalWidth: function(ignoreHidden) { // The width has to be calculated by adding up the width of all the // crumbs; getting the width of the breadcrumb element is not a // valid approach, as the returned value could be clamped to its // parent width. var totalWidth = 0; for (var i = 0; i < this.breadcrumbs.length; i++ ) { var $crumb = $(this.breadcrumbs[i]); if(!$crumb.hasClass('hidden') || ignoreHidden === true) { totalWidth += $crumb.outerWidth(true); } } return totalWidth; }, /** * Hide the middle crumb */ _hideCrumb: function() { var length = this.$el.find(this.crumbSelector).length; // Get the middle one floored down var elmt = Math.floor(length / 2 - 0.5); this.$el.find(this.crumbSelector+':eq('+elmt+')').addClass('hidden'); }, /** * Get the crumb to show */ _getCrumbElement: function() { var hidden = this.$el.find(this.hiddenCrumbSelector).length; var shown = this.$el.find(this.crumbSelector).length; // Get the outer one with priority to the highest var elmt = (1 - shown % 2) * (hidden - 1); return this.$el.find(this.hiddenCrumbSelector + ':eq('+elmt+')'); }, /** * Show the middle crumb */ _showCrumb: function() { if(this.$el.find(this.hiddenCrumbSelector).length === 1) { this.$el.find(this.hiddenCrumbSelector).removeClass('hidden'); } this._getCrumbElement().removeClass('hidden'); }, /** * Create and append the popovermenu */ _createMenu: function() { this.$el.find('.crumbmenu').append(this.$menu); this.$menu.children('ul').empty(); }, /** * Update the popovermenu */ _updateMenu: function() { var menuItems = this.$el.find(this.hiddenCrumbSelector); this.$menu.find('li').addClass('in-breadcrumb'); for (var i = 0; i < menuItems.length; i++) { var crumbId = $(menuItems[i]).data('crumb-id'); this.$menu.find('li:eq('+crumbId+')').removeClass('in-breadcrumb'); } }, _resize: function() { if (this.breadcrumbs.length <= 2) { // home & menu return; } // Always hide the menu to ensure that it does not interfere with // the width calculations; otherwise, the result could be different // depending on whether the menu was previously being shown or not. this.$el.find('.crumbmenu').addClass('hidden'); // Show the crumbs to compress the siblings before hidding again the // crumbs. This is needed when the siblings expand to fill all the // available width, as in that case their old width would limit the // available width for the crumbs. // Note that the crumbs shown always overflow the parent width // (except, of course, when they all fit in). while (this.$el.find(this.hiddenCrumbSelector).length > 0 && this.getTotalWidth() <= this.$el.parent().width()) { this._showCrumb(); } var siblingsWidth = 0; this.$el.prevAll(':visible').each(function () { siblingsWidth += $(this).outerWidth(true); }); this.$el.nextAll(':visible').each(function () { siblingsWidth += $(this).outerWidth(true); }); var availableWidth = this.$el.parent().width() - siblingsWidth; // If container is smaller than content // AND if there are crumbs left to hide while (this.getTotalWidth() > availableWidth && this.$el.find(this.crumbSelector).length > 0) { // As soon as one of the crumbs is hidden the menu will be // shown. This is needed for proper results in further width // checks. // Note that the menu is not shown only when all the crumbs were // being shown and they all fit the available space; if any of // the crumbs was not being shown then those shown would // overflow the available width, so at least one will be hidden // and thus the menu will be shown. this.$el.find('.crumbmenu').removeClass('hidden'); this._hideCrumb(); } this._updateMenu(); } }; OCA.Files.BreadCrumb = BreadCrumb; })();