summaryrefslogtreecommitdiffstats
path: root/apps/files/js/fileactions.js
diff options
context:
space:
mode:
authorVincent Petry <pvince81@owncloud.com>2015-07-16 15:28:45 +0200
committerVincent Petry <pvince81@owncloud.com>2015-08-10 14:12:34 +0200
commitca34921cdf8db4075906b3531390aa1b1ae9216c (patch)
tree49c8f0256fa2fb6d13bbec3d15cd4a1f90153c99 /apps/files/js/fileactions.js
parent15e16d335db5771778477e944d4e63ac807382b9 (diff)
downloadnextcloud-server-ca34921cdf8db4075906b3531390aa1b1ae9216c.tar.gz
nextcloud-server-ca34921cdf8db4075906b3531390aa1b1ae9216c.zip
Implement file actions dropdown
File actions now have two types "inline" and "dropdown". The default is "dropdown". The file actions will now be shown in a dropdown menu.
Diffstat (limited to 'apps/files/js/fileactions.js')
-rw-r--r--apps/files/js/fileactions.js302
1 files changed, 176 insertions, 126 deletions
diff --git a/apps/files/js/fileactions.js b/apps/files/js/fileactions.js
index 8dd26d71c3e..3c13f087f7a 100644
--- a/apps/files/js/fileactions.js
+++ b/apps/files/js/fileactions.js
@@ -10,6 +10,12 @@
(function() {
+ var TEMPLATE_FILE_ACTION_TRIGGER =
+ '<a class="action action-{{nameLowerCase}}" href="#" data-action="{{name}}">' +
+ '{{#if icon}}<img class="svg" alt="{{altText}}" src="{{icon}}" />{{/if}}' +
+ '{{#if displayName}}<span> {{displayName}}</span>{{/if}}' +
+ '</a>';
+
/**
* Construct a new FileActions instance
* @constructs FileActions
@@ -18,6 +24,8 @@
var FileActions = function() {
this.initialize();
};
+ FileActions.TYPE_DROPDOWN = 0;
+ FileActions.TYPE_INLINE = 1;
FileActions.prototype = {
/** @lends FileActions.prototype */
actions: {},
@@ -38,6 +46,15 @@
*/
_updateListeners: {},
+ _fileActionTriggerTemplate: null,
+
+ /**
+ * File actions menu
+ *
+ * @type OCA.Files.FileActionsMenu
+ */
+ _menu: null,
+
/**
* @private
*/
@@ -46,6 +63,8 @@
// abusing jquery for events until we get a real event lib
this.$el = $('<div class="dummy-fileactions hidden"></div>');
$('body').append(this.$el);
+
+ this._showMenuClosure = _.bind(this._showMenu, this);
},
/**
@@ -111,6 +130,7 @@
displayName: displayName || name
});
},
+
/**
* Register action
*
@@ -125,15 +145,14 @@
displayName: action.displayName,
mime: mime,
icon: action.icon,
- permissions: action.permissions
+ permissions: action.permissions,
+ type: action.type || FileActions.TYPE_DROPDOWN
};
if (_.isUndefined(action.displayName)) {
actionSpec.displayName = t('files', name);
}
if (_.isFunction(action.render)) {
actionSpec.render = action.render;
- } else {
- actionSpec.render = _.bind(this._defaultRenderAction, this);
}
if (!this.actions[mime]) {
this.actions[mime] = {};
@@ -162,6 +181,16 @@
this.defaults[mime] = name;
this._notifyUpdateListeners('setDefault', {defaultAction: {mime: mime, name: name}});
},
+
+ /**
+ * Returns a map of file actions handlers matching the given conditions
+ *
+ * @param {string} mime mime type
+ * @param {string} type "dir" or "file"
+ * @param {int} permissions permissions
+ *
+ * @return {Object.<string,OCA.Files.FileActions~actionHandler>} map of action name to action spec
+ */
get: function (mime, type, permissions) {
var actions = this.getActions(mime, type, permissions);
var filteredActions = {};
@@ -170,6 +199,16 @@
});
return filteredActions;
},
+
+ /**
+ * Returns an array of file actions matching the given conditions
+ *
+ * @param {string} mime mime type
+ * @param {string} type "dir" or "file"
+ * @param {int} permissions permissions
+ *
+ * @return {Array.<OCA.Files.FileAction>} array of action specs
+ */
getActions: function (mime, type, permissions) {
var actions = {};
if (this.actions.all) {
@@ -197,7 +236,37 @@
});
return filteredActions;
},
+
+ /**
+ * Returns the default file action handler for the given conditions
+ *
+ * @param {string} mime mime type
+ * @param {string} type "dir" or "file"
+ * @param {int} permissions permissions
+ *
+ * @return {OCA.Files.FileActions~actionHandler} action handler
+ *
+ * @deprecated use getDefaultFileAction instead
+ */
getDefault: function (mime, type, permissions) {
+ var defaultActionSpec = this.getDefaultFileAction(mime, type, permissions);
+ if (defaultActionSpec) {
+ return defaultActionSpec.action;
+ }
+ return undefined;
+ },
+
+ /**
+ * Returns the default file action handler for the given conditions
+ *
+ * @param {string} mime mime type
+ * @param {string} type "dir" or "file"
+ * @param {int} permissions permissions
+ *
+ * @return {OCA.Files.FileActions~actionHandler} action handler
+ * @since 8.2
+ */
+ getDefaultFileAction: function(mime, type, permissions) {
var mimePart;
if (mime) {
mimePart = mime.substr(0, mime.indexOf('/'));
@@ -212,9 +281,10 @@
} else {
name = this.defaults.all;
}
- var actions = this.get(mime, type, permissions);
+ var actions = this.getActions(mime, type, permissions);
return actions[name];
},
+
/**
* Default function to render actions
*
@@ -225,86 +295,68 @@
*/
_defaultRenderAction: function(actionSpec, isDefault, context) {
var name = actionSpec.name;
- if (name === 'Download' || !isDefault) {
- var $actionLink = this._makeActionLink(actionSpec, context);
+ if (!isDefault) {
+ var params = {
+ name: actionSpec.name,
+ nameLowerCase: actionSpec.name.toLowerCase(),
+ displayName: actionSpec.displayName,
+ icon: actionSpec.icon,
+ altText: actionSpec.altText,
+ };
+ if (_.isFunction(actionSpec.icon)) {
+ params.icon = actionSpec.icon(context.$file.attr('data-file'));
+ }
+
+ var $actionLink = this._makeActionLink(params, context);
context.$file.find('a.name>span.fileactions').append($actionLink);
return $actionLink;
}
},
+
/**
* Renders the action link element
*
- * @param {OCA.Files.FileAction} actionSpec action object
- * @param {OCA.Files.FileActionContext} context action context
+ * @param {Object} params action params
*/
- _makeActionLink: function(actionSpec, context) {
- var img = actionSpec.icon;
- if (img && img.call) {
- img = img(context.$file.attr('data-file'));
- }
- var html = '<a href="#">';
- if (img) {
- html += '<img class="svg" alt="" src="' + img + '" />';
- }
- if (actionSpec.displayName) {
- html += '<span> ' + actionSpec.displayName + '</span>';
+ _makeActionLink: function(params) {
+ if (!this._fileActionTriggerTemplate) {
+ this._fileActionTriggerTemplate = Handlebars.compile(TEMPLATE_FILE_ACTION_TRIGGER);
}
- html += '</a>';
- return $(html);
+ return $(this._fileActionTriggerTemplate(params));
},
+
/**
- * Custom renderer for the "Rename" action.
- * Displays the rename action as an icon behind the file name.
+ * Displays the file actions dropdown menu
*
- * @param {OCA.Files.FileAction} actionSpec file action to render
- * @param {boolean} isDefault true if the action is a default action,
- * false otherwise
- * @param {OCAFiles.FileActionContext} context rendering context
+ * @param {string} fileName file name
+ * @param {OCA.Files.FileActionContext} context rendering context
*/
- _renderRenameAction: function(actionSpec, isDefault, context) {
- var $actionEl = this._makeActionLink(actionSpec, context);
- var $container = context.$file.find('a.name span.nametext');
- $actionEl.find('img').attr('alt', t('files', 'Rename'));
- $container.find('.action-rename').remove();
- $container.append($actionEl);
- return $actionEl;
+ _showMenu: function(fileName, context) {
+ var $actionEl = context.$file.find('.action-menu');
+
+ this._menu = new OCA.Files.FileActionsMenu();
+ this._menu.showAt($actionEl, context);
},
+
/**
- * Custom renderer for the "Delete" action.
- * Displays the "Delete" action as a trash icon at the end of
- * the table row.
- *
- * @param {OCA.Files.FileAction} actionSpec file action to render
- * @param {boolean} isDefault true if the action is a default action,
- * false otherwise
- * @param {OCAFiles.FileActionContext} context rendering context
+ * Renders the menu trigger on the given file list row
+ *
+ * @param {Object} $tr file list row element
+ * @param {OCA.Files.FileActionContext} context rendering context
*/
- _renderDeleteAction: function(actionSpec, isDefault, context) {
- var mountType = context.$file.attr('data-mounttype');
- var deleteTitle = t('files', 'Delete');
- if (mountType === 'external-root') {
- deleteTitle = t('files', 'Disconnect storage');
- } else if (mountType === 'shared-root') {
- deleteTitle = t('files', 'Unshare');
- }
- var cssClasses = 'action delete icon-delete';
- if((context.$file.data('permissions') & OC.PERMISSION_DELETE) === 0) {
- // add css class no-permission to delete icon
- cssClasses += ' no-permission';
- deleteTitle = t('files', 'No permission to delete');
- }
- var $actionLink = $('<a href="#" original-title="' +
- escapeHTML(deleteTitle) +
- '" class="' +cssClasses + '">' +
- '<span class="hidden-visually">' + escapeHTML(deleteTitle) + '</span>' +
- '</a>'
- );
- var $container = context.$file.find('td:last');
- $container.find('.delete').remove();
- $container.append($actionLink);
- return $actionLink;
+ _renderMenuTrigger: function($tr, context) {
+ // remove previous
+ $tr.find('.action-menu').remove();
+ $tr.find('.fileactions').append(this._renderInlineAction({
+ name: 'menu',
+ displayName: '',
+ icon: OC.imagePath('core', 'actions/more'),
+ altText: t('files', 'Actions'),
+ action: this._showMenuClosure
+ }, false, context));
},
+
/**
* Renders the action element by calling actionSpec.render() and
* registers the click event to process the action.
@@ -312,21 +364,23 @@
* @param {OCA.Files.FileAction} actionSpec file action to render
* @param {boolean} isDefault true if the action is a default action,
* false otherwise
- * @param {OCAFiles.FileActionContext} context rendering context
+ * @param {OCA.Files.FileActionContext} context rendering context
*/
- _renderAction: function(actionSpec, isDefault, context) {
- var $actionEl = actionSpec.render(actionSpec, isDefault, context);
+ _renderInlineAction: function(actionSpec, isDefault, context) {
+ var renderFunc = actionSpec.render || _.bind(this._defaultRenderAction, this);
+ var $actionEl = renderFunc(actionSpec, isDefault, context);
if (!$actionEl || !$actionEl.length) {
return;
}
- $actionEl.addClass('action action-' + actionSpec.name.toLowerCase());
- $actionEl.attr('data-action', actionSpec.name);
$actionEl.on(
'click', {
a: null
},
function(event) {
var $file = $(event.target).closest('tr');
+ if ($file.hasClass('busy')) {
+ return;
+ }
var currentFile = $file.find('td.filename');
var fileName = $file.attr('data-file');
event.stopPropagation();
@@ -346,6 +400,7 @@
);
return $actionEl;
},
+
/**
* Display file actions for the given element
* @param parent "td" element of the file for which to display actions
@@ -382,30 +437,23 @@
this.getCurrentPermissions()
);
+ var context = {
+ $file: $tr,
+ fileActions: this,
+ fileList: fileList
+ };
+
$.each(actions, function (name, actionSpec) {
- if (name !== 'Share') {
- self._renderAction(
+ if (actionSpec.type === FileActions.TYPE_INLINE) {
+ self._renderInlineAction(
actionSpec,
- actionSpec.action === defaultAction, {
- $file: $tr,
- fileActions: this,
- fileList : fileList
- }
+ actionSpec.action === defaultAction,
+ context
);
}
});
- // added here to make sure it's always the last action
- var shareActionSpec = actions.Share;
- if (shareActionSpec){
- this._renderAction(
- shareActionSpec,
- shareActionSpec.action === defaultAction, {
- $file: $tr,
- fileActions: this,
- fileList: fileList
- }
- );
- }
+
+ this._renderMenuTrigger($tr, context);
if (triggerEvent){
fileList.$fileList.trigger(jQuery.Event("fileActionsReady", {fileList: fileList, $files: $tr}));
@@ -429,35 +477,42 @@
*/
registerDefaultActions: function() {
this.registerAction({
- name: 'Delete',
- displayName: '',
+ name: 'Download',
+ displayName: t('files', 'Download'),
mime: 'all',
- // permission is READ because we show a hint instead if there is no permission
permissions: OC.PERMISSION_READ,
- icon: function() {
- return OC.imagePath('core', 'actions/delete');
+ icon: function () {
+ return OC.imagePath('core', 'actions/download');
},
- render: _.bind(this._renderDeleteAction, this),
- actionHandler: function(fileName, context) {
- // if there is no permission to delete do nothing
- if((context.$file.data('permissions') & OC.PERMISSION_DELETE) === 0) {
+ actionHandler: function (filename, context) {
+ var dir = context.dir || context.fileList.getCurrentDirectory();
+ var url = context.fileList.getDownloadUrl(filename, dir);
+
+ var downloadFileaction = $(context.$file).find('.fileactions .action-download');
+
+ // don't allow a second click on the download action
+ if(downloadFileaction.hasClass('disabled')) {
return;
}
- context.fileList.do_delete(fileName, context.dir);
- $('.tipsy').remove();
+
+ if (url) {
+ var disableLoadingState = function() {
+ context.fileList.showFileBusyState(filename, false);
+ };
+
+ context.fileList.showFileBusyState(downloadFileaction, true);
+ OCA.Files.Files.handleDownload(url, disableLoadingState);
+ }
}
});
- // t('files', 'Rename')
this.registerAction({
name: 'Rename',
- displayName: '',
mime: 'all',
permissions: OC.PERMISSION_UPDATE,
icon: function() {
return OC.imagePath('core', 'actions/rename');
},
- render: _.bind(this._renderRenameAction, this),
actionHandler: function (filename, context) {
context.fileList.rename(filename);
}
@@ -471,30 +526,25 @@
context.fileList.changeDirectory(dir + filename);
});
- this.setDefault('dir', 'Open');
-
- this.register('all', 'Download', OC.PERMISSION_READ, function () {
- return OC.imagePath('core', 'actions/download');
- }, function (filename, context) {
- var dir = context.dir || context.fileList.getCurrentDirectory();
- var url = context.fileList.getDownloadUrl(filename, dir);
-
- var downloadFileaction = $(context.$file).find('.fileactions .action-download');
-
- // don't allow a second click on the download action
- if(downloadFileaction.hasClass('disabled')) {
- return;
+ this.registerAction({
+ name: 'Delete',
+ mime: 'all',
+ // permission is READ because we show a hint instead if there is no permission
+ permissions: OC.PERMISSION_READ,
+ icon: function() {
+ return OC.imagePath('core', 'actions/delete');
+ },
+ actionHandler: function(fileName, context) {
+ // if there is no permission to delete do nothing
+ if((context.$file.data('permissions') & OC.PERMISSION_DELETE) === 0) {
+ return;
+ }
+ context.fileList.do_delete(fileName, context.dir);
+ $('.tipsy').remove();
}
+ });
- if (url) {
- var disableLoadingState = function(){
- OCA.Files.FileActions.updateFileActionSpinner(downloadFileaction, false);
- };
-
- OCA.Files.FileActions.updateFileActionSpinner(downloadFileaction, true);
- OCA.Files.Files.handleDownload(url, disableLoadingState);
- }
- }, t('files', 'Download'));
+ this.setDefault('dir', 'Open');
}
};