From c02ef695217f42dff501e481a50f4669d1cb1f29 Mon Sep 17 00:00:00 2001
From: Vincent Petry <pvince81@owncloud.com>
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            | Bin 7462 -> 7515 bytes
 core/js/js.js                                 |  81 ++++++++++++++++++
 core/js/tests/specHelper.js                   |   3 +
 core/js/tests/specs/coreSpec.js               |  31 +++++++
 11 files changed, 168 insertions(+), 42 deletions(-)

diff --git a/apps/files/js/filelist.js b/apps/files/js/filelist.js
index bec0155e90e..cd20dc71c29 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 1314304c567..ff6997ab12f 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 0627ed6ab54..2ddcd84d4c1 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 8474c66d4b8..bbd107e070e 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 7a7c24993c0..bd26b13b78a 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 2cf5dc47b63..d64d4ea8137 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 dc6931af6e8..d85c0ab6dca 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 a3631a2d0fe740d1c87cb75c2887c3af010fccae..04e9da23dc1ed77f489e296569371a6da5edef55 100644
GIT binary patch
delta 62
zcmZ2xb=zu#A-kc!vtB?>X?kW}v0h?HNn&z_hPuDAqh3f+VsS=NW}co~W=^V4W^swS
RjzURBX0fLAW?lB5q5!n`6|DdO

delta 12
Tcmca@wajXRA^YYu_OGG<BY*`6

diff --git a/core/js/js.js b/core/js/js.js
index eb2f10b51f0..cc3a548de28 100644
--- a/core/js/js.js
+++ b/core/js/js.js
@@ -498,6 +498,87 @@ var OC={
 	}
 };
 
+/**
+ * @namespace OC.Plugins
+ */
+OC.Plugins = {
+	/**
+	 * @type Array.<OC.Plugin>
+	 */
+	_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.<OC.Plugin>} 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 4111b6763d9..59c2a99645f 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 2c5c22905b0..08395f4d4c2 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();
+		});
+	});
 });
 
-- 
GitLab