From c02ef695217f42dff501e481a50f4669d1cb1f29 Mon Sep 17 00:00:00 2001 From: Vincent Petry Date: Mon, 1 Dec 2014 16:17:28 +0100 Subject: [PATCH] Simple Plugin system for Javascript --- apps/files/js/filelist.js | 6 ++ apps/files_sharing/js/app.js | 3 + apps/files_sharing/js/public.js | 4 + apps/files_sharing/js/share.js | 74 ++++++++--------- apps/files_sharing/js/sharedfilelist.js | 1 + apps/files_sharing/tests/js/shareSpec.js | 2 +- .../tests/js/sharedfilelistSpec.js | 5 +- apps/files_trashbin/js/filelist.js | 1 + core/js/js.js | 81 +++++++++++++++++++ core/js/tests/specHelper.js | 3 + core/js/tests/specs/coreSpec.js | 31 +++++++ 11 files changed, 169 insertions(+), 42 deletions(-) diff --git a/apps/files/js/filelist.js b/apps/files/js/filelist.js index bec0155e90..cd20dc71c2 100644 --- a/apps/files/js/filelist.js +++ b/apps/files/js/filelist.js @@ -166,6 +166,9 @@ } this.$el = $el; + if (options.id) { + this.id = options.id; + } this.$container = options.scrollContainer || $(window); this.$table = $el.find('table:first'); this.$fileList = $el.find('#fileList'); @@ -215,6 +218,8 @@ self.scrollTo(options.scrollTo); }); } + + OC.Plugins.attach('OCA.Files.FileList', this); }, /** @@ -224,6 +229,7 @@ // TODO: also unregister other event handlers this.fileActions.off('registerAction', this._onFileActionsUpdated); this.fileActions.off('setDefault', this._onFileActionsUpdated); + OC.Plugins.detach('OCA.Files.FileList', this); }, /** diff --git a/apps/files_sharing/js/app.js b/apps/files_sharing/js/app.js index 1314304c56..ff6997ab12 100644 --- a/apps/files_sharing/js/app.js +++ b/apps/files_sharing/js/app.js @@ -30,6 +30,7 @@ OCA.Sharing.App = { this._inFileList = new OCA.Sharing.FileList( $el, { + id: 'shares.self', scrollContainer: $('#app-content'), sharedWithUser: true, fileActions: this._createFileActions() @@ -49,6 +50,7 @@ OCA.Sharing.App = { this._outFileList = new OCA.Sharing.FileList( $el, { + id: 'shares.others', scrollContainer: $('#app-content'), sharedWithUser: false, fileActions: this._createFileActions() @@ -68,6 +70,7 @@ OCA.Sharing.App = { this._linkFileList = new OCA.Sharing.FileList( $el, { + id: 'shares.link', scrollContainer: $('#app-content'), linksOnly: true, fileActions: this._createFileActions() diff --git a/apps/files_sharing/js/public.js b/apps/files_sharing/js/public.js index 0627ed6ab5..2ddcd84d4c 100644 --- a/apps/files_sharing/js/public.js +++ b/apps/files_sharing/js/public.js @@ -53,6 +53,7 @@ OCA.Sharing.PublicApp = { this.fileList = new OCA.Files.FileList( $el, { + id: 'files.public', scrollContainer: $(window), dragOptions: dragOptions, folderDropOptions: folderDropOptions, @@ -61,6 +62,9 @@ OCA.Sharing.PublicApp = { ); this.files = OCA.Files.Files; this.files.initialize(); + // TODO: move to PublicFileList.initialize() once + // the code was split into a separate class + OC.Plugins.attach('OCA.Sharing.PublicFileList', this.fileList); } var mimetype = $('#mimetype').val(); diff --git a/apps/files_sharing/js/share.js b/apps/files_sharing/js/share.js index 8474c66d4b..bbd107e070 100644 --- a/apps/files_sharing/js/share.js +++ b/apps/files_sharing/js/share.js @@ -17,46 +17,47 @@ */ OCA.Sharing.Util = { /** - * Initialize the sharing app overrides of the default - * file list. + * Initialize the sharing plugin. * * Registers the "Share" file action and adds additional * DOM attributes for the sharing file info. * - * @param {OCA.Files.FileActions} fileActions file actions to extend + * @param {OCA.Files.FileList} fileList file list to be extended */ - initialize: function(fileActions) { - if (OCA.Files.FileList) { - var oldCreateRow = OCA.Files.FileList.prototype._createRow; - OCA.Files.FileList.prototype._createRow = function(fileData) { - var tr = oldCreateRow.apply(this, arguments); - var sharePermissions = fileData.permissions; - if (fileData.mountType && fileData.mountType === "external-root"){ - // for external storages we cant use the permissions of the mountpoint - // instead we show all permissions and only use the share permissions from the mountpoint to handle resharing - sharePermissions = sharePermissions | (OC.PERMISSION_ALL & ~OC.PERMISSION_SHARE); - } - if (fileData.type === 'file') { - // files can't be shared with delete permissions - sharePermissions = sharePermissions & ~OC.PERMISSION_DELETE; - } - tr.attr('data-share-permissions', sharePermissions); - if (fileData.shareOwner) { - tr.attr('data-share-owner', fileData.shareOwner); - // user should always be able to rename a mount point - if (fileData.isShareMountPoint) { - tr.attr('data-permissions', fileData.permissions | OC.PERMISSION_UPDATE); - } - } - if (fileData.recipientsDisplayName) { - tr.attr('data-share-recipients', fileData.recipientsDisplayName); - } - return tr; - }; + attach: function(fileList) { + if (fileList.id === 'trashbin') { + return; } + var fileActions = fileList.fileActions; + var oldCreateRow = fileList._createRow; + fileList._createRow = function(fileData) { + var tr = oldCreateRow.apply(this, arguments); + var sharePermissions = fileData.permissions; + if (fileData.mountType && fileData.mountType === "external-root"){ + // for external storages we cant use the permissions of the mountpoint + // instead we show all permissions and only use the share permissions from the mountpoint to handle resharing + sharePermissions = sharePermissions | (OC.PERMISSION_ALL & ~OC.PERMISSION_SHARE); + } + if (fileData.type === 'file') { + // files can't be shared with delete permissions + sharePermissions = sharePermissions & ~OC.PERMISSION_DELETE; + } + tr.attr('data-share-permissions', sharePermissions); + if (fileData.shareOwner) { + tr.attr('data-share-owner', fileData.shareOwner); + // user should always be able to rename a mount point + if (fileData.isShareMountPoint) { + tr.attr('data-permissions', fileData.permissions | OC.PERMISSION_UPDATE); + } + } + if (fileData.recipientsDisplayName) { + tr.attr('data-share-recipients', fileData.recipientsDisplayName); + } + return tr; + }; // use delegate to catch the case with multiple file lists - $('#content').delegate('#fileList', 'fileActionsReady', function(ev){ + fileList.$el.on('fileActionsReady', function(ev){ var fileList = ev.fileList; var $files = ev.$files; @@ -198,12 +199,5 @@ }; })(); -$(document).ready(function() { - // FIXME: HACK: do not init when running unit tests, need a better way - if (!window.TESTING) { - if (!_.isUndefined(OC.Share) && !_.isUndefined(OCA.Files)) { - OCA.Sharing.Util.initialize(OCA.Files.fileActions); - } - } -}); +OC.Plugins.register('OCA.Files.FileList', OCA.Sharing.Util); diff --git a/apps/files_sharing/js/sharedfilelist.js b/apps/files_sharing/js/sharedfilelist.js index 7a7c24993c..bd26b13b78 100644 --- a/apps/files_sharing/js/sharedfilelist.js +++ b/apps/files_sharing/js/sharedfilelist.js @@ -55,6 +55,7 @@ if (options && options.linksOnly) { this._linksOnly = true; } + OC.Plugins.attach('OCA.Sharing.FileList', this); }, _renderRow: function() { diff --git a/apps/files_sharing/tests/js/shareSpec.js b/apps/files_sharing/tests/js/shareSpec.js index 2cf5dc47b6..d64d4ea813 100644 --- a/apps/files_sharing/tests/js/shareSpec.js +++ b/apps/files_sharing/tests/js/shareSpec.js @@ -41,12 +41,12 @@ describe('OCA.Sharing.Util tests', function() { $('#content').append($div); var fileActions = new OCA.Files.FileActions(); - OCA.Sharing.Util.initialize(fileActions); fileList = new OCA.Files.FileList( $div, { fileActions : fileActions } ); + OCA.Sharing.Util.attach(fileList); testFiles = [{ id: 1, diff --git a/apps/files_sharing/tests/js/sharedfilelistSpec.js b/apps/files_sharing/tests/js/sharedfilelistSpec.js index dc6931af6e..d85c0ab6dc 100644 --- a/apps/files_sharing/tests/js/sharedfilelistSpec.js +++ b/apps/files_sharing/tests/js/sharedfilelistSpec.js @@ -50,7 +50,6 @@ describe('OCA.Sharing.FileList tests', function() { // the sharing code oldFileListPrototype = _.extend({}, OCA.Files.FileList.prototype); fileActions = new OCA.Files.FileActions(); - OCA.Sharing.Util.initialize(fileActions); }); afterEach(function() { OCA.Files.FileList.prototype = oldFileListPrototype; @@ -72,6 +71,7 @@ describe('OCA.Sharing.FileList tests', function() { sharedWithUser: true } ); + OCA.Sharing.Util.attach(fileList); fileList.reload(); @@ -193,6 +193,7 @@ describe('OCA.Sharing.FileList tests', function() { sharedWithUser: false } ); + OCA.Sharing.Util.attach(fileList); fileList.reload(); @@ -433,6 +434,7 @@ describe('OCA.Sharing.FileList tests', function() { linksOnly: true } ); + OCA.Sharing.Util.attach(fileList); fileList.reload(); @@ -580,6 +582,7 @@ describe('OCA.Sharing.FileList tests', function() { fileActions: fileActions } ); + OCA.Sharing.Util.attach(fileList); }); it('external storage root folder', function () { diff --git a/apps/files_trashbin/js/filelist.js b/apps/files_trashbin/js/filelist.js index a3631a2d0f..04e9da23dc 100644 --- a/apps/files_trashbin/js/filelist.js +++ b/apps/files_trashbin/js/filelist.js @@ -64,6 +64,7 @@ return parts; }; + OC.Plugins.attach('OCA.Trashbin.FileList', this); return result; }, diff --git a/core/js/js.js b/core/js/js.js index eb2f10b51f..cc3a548de2 100644 --- a/core/js/js.js +++ b/core/js/js.js @@ -498,6 +498,87 @@ var OC={ } }; +/** + * @namespace OC.Plugins + */ +OC.Plugins = { + /** + * @type Array. + */ + _plugins: {}, + + /** + * Register plugin + * + * @param {String} targetName app name / class name to hook into + * @param {OC.Plugin} plugin + */ + register: function(targetName, plugin) { + var plugins = this._plugins[targetName]; + if (!plugins) { + plugins = this._plugins[targetName] = []; + } + plugins.push(plugin); + }, + + /** + * Returns all plugin registered to the given target + * name / app name / class name. + * + * @param {String} targetName app name / class name to hook into + * @return {Array.} array of plugins + */ + getPlugins: function(targetName) { + return this._plugins[targetName] || []; + }, + + /** + * Call attach() on all plugins registered to the given target name. + * + * @param {String} targetName app name / class name + * @param {Object} object to be extended + * @param {Object} [options] options + */ + attach: function(targetName, targetObject, options) { + var plugins = this.getPlugins(targetName); + for (var i = 0; i < plugins.length; i++) { + if (plugins[i].attach) { + plugins[i].attach(targetObject, options); + } + } + }, + + /** + * Call detach() on all plugins registered to the given target name. + * + * @param {String} targetName app name / class name + * @param {Object} object to be extended + * @param {Object} [options] options + */ + detach: function(targetName, targetObject, options) { + var plugins = this.getPlugins(targetName); + for (var i = 0; i < plugins.length; i++) { + if (plugins[i].detach) { + plugins[i].detach(targetObject, options); + } + } + }, + + /** + * Plugin + * + * @todo make this a real class in the future + * @typedef {Object} OC.Plugin + * + * @property {String} name plugin name + * @property {Function} attach function that will be called when the + * plugin is attached + * @property {Function} [detach] function that will be called when the + * plugin is detached + */ + +}; + /** * @namespace OC.search */ diff --git a/core/js/tests/specHelper.js b/core/js/tests/specHelper.js index 4111b6763d..59c2a99645 100644 --- a/core/js/tests/specHelper.js +++ b/core/js/tests/specHelper.js @@ -120,6 +120,9 @@ window.isPhantom = /phantom/i.test(navigator.userAgent); if (!OC.TestUtil) { OC.TestUtil = TestUtil; } + + // reset plugins + OC.Plugins._plugins = []; }); afterEach(function() { diff --git a/core/js/tests/specs/coreSpec.js b/core/js/tests/specs/coreSpec.js index 2c5c22905b..08395f4d4c 100644 --- a/core/js/tests/specs/coreSpec.js +++ b/core/js/tests/specs/coreSpec.js @@ -655,5 +655,36 @@ describe('Core base tests', function() { ]); }); }); + describe('Plugins', function() { + var plugin; + + beforeEach(function() { + plugin = { + name: 'Some name', + attach: function(obj) { + obj.attached = true; + }, + + detach: function(obj) { + obj.attached = false; + } + }; + OC.Plugins.register('OC.Test.SomeName', plugin); + }); + it('attach plugin to object', function() { + var obj = {something: true}; + OC.Plugins.attach('OC.Test.SomeName', obj); + expect(obj.attached).toEqual(true); + OC.Plugins.detach('OC.Test.SomeName', obj); + expect(obj.attached).toEqual(false); + }); + it('only call handler for target name', function() { + var obj = {something: true}; + OC.Plugins.attach('OC.Test.SomeOtherName', obj); + expect(obj.attached).not.toBeDefined(); + OC.Plugins.detach('OC.Test.SomeOtherName', obj); + expect(obj.attached).not.toBeDefined(); + }); + }); });