Source: apps/files/js/detailsview.js

/*
 * Copyright (c) 2015
 *
 * This file is licensed under the Affero General Public License version 3
 * or later.
 *
 * See the COPYING-README file.
 *
 */

(function() {
	/**
	 * @class OCA.Files.DetailsView
	 * @classdesc
	 *
	 * The details view show details about a selected file.
	 *
	 */
	var DetailsView = OC.Backbone.View.extend({
		id: 'app-sidebar',
		tabName: 'div',
		className: 'detailsView scroll-container',

		/**
		 * List of detail tab views
		 *
		 * @type Array<OCA.Files.DetailTabView>
		 */
		_tabViews: [],

		/**
		 * List of detail file info views
		 *
		 * @type Array<OCA.Files.DetailFileInfoView>
		 */
		_detailFileInfoViews: [],

		/**
		 * Id of the currently selected tab
		 *
		 * @type string
		 */
		_currentTabId: null,

		/**
		 * Dirty flag, whether the view needs to be rerendered
		 */
		_dirty: false,

		events: {
			'click a.close': '_onClose',
			'click .tabHeaders .tabHeader': '_onClickTab',
			'keyup .tabHeaders .tabHeader': '_onKeyboardActivateTab'
		},

		/**
		 * Initialize the details view
		 */
		initialize: function() {
			this._tabViews = [];
			this._detailFileInfoViews = [];

			this._dirty = true;
		},

		_onClose: function(event) {
			OC.Apps.hideAppSidebar(this.$el);
			event.preventDefault();
		},

		_onClickTab: function(e) {
			var $target = $(e.target);
			e.preventDefault();
			if (!$target.hasClass('tabHeader')) {
				$target = $target.closest('.tabHeader');
			}
			var tabId = $target.attr('data-tabid');
			if (_.isUndefined(tabId)) {
				return;
			}

			this.selectTab(tabId);
		},

		_onKeyboardActivateTab: function (event) {
			if (event.key === " " || event.key === "Enter") {
				this._onClickTab(event);
			}
		},

		template: function(vars) {
			return OCA.Files.Templates['detailsview'](vars);
		},

		/**
		 * Renders this details view
		 */
		render: function() {
			// remove old instances
			var $appSidebar = $('#app-sidebar');
			if ($appSidebar.length === 0) {
				this.$el.insertAfter($('#app-content'));
			} else {
				if ($appSidebar[0] !== this.el) {
					$appSidebar.replaceWith(this.$el);
				}
			}
			
			var templateVars = {
				closeLabel: t('files', 'Close')
			};

			this._tabViews = this._tabViews.sort(function(tabA, tabB) {
				var orderA = tabA.order || 0;
				var orderB = tabB.order || 0;
				if (orderA === orderB) {
					return OC.Util.naturalSortCompare(tabA.getLabel(), tabB.getLabel());
				}
				return orderA - orderB;
			});

			templateVars.tabHeaders = _.map(this._tabViews, function(tabView, i) {
				return {
					tabId: tabView.id,
					label: tabView.getLabel(),
					tabIcon: tabView.getIcon()
				};
			});

			this.$el.html(this.template(templateVars));

			var $detailsContainer = this.$el.find('.detailFileInfoContainer');

			// render details
			_.each(this._detailFileInfoViews, function(detailView) {
				$detailsContainer.append(detailView.get$());
			});

			if (!this._currentTabId && this._tabViews.length > 0) {
				this._currentTabId = this._tabViews[0].id;
			}

			this.selectTab(this._currentTabId);

			this._updateTabVisibilities();

			this._dirty = false;
		},

		/**
		 * Selects the given tab by id
		 *
		 * @param {string} tabId tab id
		 */
		selectTab: function(tabId) {
			if (!tabId) {
				return;
			}

			var tabView = _.find(this._tabViews, function(tab) {
				return tab.id === tabId;
			});

			if (!tabView) {
				console.warn('Details view tab with id "' + tabId + '" not found');
				return;
			}

			this._currentTabId = tabId;

			var $tabsContainer = this.$el.find('.tabsContainer');
			var $tabEl = $tabsContainer.find('#' + tabId);

			// hide other tabs
			$tabsContainer.find('.tab').addClass('hidden');

			// tab already rendered ?
			if (!$tabEl.length) {
				// render tab
				$tabsContainer.append(tabView.$el);
				$tabEl = tabView.$el;
			}

			// this should trigger tab rendering
			tabView.setFileInfo(this.model);

			$tabEl.removeClass('hidden');

			// update tab headers
			var $tabHeaders = this.$el.find('.tabHeaders li');
			$tabHeaders.removeClass('selected');
			$tabHeaders.filterAttr('data-tabid', tabView.id).addClass('selected');
		},

		/**
		 * Sets the file info to be displayed in the view
		 *
		 * @param {OCA.Files.FileInfoModel} fileInfo file info to set
		 */
		setFileInfo: function(fileInfo) {
			this.model = fileInfo;

			if (this._dirty) {
				this.render();
			} else {
				this._updateTabVisibilities();
			}

			if (this._currentTabId) {
				// only update current tab, others will be updated on-demand
				var tabId = this._currentTabId;
				var tabView = _.find(this._tabViews, function(tab) {
					return tab.id === tabId;
				});
				tabView.setFileInfo(fileInfo);
			}

			_.each(this._detailFileInfoViews, function(detailView) {
				detailView.setFileInfo(fileInfo);
			});
		},

		/**
		 * Update tab headers based on the current model
		 */
		_updateTabVisibilities: function() {
			// update tab header visibilities
			var self = this;
			var deselect = false;
			var countVisible = 0;
			var $tabHeaders = this.$el.find('.tabHeaders li');
			_.each(this._tabViews, function(tabView) {
				var isVisible = tabView.canDisplay(self.model);
				if (isVisible) {
					countVisible += 1;
				}
				if (!isVisible && self._currentTabId === tabView.id) {
					deselect = true;
				}
				$tabHeaders.filterAttr('data-tabid', tabView.id).toggleClass('hidden', !isVisible);
			});

			// hide the whole container if there is only one tab
			this.$el.find('.tabHeaders').toggleClass('hidden', countVisible <= 1);

			if (deselect) {
				// select the first visible tab instead
				var visibleTabId = this.$el.find('.tabHeader:not(.hidden):first').attr('data-tabid');
				this.selectTab(visibleTabId);
			}

		},

		/**
		 * Returns the file info.
		 *
		 * @return {OCA.Files.FileInfoModel} file info
		 */
		getFileInfo: function() {
			return this.model;
		},

		/**
		 * Adds a tab in the tab view
		 *
		 * @param {OCA.Files.DetailTabView} tab view
		 */
		addTabView: function(tabView) {
			this._tabViews.push(tabView);
			this._dirty = true;
		},

		/**
		 * Adds a detail view for file info.
		 *
		 * @param {OCA.Files.DetailFileInfoView} detail view
		 */
		addDetailView: function(detailView) {
			this._detailFileInfoViews.push(detailView);
			this._dirty = true;
		},

		/**
		 * Returns an array with the added DetailFileInfoViews.
		 *
		 * @return Array<OCA.Files.DetailFileInfoView> an array with the added
		 *         DetailFileInfoViews.
		 */
		getDetailViews: function() {
			return [].concat(this._detailFileInfoViews);
		}
	});

	OCA.Files.DetailsView = DetailsView;
})();