diff --git a/plugins/CoreHome/CoreHome.php b/plugins/CoreHome/CoreHome.php
index 3b7c804a7a5d69a272f23e69b291a15966176bd0..187f356c5e5e9928cc511e1d31a27e6df96cc473 100644
--- a/plugins/CoreHome/CoreHome.php
+++ b/plugins/CoreHome/CoreHome.php
@@ -66,8 +66,8 @@ class CoreHome extends \Piwik\Plugin
         $stylesheets[] = "plugins/CoreHome/stylesheets/sparklineColors.less";
         $stylesheets[] = "plugins/CoreHome/stylesheets/notification.less";
         $stylesheets[] = "plugins/CoreHome/stylesheets/zen-mode.less";
-        $stylesheets[] = "plugins/CoreHome/angularjs/enrichedheadline/enrichedheadline.less";
-        $stylesheets[] = "plugins/CoreHome/angularjs/menudropdown/menudropdown.less";
+        $stylesheets[] = "plugins/CoreHome/angularjs/enrichedheadline/enrichedheadline.directive.less";
+        $stylesheets[] = "plugins/CoreHome/angularjs/menudropdown/menudropdown.directive.less";
         $stylesheets[] = "plugins/CoreHome/angularjs/dialogtoggler/ngdialog.less";
     }
 
@@ -111,13 +111,13 @@ class CoreHome extends \Piwik\Plugin
         $jsFiles[] = "plugins/CoreHome/javascripts/notification.js";
         $jsFiles[] = "plugins/CoreHome/javascripts/notification_parser.js";
 
-        $jsFiles[] = "plugins/CoreHome/angularjs/piwikAppConfig.js";
+        $jsFiles[] = "plugins/CoreHome/angularjs/piwikApp.config.js";
 
-        $jsFiles[] = "plugins/CoreHome/angularjs/common/services/service.js";
+        $jsFiles[] = "plugins/CoreHome/angularjs/common/services/service.module.js";
         $jsFiles[] = "plugins/CoreHome/angularjs/common/services/piwik.js";
         $jsFiles[] = "plugins/CoreHome/angularjs/common/services/piwik-api.js";
 
-        $jsFiles[] = "plugins/CoreHome/angularjs/common/filters/filter.js";
+        $jsFiles[] = "plugins/CoreHome/angularjs/common/filters/filter.module.js";
         $jsFiles[] = "plugins/CoreHome/angularjs/common/filters/translate.js";
         $jsFiles[] = "plugins/CoreHome/angularjs/common/filters/startfrom.js";
         $jsFiles[] = "plugins/CoreHome/angularjs/common/filters/evolution.js";
@@ -125,7 +125,7 @@ class CoreHome extends \Piwik\Plugin
         $jsFiles[] = "plugins/CoreHome/angularjs/common/filters/trim.js";
         $jsFiles[] = "plugins/CoreHome/angularjs/common/filters/pretty-url.js";
 
-        $jsFiles[] = "plugins/CoreHome/angularjs/common/directives/directive.js";
+        $jsFiles[] = "plugins/CoreHome/angularjs/common/directives/directive.module.js";
         $jsFiles[] = "plugins/CoreHome/angularjs/common/directives/autocomplete-matched.js";
         $jsFiles[] = "plugins/CoreHome/angularjs/common/directives/focus-anywhere-but-here.js";
         $jsFiles[] = "plugins/CoreHome/angularjs/common/directives/ignore-click.js";
@@ -138,17 +138,17 @@ class CoreHome extends \Piwik\Plugin
         $jsFiles[] = "plugins/CoreHome/angularjs/anchorLinkFix.js";
         $jsFiles[] = "plugins/CoreHome/angularjs/http404check.js";
 
-        $jsFiles[] = "plugins/CoreHome/angularjs/siteselector/siteselector-model.js";
-        $jsFiles[] = "plugins/CoreHome/angularjs/siteselector/siteselector-controller.js";
-        $jsFiles[] = "plugins/CoreHome/angularjs/siteselector/siteselector-directive.js";
+        $jsFiles[] = "plugins/CoreHome/angularjs/siteselector/siteselector-model.service.js";
+        $jsFiles[] = "plugins/CoreHome/angularjs/siteselector/siteselector.controller.js";
+        $jsFiles[] = "plugins/CoreHome/angularjs/siteselector/siteselector.directive.js";
 
-        $jsFiles[] = "plugins/CoreHome/angularjs/menudropdown/menudropdown-directive.js";
+        $jsFiles[] = "plugins/CoreHome/angularjs/menudropdown/menudropdown.directive.js";
 
-        $jsFiles[] = "plugins/CoreHome/angularjs/enrichedheadline/enrichedheadline-directive.js";
+        $jsFiles[] = "plugins/CoreHome/angularjs/enrichedheadline/enrichedheadline.directive.js";
 
-        $jsFiles[] = "plugins/CoreHome/angularjs/dialogtoggler/dialogtoggler-directive.js";
-        $jsFiles[] = "plugins/CoreHome/angularjs/dialogtoggler/dialogtoggler-controller.js";
-        $jsFiles[] = "plugins/CoreHome/angularjs/dialogtoggler/dialogtoggler-urllistener-service.js";
+        $jsFiles[] = "plugins/CoreHome/angularjs/dialogtoggler/dialogtoggler.directive.js";
+        $jsFiles[] = "plugins/CoreHome/angularjs/dialogtoggler/dialogtoggler.controller.js";
+        $jsFiles[] = "plugins/CoreHome/angularjs/dialogtoggler/dialogtoggler-urllistener.service.js";
     }
 
     public function getClientSideTranslationKeys(&$translationKeys)
diff --git a/plugins/CoreHome/angularjs/anchorLinkFix.js b/plugins/CoreHome/angularjs/anchorLinkFix.js
index 59d8114a568272eb401bf5044d4edc489cd190b8..ec07049ef7ae4228e3a281ec215d1de244e2f016 100644
--- a/plugins/CoreHome/angularjs/anchorLinkFix.js
+++ b/plugins/CoreHome/angularjs/anchorLinkFix.js
@@ -80,7 +80,9 @@
     {
         angular.module('piwikApp').run(['$rootScope', function ($rootScope) {
 
-            $rootScope.$on('$locationChangeStart', function (event, newUrl, oldUrl, $location) {
+            $rootScope.$on('$locationChangeStart', onLocationChangeStart);
+
+            function onLocationChangeStart (event, newUrl, oldUrl, $location) {
 
                 if (!newUrl) {
                     return;
@@ -98,7 +100,7 @@
                 var hash = newUrl.substr(hashPos + 2);
 
                 scrollToAnchorIfPossible(hash, event);
-            });
+            }
         }]);
     }
 
diff --git a/plugins/CoreHome/angularjs/common/directives/autocomplete-matched.js b/plugins/CoreHome/angularjs/common/directives/autocomplete-matched.js
index 0a0c32be0bd4a9e42d06dbf4a81d6d12d92b4a5a..5594f4194d49798abf559071175fda709d210b0c 100644
--- a/plugins/CoreHome/angularjs/common/directives/autocomplete-matched.js
+++ b/plugins/CoreHome/angularjs/common/directives/autocomplete-matched.js
@@ -15,28 +15,32 @@
  * <div piwik-autocomplete-matched="searchTerm">{{ name }}</div>
  * <input type="text" ng-model="searchTerm">
  */
-angular.module('piwikApp.directive').directive('piwikAutocompleteMatched', function() {
-    return function(scope, element, attrs) {
-        var searchTerm;
+(function () {
+    angular.module('piwikApp.directive').directive('piwikAutocompleteMatched', piwikAutocompleteMatched);
 
-        scope.$watch(attrs.piwikAutocompleteMatched, function(value) {
-            searchTerm = value;
-            updateText();
-        });
+    function piwikAutocompleteMatched() {
+        return function(scope, element, attrs) {
+            var searchTerm;
 
-        function updateText () {
-            if (!searchTerm || !element) {
-                return;
-            }
+            scope.$watch(attrs.piwikAutocompleteMatched, function(value) {
+                searchTerm = value;
+                updateText();
+            });
+
+            function updateText () {
+                if (!searchTerm || !element) {
+                    return;
+                }
 
-            var content   = element.html();
-            var startTerm = content.toLowerCase().indexOf(searchTerm.toLowerCase());
+                var content   = element.html();
+                var startTerm = content.toLowerCase().indexOf(searchTerm.toLowerCase());
 
-            if (-1 !== startTerm) {
-                var word = content.substr(startTerm, searchTerm.length);
-                content = content.replace(word, '<span class="autocompleteMatched">' + word + '</span>');
-                element.html(content);
+                if (-1 !== startTerm) {
+                    var word = content.substr(startTerm, searchTerm.length);
+                    content = content.replace(word, '<span class="autocompleteMatched">' + word + '</span>');
+                    element.html(content);
+                }
             }
-        }
-    };
-});
\ No newline at end of file
+        };
+    }
+})();
\ No newline at end of file
diff --git a/plugins/CoreHome/angularjs/common/directives/autocomplete-matched.spec.js b/plugins/CoreHome/angularjs/common/directives/autocomplete-matched.spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..09ef14b21e6f3125adcfc4f2b578c246eb3628ec
--- /dev/null
+++ b/plugins/CoreHome/angularjs/common/directives/autocomplete-matched.spec.js
@@ -0,0 +1,44 @@
+/*!
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+(function () {
+    describe('piwikAutocompleteMatchedDirective', function() {
+        var $compile;
+        var $rootScope;
+
+        beforeEach(module('piwikApp.directive'));
+        beforeEach(inject(function(_$compile_, _$rootScope_){
+            $compile = _$compile_;
+            $rootScope = _$rootScope_;
+        }));
+
+        function assertRenderedContentIs(query, expectedResult) {
+            var template = '<div piwik-autocomplete-matched="\'' + query + '\'">My Content</div>';
+            var element  = $compile(template)($rootScope);
+            $rootScope.$digest();
+            expect(element.html()).to.eql(expectedResult);
+        }
+
+        describe('#piwikAutocompleteMatched()', function() {
+
+            it('should not change anything if query does not match the text', function() {
+                assertRenderedContentIs('Whatever', 'My Content');
+            });
+
+            it('should wrap the matching part and find case insensitive', function() {
+                assertRenderedContentIs('y cont', 'M<span class="autocompleteMatched">y Cont</span>ent');
+            });
+
+            it('should be able to wrap the whole content', function() {
+                assertRenderedContentIs('my content', '<span class="autocompleteMatched">My Content</span>');
+            });
+
+            it('should find matching content case sensitive', function() {
+                assertRenderedContentIs('My Co', '<span class="autocompleteMatched">My Co</span>ntent');
+            });
+        });
+    });
+})();
\ No newline at end of file
diff --git a/plugins/CoreHome/angularjs/common/directives/autocomplete-matched_spec.js b/plugins/CoreHome/angularjs/common/directives/autocomplete-matched_spec.js
deleted file mode 100644
index 0fcc9191dbf0ca6b0d5ec73ef03b11246d8e268e..0000000000000000000000000000000000000000
--- a/plugins/CoreHome/angularjs/common/directives/autocomplete-matched_spec.js
+++ /dev/null
@@ -1,43 +0,0 @@
-/*!
- * Piwik - free/libre analytics platform
- *
- * @link http://piwik.org
- * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
- */
-
-describe('piwikAutocompleteMatchedDirective', function() {
-    var $compile;
-    var $rootScope;
-
-    beforeEach(module('piwikApp.directive'));
-    beforeEach(inject(function(_$compile_, _$rootScope_){
-        $compile = _$compile_;
-        $rootScope = _$rootScope_;
-    }));
-
-    function assertRenderedContentIs(query, expectedResult) {
-        var template = '<div piwik-autocomplete-matched="\'' + query + '\'">My Content</div>';
-        var element  = $compile(template)($rootScope);
-        $rootScope.$digest();
-        expect(element.html()).to.eql(expectedResult);
-    }
-
-    describe('#piwikAutocompleteMatched()', function() {
-
-        it('should not change anything if query does not match the text', function() {
-            assertRenderedContentIs('Whatever', 'My Content');
-        });
-
-        it('should wrap the matching part and find case insensitive', function() {
-            assertRenderedContentIs('y cont', 'M<span class="autocompleteMatched">y Cont</span>ent');
-        });
-
-        it('should be able to wrap the whole content', function() {
-            assertRenderedContentIs('my content', '<span class="autocompleteMatched">My Content</span>');
-        });
-
-        it('should find matching content case sensitive', function() {
-            assertRenderedContentIs('My Co', '<span class="autocompleteMatched">My Co</span>ntent');
-        });
-    });
-});
\ No newline at end of file
diff --git a/plugins/CoreHome/angularjs/common/directives/dialog.js b/plugins/CoreHome/angularjs/common/directives/dialog.js
index 71157aeb712395d0ef05ba59538ca457196ebbc5..e711d3bc29db4b5b0db7689d1ecf3627fab58368 100644
--- a/plugins/CoreHome/angularjs/common/directives/dialog.js
+++ b/plugins/CoreHome/angularjs/common/directives/dialog.js
@@ -15,27 +15,33 @@
  * </div>
  * Will execute the "executeMyFunction" function in the current scope once the yes button is pressed.
  */
-angular.module('piwikApp.directive').directive('piwikDialog', function(piwik, $parse) {
+(function () {
+    angular.module('piwikApp.directive').directive('piwikDialog', piwikDialog);
 
-    return {
-        restrict: 'A',
-        link: function(scope, element, attrs) {
+    piwikDialog.$inject = ['piwik', '$parse'];
 
-            element.css('display', 'none');
+    function piwikDialog(piwik, $parse) {
 
-            element.on( "dialogclose", function() {
-                scope.$apply($parse(attrs.piwikDialog).assign(scope, false));
-            });
+        return {
+            restrict: 'A',
+            link: function(scope, element, attrs) {
 
-            scope.$watch(attrs.piwikDialog, function(newValue, oldValue) {
-                if (newValue) {
-                    piwik.helper.modalConfirm(element, {yes: function() {
-                        if (attrs.yes) {
-                            scope.$eval(attrs.yes);
-                        }
-                    }});
-                }
-            });
-        }
-    };
-});
\ No newline at end of file
+                element.css('display', 'none');
+
+                element.on( "dialogclose", function() {
+                    scope.$apply($parse(attrs.piwikDialog).assign(scope, false));
+                });
+
+                scope.$watch(attrs.piwikDialog, function(newValue, oldValue) {
+                    if (newValue) {
+                        piwik.helper.modalConfirm(element, {yes: function() {
+                            if (attrs.yes) {
+                                scope.$eval(attrs.yes);
+                            }
+                        }});
+                    }
+                });
+            }
+        };
+    }
+})();
\ No newline at end of file
diff --git a/plugins/CoreHome/angularjs/common/filters/filter.js b/plugins/CoreHome/angularjs/common/directives/directive.module.js
similarity index 68%
rename from plugins/CoreHome/angularjs/common/filters/filter.js
rename to plugins/CoreHome/angularjs/common/directives/directive.module.js
index 1a79c4008baab7ba48eb6bcad4d2e5f48593ec41..5106f5be148d8cd985bbd9605e56c82aeee60d13 100644
--- a/plugins/CoreHome/angularjs/common/filters/filter.js
+++ b/plugins/CoreHome/angularjs/common/directives/directive.module.js
@@ -4,4 +4,6 @@
  * @link http://piwik.org
  * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
  */
-angular.module('piwikApp.filter', []);
+(function () {
+    angular.module('piwikApp.directive', []);
+})();
diff --git a/plugins/CoreHome/angularjs/common/directives/focus-anywhere-but-here.js b/plugins/CoreHome/angularjs/common/directives/focus-anywhere-but-here.js
index f30c765b7d90bd608ff1b76429fb81e1d9c89aa4..0617c5df88d2fe74fa6aa4f229c003d1c2179e06 100644
--- a/plugins/CoreHome/angularjs/common/directives/focus-anywhere-but-here.js
+++ b/plugins/CoreHome/angularjs/common/directives/focus-anywhere-but-here.js
@@ -12,29 +12,35 @@
  * Example:
  * <div piwik-focus-anywhere-but-here="closeDialog()">my dialog</div>
  */
-angular.module('piwikApp.directive').directive('piwikFocusAnywhereButHere', function($document){
-    return {
-        restrict: 'A',
-        link: function(scope, element, attr, ctrl) {
+(function () {
+    angular.module('piwikApp.directive').directive('piwikFocusAnywhereButHere', piwikFocusAnywhereButHere);
 
-            function onClickOutsideElement (event) {
-                if (element.has(event.target).length === 0) {
-                    scope.$apply(attr.piwikFocusAnywhereButHere);
+    piwikFocusAnywhereButHere.$inject = ['$document'];
+
+    function piwikFocusAnywhereButHere($document){
+        return {
+            restrict: 'A',
+            link: function(scope, element, attr, ctrl) {
+
+                function onClickOutsideElement (event) {
+                    if (element.has(event.target).length === 0) {
+                        scope.$apply(attr.piwikFocusAnywhereButHere);
+                    }
                 }
-            }
 
-            function onEscapeHandler (event) {
-                if (event.which === 27) {
-                    scope.$apply(attr.piwikFocusAnywhereButHere);
+                function onEscapeHandler (event) {
+                    if (event.which === 27) {
+                        scope.$apply(attr.piwikFocusAnywhereButHere);
+                    }
                 }
-            }
 
-            $document.on('keyup', onEscapeHandler);
-            $document.on('mouseup', onClickOutsideElement);
-            scope.$on('$destroy', function() {
-                $document.off('mouseup', onClickOutsideElement);
-                $document.off('keyup', onEscapeHandler);
-            });
-        }
-    };
-});
+                $document.on('keyup', onEscapeHandler);
+                $document.on('mouseup', onClickOutsideElement);
+                scope.$on('$destroy', function() {
+                    $document.off('mouseup', onClickOutsideElement);
+                    $document.off('keyup', onEscapeHandler);
+                });
+            }
+        };
+    }
+})();
diff --git a/plugins/CoreHome/angularjs/common/directives/focusif.js b/plugins/CoreHome/angularjs/common/directives/focusif.js
index 256501653649496639aa7ca3012548c9d75ee154..56940808b131325ef06d2a0622b43353f709d709 100644
--- a/plugins/CoreHome/angularjs/common/directives/focusif.js
+++ b/plugins/CoreHome/angularjs/common/directives/focusif.js
@@ -11,17 +11,23 @@
  * Example:
  * <input type="text" piwik-focus-if="view.editName">
  */
-angular.module('piwikApp.directive').directive('piwikFocusIf', function($timeout) {
-    return {
-        restrict: 'A',
-        link: function(scope, element, attrs) {
-            scope.$watch(attrs.piwikFocusIf, function(newValue, oldValue) {
-                if (newValue) {
-                    $timeout(function () {
-                        element[0].focus();
-                    }, 5);
-                }
-            });
-        }
-    };
-});
\ No newline at end of file
+(function () {
+    angular.module('piwikApp.directive').directive('piwikFocusIf', piwikFocusIf);
+
+    piwikFocusIf.$inject = ['$timeout'];
+
+    function piwikFocusIf($timeout) {
+        return {
+            restrict: 'A',
+            link: function(scope, element, attrs) {
+                scope.$watch(attrs.piwikFocusIf, function(newValue, oldValue) {
+                    if (newValue) {
+                        $timeout(function () {
+                            element[0].focus();
+                        }, 5);
+                    }
+                });
+            }
+        };
+    }
+})();
\ No newline at end of file
diff --git a/plugins/CoreHome/angularjs/common/directives/ignore-click.js b/plugins/CoreHome/angularjs/common/directives/ignore-click.js
index e43cae45cbe70c0af051dd134ecfc6fbcb6256fa..d90518bdce2cdbc05ab27c3ad60412794b43084b 100644
--- a/plugins/CoreHome/angularjs/common/directives/ignore-click.js
+++ b/plugins/CoreHome/angularjs/common/directives/ignore-click.js
@@ -12,10 +12,14 @@
  * Example
  * <a piwik-ignore-click ng-click="doSomething()" href="/">my link</a>
  */
-angular.module('piwikApp.directive').directive('piwikIgnoreClick', function() {
-    return function(scope, element, attrs) {
-        $(element).click(function(event) {
-            event.preventDefault();
-        });
-    };
-});
\ No newline at end of file
+(function () {
+    angular.module('piwikApp.directive').directive('piwikIgnoreClick', piwikIgnoreClick);
+
+    function piwikIgnoreClick() {
+        return function(scope, element, attrs) {
+            $(element).click(function(event) {
+                event.preventDefault();
+            });
+        };
+    }
+})();
\ No newline at end of file
diff --git a/plugins/CoreHome/angularjs/common/directives/onenter.js b/plugins/CoreHome/angularjs/common/directives/onenter.js
index 5dc2daa3a88505682ca9f4fc0c1dd6c4f7501d21..d8d39d21b84f7d2551469a7d6169066f11953e85 100644
--- a/plugins/CoreHome/angularjs/common/directives/onenter.js
+++ b/plugins/CoreHome/angularjs/common/directives/onenter.js
@@ -12,16 +12,20 @@
  * <div piwik-onenter="save()">
  * <div piwik-onenter="showList=false">
  */
-angular.module('piwikApp.directive').directive('piwikOnenter', function() {
-    return function(scope, element, attrs) {
-        element.bind("keydown keypress", function(event) {
-            if(event.which === 13) {
-                scope.$apply(function(){
-                    scope.$eval(attrs.piwikOnenter, {'event': event});
-                });
+(function () {
+    angular.module('piwikApp.directive').directive('piwikOnenter', piwikOnenter);
 
-                event.preventDefault();
-            }
-        });
-    };
-});
\ No newline at end of file
+    function piwikOnenter() {
+        return function(scope, element, attrs) {
+            element.bind("keydown keypress", function(event) {
+                if(event.which === 13) {
+                    scope.$apply(function(){
+                        scope.$eval(attrs.piwikOnenter, {'event': event});
+                    });
+
+                    event.preventDefault();
+                }
+            });
+        };
+    }
+})();
\ No newline at end of file
diff --git a/plugins/CoreHome/angularjs/common/directives/translate.js b/plugins/CoreHome/angularjs/common/directives/translate.js
index 17e0638476d56300838e087f794fa765b803aea0..4cc20e83b3d88da0465320c35b872eb1b7d962e8 100644
--- a/plugins/CoreHome/angularjs/common/directives/translate.js
+++ b/plugins/CoreHome/angularjs/common/directives/translate.js
@@ -17,16 +17,20 @@
  *     first arg::<strong>second arg</strong>::{{ unsafeDataThatWillBeSanitized }}
  * </span>
  */
-angular.module('piwikApp.directive').directive('piwikTranslate', function() {
-    return {
-        restrict: 'A',
-        scope: {
-            piwikTranslate: '@'
-        },
-        compile: function(element, attrs) {
-          var parts = element.html().split('::'),
-                translated = _pk_translate(attrs.piwikTranslate, parts);
-            element.html(translated);
-        }
-    };
-});
+(function () {
+    angular.module('piwikApp.directive').directive('piwikTranslate', piwikTranslate);
+
+    function piwikTranslate() {
+        return {
+            restrict: 'A',
+            scope: {
+                piwikTranslate: '@'
+            },
+            compile: function(element, attrs) {
+                var parts = element.html().split('::'),
+                    translated = _pk_translate(attrs.piwikTranslate, parts);
+                element.html(translated);
+            }
+        };
+    }
+})();
diff --git a/plugins/CoreHome/angularjs/common/filters/evolution.js b/plugins/CoreHome/angularjs/common/filters/evolution.js
index ae9e279f4e9d01ba2e6aa8b1b2b2d74e0d6405e6..4ff785c5d898ede6179e627225955a42bf153b84 100644
--- a/plugins/CoreHome/angularjs/common/filters/evolution.js
+++ b/plugins/CoreHome/angularjs/common/filters/evolution.js
@@ -4,41 +4,44 @@
  * @link http://piwik.org
  * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
  */
+(function () {
+    angular.module('piwikApp.filter').filter('evolution', evolution);
 
-angular.module('piwikApp.filter').filter('evolution', function() {
+    function evolution() {
 
-    function calculateEvolution(currentValue, pastValue)
-    {
-        pastValue    = parseInt(pastValue, 10);
-        currentValue = parseInt(currentValue, 10) - pastValue;
+        function calculateEvolution(currentValue, pastValue)
+        {
+            pastValue    = parseInt(pastValue, 10);
+            currentValue = parseInt(currentValue, 10) - pastValue;
 
-        if (currentValue === 0 || isNaN(currentValue)) {
-            evolution = 0;
-        } else if (pastValue === 0 || isNaN(pastValue)) {
-            evolution = 100;
-        } else {
-            evolution = (currentValue / pastValue) * 100;
+            if (currentValue === 0 || isNaN(currentValue)) {
+                evolution = 0;
+            } else if (pastValue === 0 || isNaN(pastValue)) {
+                evolution = 100;
+            } else {
+                evolution = (currentValue / pastValue) * 100;
+            }
+
+            return evolution;
         }
 
-        return evolution;
-    }
+        function formatEvolution(evolution)
+        {
+            evolution = Math.round(evolution);
+
+            if (evolution > 0) {
+                evolution = '+' + evolution;
+            }
 
-    function formatEvolution(evolution)
-    {
-        evolution = Math.round(evolution);
+            evolution += '%';
 
-        if (evolution > 0) {
-            evolution = '+' + evolution;
+            return evolution;
         }
 
-        evolution += '%';
+        return function(currentValue, pastValue) {
+            var evolution = calculateEvolution(currentValue, pastValue);
 
-        return evolution;
+            return formatEvolution(evolution);
+        };
     }
-
-    return function(currentValue, pastValue) {
-        var evolution = calculateEvolution(currentValue, pastValue);
-
-        return formatEvolution(evolution);
-    };
-});
+})();
diff --git a/plugins/CoreHome/angularjs/common/services/service.js b/plugins/CoreHome/angularjs/common/filters/filter.module.js
similarity index 69%
rename from plugins/CoreHome/angularjs/common/services/service.js
rename to plugins/CoreHome/angularjs/common/filters/filter.module.js
index 0467b405558da282247e7f24492342fce0a9b696..cdb39ef78fb2f3a0349e9ed205e0ff11bdce2158 100644
--- a/plugins/CoreHome/angularjs/common/services/service.js
+++ b/plugins/CoreHome/angularjs/common/filters/filter.module.js
@@ -4,5 +4,6 @@
  * @link http://piwik.org
  * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
  */
-
-angular.module('piwikApp.service', []);
+(function () {
+    angular.module('piwikApp.filter', []);
+})();
diff --git a/plugins/CoreHome/angularjs/common/filters/length.js b/plugins/CoreHome/angularjs/common/filters/length.js
index b1625aa82611870cb29e42fe4807cfa69b525ec7..a648347c49922ba02b86f2a01e1f919e1e00e22a 100644
--- a/plugins/CoreHome/angularjs/common/filters/length.js
+++ b/plugins/CoreHome/angularjs/common/filters/length.js
@@ -4,14 +4,18 @@
  * @link http://piwik.org
  * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
  */
+(function () {
+    angular.module('piwikApp.filter').filter('length', length);
 
-angular.module('piwikApp.filter').filter('length', function() {
+    function length() {
 
-    return function(stringOrArray) {
-        if (stringOrArray && stringOrArray.length) {
-            return stringOrArray.length;
-        }
+        return function(stringOrArray) {
+            if (stringOrArray && stringOrArray.length) {
+                return stringOrArray.length;
+            }
 
-        return 0;
-    };
-});
+            return 0;
+        };
+    }
+
+})();
\ No newline at end of file
diff --git a/plugins/CoreHome/angularjs/common/filters/pretty-url.js b/plugins/CoreHome/angularjs/common/filters/pretty-url.js
index f46a64bfaf2fbdf50173d185e6ee347301660228..e3b67c877582502c1e043323db186a253d7138ea 100644
--- a/plugins/CoreHome/angularjs/common/filters/pretty-url.js
+++ b/plugins/CoreHome/angularjs/common/filters/pretty-url.js
@@ -4,9 +4,13 @@
  * @link http://piwik.org
  * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
  */
+(function () {
+    angular.module('piwikApp.filter').filter('prettyUrl', prettyUrl);
 
-angular.module('piwikApp.filter').filter('prettyUrl', function() {
-    return function(input) {
-        return input.trim().replace('http://', '');
-    };
-});
+    function prettyUrl() {
+        return function(input) {
+            return input.trim().replace('http://', '');
+        };
+    }
+
+})();
\ No newline at end of file
diff --git a/plugins/CoreHome/angularjs/common/filters/startfrom.js b/plugins/CoreHome/angularjs/common/filters/startfrom.js
index fa548251da9d43ac131ed09bed42edc70008e110..d912e2064f6c11c0f1b08ee9aa05a4d7f8ca8809 100644
--- a/plugins/CoreHome/angularjs/common/filters/startfrom.js
+++ b/plugins/CoreHome/angularjs/common/filters/startfrom.js
@@ -4,10 +4,13 @@
  * @link http://piwik.org
  * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
  */
+(function () {
+    angular.module('piwikApp.filter').filter('startFrom', startFrom);
 
-angular.module('piwikApp.filter').filter('startFrom', function() {
-    return function(input, start) {
-        start = +start; //parse to int
-        return input.slice(start);
-    };
-});
\ No newline at end of file
+    function startFrom() {
+        return function(input, start) {
+            start = +start; //parse to int
+            return input.slice(start);
+        };
+    }
+})();
\ No newline at end of file
diff --git a/plugins/CoreHome/angularjs/common/filters/startfrom.spec.js b/plugins/CoreHome/angularjs/common/filters/startfrom.spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..e86e48a6184d34935709245da1a5a4bf9a44ccba
--- /dev/null
+++ b/plugins/CoreHome/angularjs/common/filters/startfrom.spec.js
@@ -0,0 +1,41 @@
+/*!
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+(function () {
+    describe('startFromFilter', function() {
+        var startFrom;
+
+        beforeEach(module('piwikApp.filter'));
+        beforeEach(inject(function($injector) {
+            var $filter = $injector.get('$filter');
+            startFrom = $filter('startFrom');
+        }));
+
+        describe('#startFrom()', function() {
+
+            it('should return all entries if index is zero', function() {
+
+                var result = startFrom([1,2,3], 0);
+
+                expect(result).to.eql([1,2,3]);
+            });
+
+            it('should return only partial entries if filter is higher than zero', function() {
+
+                var result = startFrom([1,2,3], 2);
+
+                expect(result).to.eql([3]);
+            });
+
+            it('should return no entries if start is higher than input length', function() {
+
+                var result = startFrom([1,2,3], 11);
+
+                expect(result).to.eql([]);
+            });
+        });
+    });
+})();
\ No newline at end of file
diff --git a/plugins/CoreHome/angularjs/common/filters/startfrom_spec.js b/plugins/CoreHome/angularjs/common/filters/startfrom_spec.js
deleted file mode 100644
index 802dab7f7225bad6a50afcef52a2526efa004340..0000000000000000000000000000000000000000
--- a/plugins/CoreHome/angularjs/common/filters/startfrom_spec.js
+++ /dev/null
@@ -1,40 +0,0 @@
-/*!
- * Piwik - free/libre analytics platform
- *
- * @link http://piwik.org
- * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
- */
-
-describe('startFromFilter', function() {
-    var startFrom;
-
-    beforeEach(module('piwikApp.filter'));
-    beforeEach(inject(function($injector) {
-        var $filter = $injector.get('$filter');
-        startFrom = $filter('startFrom');
-    }));
-
-    describe('#startFrom()', function() {
-
-        it('should return all entries if index is zero', function() {
-
-            var result = startFrom([1,2,3], 0);
-
-            expect(result).to.eql([1,2,3]);
-        });
-
-        it('should return only partial entries if filter is higher than zero', function() {
-
-            var result = startFrom([1,2,3], 2);
-
-            expect(result).to.eql([3]);
-        });
-
-        it('should return no entries if start is higher than input length', function() {
-
-            var result = startFrom([1,2,3], 11);
-
-            expect(result).to.eql([]);
-        });
-    });
-});
\ No newline at end of file
diff --git a/plugins/CoreHome/angularjs/common/filters/translate.js b/plugins/CoreHome/angularjs/common/filters/translate.js
index a23cbc5314c6fb48d45f610fa712b1e0e8630c46..42494f634e28130cbd997dd50664b4b4823390a6 100644
--- a/plugins/CoreHome/angularjs/common/filters/translate.js
+++ b/plugins/CoreHome/angularjs/common/filters/translate.js
@@ -4,16 +4,19 @@
  * @link http://piwik.org
  * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
  */
+(function () {
+    angular.module('piwikApp.filter').filter('translate', translate);
 
-angular.module('piwikApp.filter').filter('translate', function() {
+    function translate() {
 
-    return function(key, value1, value2, value3) {
-        var values = [];
-        if (arguments && arguments.length > 1) {
-            for (var index = 1; index < arguments.length; index++) {
-                values.push(arguments[index]);
+        return function(key, value1, value2, value3) {
+            var values = [];
+            if (arguments && arguments.length > 1) {
+                for (var index = 1; index < arguments.length; index++) {
+                    values.push(arguments[index]);
+                }
             }
-        }
-        return _pk_translate(key, values);
-    };
-});
\ No newline at end of file
+            return _pk_translate(key, values);
+        };
+    }
+})();
\ No newline at end of file
diff --git a/plugins/CoreHome/angularjs/common/filters/trim.js b/plugins/CoreHome/angularjs/common/filters/trim.js
index f0879a0911108aea47776ce1a7fd7440c705a9d4..d761618eb98529c413d65cf86e3ff2f21e90fca8 100644
--- a/plugins/CoreHome/angularjs/common/filters/trim.js
+++ b/plugins/CoreHome/angularjs/common/filters/trim.js
@@ -4,14 +4,17 @@
  * @link http://piwik.org
  * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
  */
+(function () {
+    angular.module('piwikApp.filter').filter('trim', trim);
 
-angular.module('piwikApp.filter').filter('trim', function() {
+    function trim() {
 
-    return function(string) {
-        if (string) {
-            return $.trim('' + string);
-        }
+        return function(string) {
+            if (string) {
+                return $.trim('' + string);
+            }
 
-        return string;
-    };
-});
+            return string;
+        };
+    }
+})();
diff --git a/plugins/CoreHome/angularjs/common/services/piwik-api.js b/plugins/CoreHome/angularjs/common/services/piwik-api.js
index d92bf0436c3bff20cb75dab3150b4cf007f2f117..90badd22080f26b8f6771bcaa56f36fe743c2052 100644
--- a/plugins/CoreHome/angularjs/common/services/piwik-api.js
+++ b/plugins/CoreHome/angularjs/common/services/piwik-api.js
@@ -4,265 +4,274 @@
  * @link http://piwik.org
  * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
  */
+(function () {
+    angular.module('piwikApp.service').factory('piwikApi', piwikApiService);
+
+    piwikApiService.$inject = ['$http', '$q', '$rootScope', 'piwik', '$window'];
+
+    function piwikApiService ($http, $q, $rootScope, piwik, $window) {
+
+        var url = 'index.php';
+        var format = 'json';
+        var getParams  = {};
+        var postParams = {};
+        var allRequests = [];
+
+        /**
+         * Adds params to the request.
+         * If params are given more then once, the latest given value is used for the request
+         *
+         * @param {object}  params
+         * @return {void}
+         */
+        function addParams (params) {
+            if (typeof params == 'string') {
+                params = piwik.broadcast.getValuesFromUrl(params);
+            }
 
-angular.module('piwikApp.service').factory('piwikApi', function ($http, $q, $rootScope, piwik, $window) {
-
-    var url = 'index.php';
-    var format = 'json';
-    var getParams  = {};
-    var postParams = {};
-    var allRequests = [];
-
-    var piwikApi = {};
-
-    /**
-     * Adds params to the request.
-     * If params are given more then once, the latest given value is used for the request
-     *
-     * @param {object}  params
-     * @return {void}
-     */
-    function addParams (params) {
-        if (typeof params == 'string') {
-            params = piwik.broadcast.getValuesFromUrl(params);
+            for (var key in params) {
+                getParams[key] = params[key];
+            }
         }
 
-        for (var key in params) {
-            getParams[key] = params[key];
+        function reset () {
+            getParams  = {};
+            postParams = {};
         }
-    }
-
-    function reset () {
-        getParams  = {};
-        postParams = {};
-    }
-
-    function isErrorResponse(response) {
-        return response && response.result == 'error';
-    }
 
-    function createResponseErrorNotification(response, options) {
-        if (response.message) {
-            var UI = require('piwik/UI');
-            var notification = new UI.Notification();
-            notification.show(response.message, {
-                context: 'error',
-                type: 'toast',
-                id: 'ajaxHelper',
-                placeat: options.placeat
-            });
-            notification.scrollToNotification();
+        function isErrorResponse(response) {
+            return response && response.result == 'error';
         }
-    }
 
-    /**
-     * Send the request
-     * @return $promise
-     */
-    function send (options) {
-        if (!options) {
-            options = {};
+        function createResponseErrorNotification(response, options) {
+            if (response.message) {
+                var UI = require('piwik/UI');
+                var notification = new UI.Notification();
+                notification.show(response.message, {
+                    context: 'error',
+                    type: 'toast',
+                    id: 'ajaxHelper',
+                    placeat: options.placeat
+                });
+                notification.scrollToNotification();
+            }
         }
 
-        var deferred = $q.defer(),
-            requestPromise = deferred.promise;
+        /**
+         * Send the request
+         * @return $promise
+         */
+        function send (options) {
+            if (!options) {
+                options = {};
+            }
 
-        var onError = function (message) {
-            deferred.reject(message);
-        };
+            var deferred = $q.defer(),
+                requestPromise = deferred.promise;
 
-        var onSuccess = function (response) {
-            if (isErrorResponse(response)) {
-                onError(response.message || null);
+            var onError = function (message) {
+                deferred.reject(message);
+            };
 
-                createResponseErrorNotification(response, options);
-            } else {
-                deferred.resolve(response);
-            }
-        };
+            var onSuccess = function (response) {
+                if (isErrorResponse(response)) {
+                    onError(response.message || null);
 
-        var headers = {
-            'Content-Type': 'application/x-www-form-urlencoded',
-            // ie 8,9,10 caches ajax requests, prevent this
-            'cache-control': 'no-cache'
-        };
+                    createResponseErrorNotification(response, options);
+                } else {
+                    deferred.resolve(response);
+                }
+            };
 
-        var ajaxCall = {
-            method: 'POST',
-            url: url,
-            responseType: format,
-            params: _mixinDefaultGetParams(getParams),
-            data: $.param(getPostParams(postParams)),
-            timeout: requestPromise,
-            headers: headers
-        };
+            var headers = {
+                'Content-Type': 'application/x-www-form-urlencoded',
+                // ie 8,9,10 caches ajax requests, prevent this
+                'cache-control': 'no-cache'
+            };
 
-        $http(ajaxCall).success(onSuccess).error(onError);
+            var ajaxCall = {
+                method: 'POST',
+                url: url,
+                responseType: format,
+                params: _mixinDefaultGetParams(getParams),
+                data: $.param(getPostParams(postParams)),
+                timeout: requestPromise,
+                headers: headers
+            };
 
-        // we can't modify requestPromise directly and add an abort method since for some reason it gets
-        // removed after then/finally/catch is called.
-        var addAbortMethod = function (to) {
-            return {
-                then: function () {
-                    return addAbortMethod(to.then.apply(to, arguments));
-                },
+            $http(ajaxCall).success(onSuccess).error(onError);
 
-                'finally': function () {
-                    return addAbortMethod(to['finally'].apply(to, arguments));
-                },
+            // we can't modify requestPromise directly and add an abort method since for some reason it gets
+            // removed after then/finally/catch is called.
+            var addAbortMethod = function (to) {
+                return {
+                    then: function () {
+                        return addAbortMethod(to.then.apply(to, arguments));
+                    },
 
-                'catch': function () {
-                    return addAbortMethod(to['catch'].apply(to, arguments));
-                },
+                    'finally': function () {
+                        return addAbortMethod(to['finally'].apply(to, arguments));
+                    },
 
-                abort: function () {
-                    deferred.reject();
-                    return this;
-                }
+                    'catch': function () {
+                        return addAbortMethod(to['catch'].apply(to, arguments));
+                    },
+
+                    abort: function () {
+                        deferred.reject();
+                        return this;
+                    }
+                };
             };
-        };
 
-        var request = addAbortMethod(requestPromise);
+            var request = addAbortMethod(requestPromise);
 
-        allRequests.push(request);
+            allRequests.push(request);
 
-        return request;
-    }
+            return request;
+        }
 
-    /**
-     * Get the parameters to send as POST
-     *
-     * @param {object}   params   parameter object
-     * @return {object}
-     * @private
-     */
-     function getPostParams (params) {
-        params.token_auth = piwik.token_auth;
-        return params;
-    }
+        /**
+         * Get the parameters to send as POST
+         *
+         * @param {object}   params   parameter object
+         * @return {object}
+         * @private
+         */
+        function getPostParams (params) {
+            params.token_auth = piwik.token_auth;
+            return params;
+        }
 
-    /**
-     * Mixin the default parameters to send as GET
-     *
-     * @param {object}   getParamsToMixin   parameter object
-     * @return {object}
-     * @private
-     */
-    function _mixinDefaultGetParams (getParamsToMixin) {
-
-        var defaultParams = {
-            idSite:  piwik.idSite || piwik.broadcast.getValueFromUrl('idSite'),
-            period:  piwik.period || piwik.broadcast.getValueFromUrl('period'),
-            segment: piwik.broadcast.getValueFromHash('segment', $window.location.href.split('#')[1])
-        };
+        /**
+         * Mixin the default parameters to send as GET
+         *
+         * @param {object}   getParamsToMixin   parameter object
+         * @return {object}
+         * @private
+         */
+        function _mixinDefaultGetParams (getParamsToMixin) {
+
+            var defaultParams = {
+                idSite:  piwik.idSite || piwik.broadcast.getValueFromUrl('idSite'),
+                period:  piwik.period || piwik.broadcast.getValueFromUrl('period'),
+                segment: piwik.broadcast.getValueFromHash('segment', $window.location.href.split('#')[1])
+            };
 
-        // never append token_auth to url
-        if (getParamsToMixin.token_auth) {
-            getParamsToMixin.token_auth = null;
-            delete getParamsToMixin.token_auth;
-        }
+            // never append token_auth to url
+            if (getParamsToMixin.token_auth) {
+                getParamsToMixin.token_auth = null;
+                delete getParamsToMixin.token_auth;
+            }
 
-        for (var key in defaultParams) {
-            if (!getParamsToMixin[key] && !postParams[key] && defaultParams[key]) {
-                getParamsToMixin[key] = defaultParams[key];
+            for (var key in defaultParams) {
+                if (!getParamsToMixin[key] && !postParams[key] && defaultParams[key]) {
+                    getParamsToMixin[key] = defaultParams[key];
+                }
             }
-        }
 
-        // handle default date & period if not already set
-        if (!getParamsToMixin.date && !postParams.date) {
-            getParamsToMixin.date = piwik.currentDateString || piwik.broadcast.getValueFromUrl('date');
-            if (getParamsToMixin.period == 'range' && piwik.currentDateString) {
-                getParamsToMixin.date = piwik.startDateString + ',' + getParamsToMixin.date;
+            // handle default date & period if not already set
+            if (!getParamsToMixin.date && !postParams.date) {
+                getParamsToMixin.date = piwik.currentDateString || piwik.broadcast.getValueFromUrl('date');
+                if (getParamsToMixin.period == 'range' && piwik.currentDateString) {
+                    getParamsToMixin.date = piwik.startDateString + ',' + getParamsToMixin.date;
+                }
             }
-        }
 
-        return getParamsToMixin;
-    }
+            return getParamsToMixin;
+        }
 
-    piwikApi.abortAll = function () {
-        reset();
+        function abortAll() {
+            reset();
 
-        allRequests.forEach(function (request) {
-            request.abort();
-        });
+            allRequests.forEach(function (request) {
+                request.abort();
+            });
 
-        allRequests = [];
-    };
+            allRequests = [];
+        };
 
-    /**
-     * @deprecated
-     */
-    piwikApi.abort = function () {
-        this.abortAll();
-    };
+        function abort () {
+            abortAll();
+        };
 
-    /**
-     * Perform a reading API request.
-     * @param getParams
-     */
-    piwikApi.fetch = function (getParams, options) {
+        /**
+         * Perform a reading API request.
+         * @param getParams
+         */
+        function fetch (getParams, options) {
 
-        getParams.module = getParams.module || 'API';
-        getParams.format = 'JSON2';
+            getParams.module = getParams.module || 'API';
+            getParams.format = 'JSON2';
 
-        addParams(getParams, 'GET');
+            addParams(getParams, 'GET');
 
-        var promise = send(options);
+            var promise = send(options);
 
-        reset();
+            reset();
 
-        return promise;
-    };
+            return promise;
+        };
 
-    piwikApi.post = function (getParams, _postParams_, options) {
-        if (_postParams_) {
-            postParams = _postParams_;
-        }
+        function post(getParams, _postParams_, options) {
+            if (_postParams_) {
+                postParams = _postParams_;
+            }
 
-        return this.fetch(getParams, options);
-    };
-
-    /**
-     * Convenience method that will perform a bulk request using Piwik's API.getBulkRequest method.
-     * Bulk requests allow you to execute multiple Piwik requests with one HTTP request.
-     *
-     * @param {object[]} requests
-     * @param {object} options
-     * @return {HttpPromise} a promise that is resolved when the request finishes. The argument passed
-     *                       to the .then(...) callback will be an array with one element per request
-     *                       made.
-     */
-    piwikApi.bulkFetch = function (requests, options) {
-        var bulkApiRequestParams = {
-            urls: requests.map(function (requestObj) { return '?' + $.param(requestObj); })
+            return fetch(getParams, options);
         };
 
-        var deferred = $q.defer(),
-            requestPromise = this.post({method: "API.getBulkRequest"}, bulkApiRequestParams, options).then(function (response) {
-                if (!(response instanceof Array)) {
-                    response = [response];
-                }
+        /**
+         * Convenience method that will perform a bulk request using Piwik's API.getBulkRequest method.
+         * Bulk requests allow you to execute multiple Piwik requests with one HTTP request.
+         *
+         * @param {object[]} requests
+         * @param {object} options
+         * @return {HttpPromise} a promise that is resolved when the request finishes. The argument passed
+         *                       to the .then(...) callback will be an array with one element per request
+         *                       made.
+         */
+        function bulkFetch(requests, options) {
+            var bulkApiRequestParams = {
+                urls: requests.map(function (requestObj) { return '?' + $.param(requestObj); })
+            };
 
-                // check for errors
-                for (var i = 0; i != response.length; ++i) {
-                    var specificResponse = response[i];
+            var deferred = $q.defer(),
+                requestPromise = post({method: "API.getBulkRequest"}, bulkApiRequestParams, options).then(function (response) {
+                    if (!(response instanceof Array)) {
+                        response = [response];
+                    }
 
-                    if (isErrorResponse(specificResponse)) {
-                        deferred.reject(specificResponse.message || null);
+                    // check for errors
+                    for (var i = 0; i != response.length; ++i) {
+                        var specificResponse = response[i];
 
-                        createResponseErrorNotification(specificResponse, options || {});
+                        if (isErrorResponse(specificResponse)) {
+                            deferred.reject(specificResponse.message || null);
 
-                        return;
+                            createResponseErrorNotification(specificResponse, options || {});
+
+                            return;
+                        }
                     }
-                }
 
-                deferred.resolve(response);
-            }).catch(function () {
-                deferred.reject.apply(deferred, arguments);
-            });
+                    deferred.resolve(response);
+                }).catch(function () {
+                    deferred.reject.apply(deferred, arguments);
+                });
 
-        return deferred.promise;
-    };
+            return deferred.promise;
+        };
 
-    return piwikApi;
-});
\ No newline at end of file
+        return {
+            bulkFetch: bulkFetch,
+            post: post,
+            fetch: fetch,
+            /**
+             * @deprecated
+             */
+            abort: abort,
+            abortAll: abortAll,
+        }
+    }
+})();
\ No newline at end of file
diff --git a/plugins/CoreHome/angularjs/common/services/piwik-api.spec.js b/plugins/CoreHome/angularjs/common/services/piwik-api.spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..9f72af9499d418c0b19ed43a6991f58038d66752
--- /dev/null
+++ b/plugins/CoreHome/angularjs/common/services/piwik-api.spec.js
@@ -0,0 +1,279 @@
+/*!
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+(function () {
+    describe('piwikApiClient', function () {
+        var piwikApi,
+            $httpBackend;
+
+        if (!window.piwik) window.piwik = {};
+        if (!window.piwik.UI) window.piwik.UI = {};
+        if (!window.piwik.UI.Notification) {
+            window.piwik.UI.Notification = function () {
+                this.show = function () {};
+                this.scrollToNotification = function () {};
+                return this;
+            };
+        }
+
+        beforeEach(module('piwikApp.service'));
+        beforeEach(inject(function($injector) {
+            piwikApi = $injector.get('piwikApi');
+
+            $httpBackend = $injector.get('$httpBackend');
+
+            $httpBackend.when('POST', /.*getBulkRequest.*/, /.*errorAction.*/).respond(function (method, url, data, headers) {
+                url = url.replace(/date=[^&]+/, "date=");
+
+                var errorResponse = {result: 'error', message: "error message"},
+                    successResponse= "Response #2: " + url + " - " + data;
+
+                return [200, [errorResponse, successResponse]];
+            });
+
+            $httpBackend.when('POST', /.*getBulkRequest.*/).respond(function (method, url, data, headers) {
+                url = url.replace(/date=[^&]+/, "date=");
+
+                var responses = [
+                    "Response #1: " + url + " - " + data,
+                    "Response #2: " + url + " - " + data
+                ];
+
+                return [200, JSON.stringify(responses)];
+            });
+
+            $httpBackend.when('POST', /.*/).respond(function (method, url, data, headers) {
+                url = url.replace(/date=[^&]+/, "date=");
+                return [200, "Request url: " + url];
+            });
+        }));
+
+        it("should successfully send a request to Piwik when fetch is called", function (done) {
+            piwikApi.fetch({
+                method: "SomePlugin.action"
+            }).then(function (response) {
+                expect(response).to.equal("Request url: index.php?date=&format=JSON2&idSite=1&method=SomePlugin.action&module=API&period=day");
+
+                done();
+            }).catch(function (ex) {
+                done(ex);
+            });
+
+            $httpBackend.flush();
+        });
+
+        it("should chain multiple then callbacks correctly when a fetch succeeds", function (done) {
+            var firstThenDone = false;
+
+            piwikApi.fetch({
+                method: "SomePlugin.action"
+            }).then(function (response) {
+                firstThenDone = true;
+
+                return "newval";
+            }).then(function (response) {
+                expect(firstThenDone).to.equal(true);
+                expect(response).to.equal("newval");
+
+                done();
+            }).catch(function (ex) {
+                done(ex);
+            });
+
+            $httpBackend.flush();
+        });
+
+        it("should not fail when multiple aborts are issued", function (done) {
+            var request = piwikApi.fetch({
+                method: "SomePlugin.action"
+            }).then(function (response) {
+                done(new Error("Aborted request succeeded!"));
+            }).catch(function (ex) {
+                done(ex);
+            });
+
+            request.abort();
+            request.abort();
+
+            $httpBackend.flush();
+
+            request.abort();
+        });
+
+        it("should send multiple requests concurrently when fetch is called more than once", function (done) {
+            var request1Done, request2Done;
+
+            function finishIfBothDone() {
+                if (request1Done && request2Done) {
+                    done();
+                }
+            }
+
+            piwikApi.fetch({
+                method: "SomePlugin.action"
+            }).then(function (response) {
+                expect(response).to.equal("Request url: index.php?date=&format=JSON2&idSite=1&method=SomePlugin.action&module=API&period=day");
+
+                request1Done = true;
+
+                finishIfBothDone();
+            }).catch(function (ex) {
+                done(ex);
+            });
+
+            piwikApi.fetch({
+                method: "SomeOtherPlugin.action"
+            }).then(function (response) {
+                expect(response).to.equal("Request url: index.php?date=&format=JSON2&idSite=1&method=SomeOtherPlugin.action&module=API&period=day");
+
+                request2Done = true;
+
+                finishIfBothDone();
+            }).catch(function (ex) {
+                done(ex);
+            });
+
+            $httpBackend.flush();
+        });
+
+        it("should abort individual requests when abort() is called on a promise", function (done) {
+            var request1Done, request2Done;
+
+            function finishIfBothDone() {
+                if (request1Done && request2Done) {
+                    done();
+                }
+            }
+
+            var request = piwikApi.fetch({
+                method: "SomePlugin.waitAction"
+            }).then(function (response) {
+                done(new Error("Aborted request finished!"));
+            }).catch(function (ex) {
+                done(ex);
+            }).finally(function () {
+                request1Done = true;
+                finishIfBothDone();
+            });
+
+            piwikApi.fetch({
+                method: "SomeOtherPlugin.action"
+            }).then(function (response) {
+                expect(response).to.equal("Request url: index.php?date=&format=JSON2&idSite=1&method=SomeOtherPlugin.action&module=API&period=day");
+
+                request2Done = true;
+
+                finishIfBothDone();
+            }).catch(function (ex) {
+                done(ex);
+            });
+
+            request.abort();
+
+            $httpBackend.flush();
+        });
+
+        it("should abort all requests when abortAll() is called on the piwikApi", function (done) {
+            var request1Done, request2Done;
+
+            function finishIfBothDone() {
+                if (request1Done && request2Done) {
+                    done();
+                }
+            }
+
+            piwikApi.fetch({
+                method: "SomePlugin.waitAction"
+            }).then(function (response) {
+                done(new Error("Aborted request finished (request 1)!"));
+            }).catch(function (ex) {
+                done(ex);
+            }).finally(function () {
+                request1Done = true;
+                finishIfBothDone();
+            });
+
+            piwikApi.fetch({
+                method: "SomePlugin.waitAction"
+            }).then(function (response) {
+                done(new Error("Aborted request finished (request 2)!"));
+            }).catch(function (ex) {
+                done(ex);
+            }).finally(function () {
+                request2Done = true;
+                finishIfBothDone();
+            });
+
+            piwikApi.abortAll();
+
+            $httpBackend.flush();
+        });
+
+        it("should perform a bulk request correctly when bulkFetch is called on the piwikApi", function (done) {
+            piwikApi.bulkFetch([
+                {
+                    method: "SomePlugin.action",
+                    param: "value"
+                },
+                {
+                    method: "SomeOtherPlugin.action"
+                }
+            ]).then(function (response) {
+                var restOfExpected = "index.php?date=&format=JSON2&idSite=1&method=API.getBulkRequest&" +
+                    "module=API&period=day - urls%5B%5D=%3Fmethod%3DSomePlugin.action%26param%3D" +
+                    "value&urls%5B%5D=%3Fmethod%3DSomeOtherPlugin.action&token_auth=100bf5eeeed1468f3f9d93750044d3dd";
+
+                expect(response.length).to.equal(2);
+                expect(response[0]).to.equal("Response #1: " + restOfExpected);
+                expect(response[1]).to.equal("Response #2: " + restOfExpected);
+
+                done();
+            }).catch(function (ex) {
+                done(ex);
+            });
+
+            $httpBackend.flush();
+        });
+
+        it("should correctly handle errors in a bulk request response", function (done) {
+            piwikApi.bulkFetch([
+                {
+                    method: "SomePlugin.errorAction"
+                },
+                {
+                    method: "SomeOtherPlugin.whatever"
+                }
+            ]).then(function (response) {
+                done(new Error("promise resolved after bulkFetch request returned an error (response = " + JSON.stringify(response) + ")"));
+            }).catch(function (error) {
+                expect(error).to.equal("error message");
+
+                done();
+            });
+
+            $httpBackend.flush();
+        });
+
+        it("shuld correctly handle errors in a bulk request response, regardless of error location", function (done) {
+            piwikApi.bulkFetch([
+                {
+                    method: "SomeOtherPlugin.whatever"
+                },
+                {
+                    method: "SomePlugin.errorAction"
+                }
+            ]).then(function (response) {
+                done(new Error("promise resolved after bulkFetch request returned an error (response = " + JSON.stringify(response) + ")"));
+            }).catch(function (error) {
+                expect(error).to.equal("error message");
+
+                done();
+            });
+
+            $httpBackend.flush();
+        });
+    });
+})();
\ No newline at end of file
diff --git a/plugins/CoreHome/angularjs/common/services/piwik-api_spec.js b/plugins/CoreHome/angularjs/common/services/piwik-api_spec.js
deleted file mode 100644
index 6e49c14ed1e06dfc71e574dc7d9533a2642c4a4d..0000000000000000000000000000000000000000
--- a/plugins/CoreHome/angularjs/common/services/piwik-api_spec.js
+++ /dev/null
@@ -1,278 +0,0 @@
-/*!
- * Piwik - free/libre analytics platform
- *
- * @link http://piwik.org
- * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
- */
-
-describe('piwikApiClient', function () {
-    var piwikApi,
-        $httpBackend;
-
-    if (!window.piwik) window.piwik = {};
-    if (!window.piwik.UI) window.piwik.UI = {};
-    if (!window.piwik.UI.Notification) {
-        window.piwik.UI.Notification = function () {
-            this.show = function () {};
-            this.scrollToNotification = function () {};
-            return this;
-        };
-    }
-
-    beforeEach(module('piwikApp.service'));
-    beforeEach(inject(function($injector) {
-        piwikApi = $injector.get('piwikApi');
-
-        $httpBackend = $injector.get('$httpBackend');
-
-        $httpBackend.when('POST', /.*getBulkRequest.*/, /.*errorAction.*/).respond(function (method, url, data, headers) {
-            url = url.replace(/date=[^&]+/, "date=");
-
-            var errorResponse = {result: 'error', message: "error message"},
-                successResponse= "Response #2: " + url + " - " + data;
-
-            return [200, [errorResponse, successResponse]];
-        });
-
-        $httpBackend.when('POST', /.*getBulkRequest.*/).respond(function (method, url, data, headers) {
-            url = url.replace(/date=[^&]+/, "date=");
-
-            var responses = [
-                "Response #1: " + url + " - " + data,
-                "Response #2: " + url + " - " + data
-            ];
-
-            return [200, JSON.stringify(responses)];
-        });
-
-        $httpBackend.when('POST', /.*/).respond(function (method, url, data, headers) {
-            url = url.replace(/date=[^&]+/, "date=");
-            return [200, "Request url: " + url];
-        });
-    }));
-
-    it("should successfully send a request to Piwik when fetch is called", function (done) {
-        piwikApi.fetch({
-            method: "SomePlugin.action"
-        }).then(function (response) {
-            expect(response).to.equal("Request url: index.php?date=&format=JSON2&idSite=1&method=SomePlugin.action&module=API&period=day");
-
-            done();
-        }).catch(function (ex) {
-            done(ex);
-        });
-
-        $httpBackend.flush();
-    });
-
-    it("should chain multiple then callbacks correctly when a fetch succeeds", function (done) {
-        var firstThenDone = false;
-
-        piwikApi.fetch({
-            method: "SomePlugin.action"
-        }).then(function (response) {
-            firstThenDone = true;
-
-            return "newval";
-        }).then(function (response) {
-            expect(firstThenDone).to.equal(true);
-            expect(response).to.equal("newval");
-
-            done();
-        }).catch(function (ex) {
-            done(ex);
-        });
-
-        $httpBackend.flush();
-    });
-
-    it("should not fail when multiple aborts are issued", function (done) {
-        var request = piwikApi.fetch({
-            method: "SomePlugin.action"
-        }).then(function (response) {
-            done(new Error("Aborted request succeeded!"));
-        }).catch(function (ex) {
-            done(ex);
-        });
-
-        request.abort();
-        request.abort();
-
-        $httpBackend.flush();
-
-        request.abort();
-    });
-
-    it("should send multiple requests concurrently when fetch is called more than once", function (done) {
-        var request1Done, request2Done;
-
-        function finishIfBothDone() {
-            if (request1Done && request2Done) {
-                done();
-            }
-        }
-
-        piwikApi.fetch({
-            method: "SomePlugin.action"
-        }).then(function (response) {
-            expect(response).to.equal("Request url: index.php?date=&format=JSON2&idSite=1&method=SomePlugin.action&module=API&period=day");
-
-            request1Done = true;
-
-            finishIfBothDone();
-        }).catch(function (ex) {
-            done(ex);
-        });
-
-        piwikApi.fetch({
-            method: "SomeOtherPlugin.action"
-        }).then(function (response) {
-            expect(response).to.equal("Request url: index.php?date=&format=JSON2&idSite=1&method=SomeOtherPlugin.action&module=API&period=day");
-
-            request2Done = true;
-
-            finishIfBothDone();
-        }).catch(function (ex) {
-            done(ex);
-        });
-
-        $httpBackend.flush();
-    });
-
-    it("should abort individual requests when abort() is called on a promise", function (done) {
-        var request1Done, request2Done;
-
-        function finishIfBothDone() {
-            if (request1Done && request2Done) {
-                done();
-            }
-        }
-
-        var request = piwikApi.fetch({
-            method: "SomePlugin.waitAction"
-        }).then(function (response) {
-            done(new Error("Aborted request finished!"));
-        }).catch(function (ex) {
-            done(ex);
-        }).finally(function () {
-            request1Done = true;
-            finishIfBothDone();
-        });
-
-        piwikApi.fetch({
-            method: "SomeOtherPlugin.action"
-        }).then(function (response) {
-            expect(response).to.equal("Request url: index.php?date=&format=JSON2&idSite=1&method=SomeOtherPlugin.action&module=API&period=day");
-
-            request2Done = true;
-
-            finishIfBothDone();
-        }).catch(function (ex) {
-            done(ex);
-        });
-
-        request.abort();
-
-        $httpBackend.flush();
-    });
-
-    it("should abort all requests when abortAll() is called on the piwikApi", function (done) {
-        var request1Done, request2Done;
-
-        function finishIfBothDone() {
-            if (request1Done && request2Done) {
-                done();
-            }
-        }
-
-        piwikApi.fetch({
-            method: "SomePlugin.waitAction"
-        }).then(function (response) {
-            done(new Error("Aborted request finished (request 1)!"));
-        }).catch(function (ex) {
-            done(ex);
-        }).finally(function () {
-            request1Done = true;
-            finishIfBothDone();
-        });
-
-        piwikApi.fetch({
-            method: "SomePlugin.waitAction"
-        }).then(function (response) {
-            done(new Error("Aborted request finished (request 2)!"));
-        }).catch(function (ex) {
-            done(ex);
-        }).finally(function () {
-            request2Done = true;
-            finishIfBothDone();
-        });
-
-        piwikApi.abortAll();
-
-        $httpBackend.flush();
-    });
-
-    it("should perform a bulk request correctly when bulkFetch is called on the piwikApi", function (done) {
-        piwikApi.bulkFetch([
-            {
-                method: "SomePlugin.action",
-                param: "value"
-            },
-            {
-                method: "SomeOtherPlugin.action"
-            }
-        ]).then(function (response) {
-            var restOfExpected = "index.php?date=&format=JSON2&idSite=1&method=API.getBulkRequest&" +
-                "module=API&period=day - urls%5B%5D=%3Fmethod%3DSomePlugin.action%26param%3D" +
-                "value&urls%5B%5D=%3Fmethod%3DSomeOtherPlugin.action&token_auth=100bf5eeeed1468f3f9d93750044d3dd";
-
-            expect(response.length).to.equal(2);
-            expect(response[0]).to.equal("Response #1: " + restOfExpected);
-            expect(response[1]).to.equal("Response #2: " + restOfExpected);
-
-            done();
-        }).catch(function (ex) {
-            done(ex);
-        });
-
-        $httpBackend.flush();
-    });
-
-    it("should correctly handle errors in a bulk request response", function (done) {
-        piwikApi.bulkFetch([
-            {
-                method: "SomePlugin.errorAction"
-            },
-            {
-                method: "SomeOtherPlugin.whatever"
-            }
-        ]).then(function (response) {
-            done(new Error("promise resolved after bulkFetch request returned an error (response = " + JSON.stringify(response) + ")"));
-        }).catch(function (error) {
-            expect(error).to.equal("error message");
-
-            done();
-        });
-
-        $httpBackend.flush();
-    });
-
-    it("shuld correctly handle errors in a bulk request response, regardless of error location", function (done) {
-        piwikApi.bulkFetch([
-            {
-                method: "SomeOtherPlugin.whatever"
-            },
-            {
-                method: "SomePlugin.errorAction"
-            }
-        ]).then(function (response) {
-            done(new Error("promise resolved after bulkFetch request returned an error (response = " + JSON.stringify(response) + ")"));
-        }).catch(function (error) {
-            expect(error).to.equal("error message");
-
-            done();
-        });
-
-        $httpBackend.flush();
-    });
-});
\ No newline at end of file
diff --git a/plugins/CoreHome/angularjs/common/services/piwik.js b/plugins/CoreHome/angularjs/common/services/piwik.js
index 80b904dabf1cc21670f6fcca5c0fa36e42886797..d0ada0dd4efbb6b6489ec1acdb9d7b8fa958b59c 100644
--- a/plugins/CoreHome/angularjs/common/services/piwik.js
+++ b/plugins/CoreHome/angularjs/common/services/piwik.js
@@ -4,10 +4,13 @@
  * @link http://piwik.org
  * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
  */
+(function () {
+    angular.module('piwikApp.service').service('piwik', piwikService);
 
-angular.module('piwikApp.service').service('piwik', function () {
+    function piwikService() {
 
-    piwik.helper    = piwikHelper;
-    piwik.broadcast = broadcast;
-    return piwik;
-});
+        piwik.helper    = piwikHelper;
+        piwik.broadcast = broadcast;
+        return piwik;
+    }
+})();
\ No newline at end of file
diff --git a/plugins/CoreHome/angularjs/common/services/piwik.spec.js b/plugins/CoreHome/angularjs/common/services/piwik.spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..44d7678e32c15c19aad0dc7ad8483b04ec351e37
--- /dev/null
+++ b/plugins/CoreHome/angularjs/common/services/piwik.spec.js
@@ -0,0 +1,38 @@
+/*!
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+(function () {
+    describe('piwikService', function() {
+        var piwikService;
+
+        beforeEach(module('piwikApp.service'));
+        beforeEach(inject(function($injector) {
+            piwikService = $injector.get('piwik');
+        }));
+
+        describe('#piwikService', function() {
+
+            it('should be the same as piwik global var', function() {
+                piwik.should.equal(piwikService);
+            });
+
+            it('should mixin broadcast', function() {
+                expect(piwikService.broadcast).to.be.an('object');
+            });
+
+            it('should mixin piwikHelper', function() {
+                expect(piwikService.helper).to.be.an('object');
+            });
+        });
+
+        describe('#piwik_url', function() {
+
+            it('should contain the piwik url', function() {
+                expect(piwikService.piwik_url).to.eql('http://localhost/');
+            });
+        });
+    });
+})();
\ No newline at end of file
diff --git a/plugins/CoreHome/angularjs/common/services/piwik_spec.js b/plugins/CoreHome/angularjs/common/services/piwik_spec.js
deleted file mode 100644
index c3eacc80ffe7730231a4d7c6a75142ff33b8c4f6..0000000000000000000000000000000000000000
--- a/plugins/CoreHome/angularjs/common/services/piwik_spec.js
+++ /dev/null
@@ -1,37 +0,0 @@
-/*!
- * Piwik - free/libre analytics platform
- *
- * @link http://piwik.org
- * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
- */
-
-describe('piwikService', function() {
-    var piwikService;
-
-    beforeEach(module('piwikApp.service'));
-    beforeEach(inject(function($injector) {
-        piwikService = $injector.get('piwik');
-    }));
-
-    describe('#piwikService', function() {
-
-        it('should be the same as piwik global var', function() {
-            piwik.should.equal(piwikService);
-        });
-
-        it('should mixin broadcast', function() {
-            expect(piwikService.broadcast).to.be.an('object');
-        });
-
-        it('should mixin piwikHelper', function() {
-            expect(piwikService.helper).to.be.an('object');
-        });
-    });
-
-    describe('#piwik_url', function() {
-
-        it('should contain the piwik url', function() {
-            expect(piwikService.piwik_url).to.eql('http://localhost/');
-        });
-    });
-});
\ No newline at end of file
diff --git a/plugins/CoreHome/angularjs/common/directives/directive.js b/plugins/CoreHome/angularjs/common/services/service.module.js
similarity index 69%
rename from plugins/CoreHome/angularjs/common/directives/directive.js
rename to plugins/CoreHome/angularjs/common/services/service.module.js
index c2d119e26e19c5065153c4e0c6c4294402d2d016..24f41cdfd87e9fc93baa8f04406899558de88eb3 100644
--- a/plugins/CoreHome/angularjs/common/directives/directive.js
+++ b/plugins/CoreHome/angularjs/common/services/service.module.js
@@ -4,5 +4,6 @@
  * @link http://piwik.org
  * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
  */
-
-angular.module('piwikApp.directive', []);
+(function () {
+    angular.module('piwikApp.service', []);
+})();
\ No newline at end of file
diff --git a/plugins/CoreHome/angularjs/dialogtoggler/dialogtoggler-controller.js b/plugins/CoreHome/angularjs/dialogtoggler/dialogtoggler-controller.js
deleted file mode 100644
index cc68a450cc8ffb2fad2b57033ba387dadc780ab5..0000000000000000000000000000000000000000
--- a/plugins/CoreHome/angularjs/dialogtoggler/dialogtoggler-controller.js
+++ /dev/null
@@ -1,62 +0,0 @@
-/*!
- * Piwik - free/libre analytics platform
- *
- * @link http://piwik.org
- * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
- */
-
-/**
- * Controller for the piwikDialogToggler directive. Adds a couple methods to the
- * scope allowing elements to open and close dialogs.
- */
-angular.module('piwikApp').controller('DialogTogglerController', function ($scope, piwik, ngDialog, piwikDialogtogglerUrllistener) {
-    /**
-     * Open a new dialog window using ngDialog.
-     *
-     * @param {object|string} contentsInfo If an object, it is assumed to be ngDialog open(...) config and is
-     *                                     passed to ngDialog.open unaltered.
-     *                                     If a string that beings with '#', we assume it is an ID of an element
-     *                                     with the dialog contents. (Note: ngDialog doesn't appear to support arbitrary
-     *                                     selectors).
-     *                                     If a string that ends with .html, we assume it is a link to a an angular
-     *                                     template.
-     *                                     Otherwise we assume it is a raw angular
-     * @return {object} Returns the result of ngDialog.open. Can be used to close the dialog or listen for
-     *                  when the dialog is closed.
-     */
-    $scope.open = function (contentsInfo) {
-        var ngDialogInfo;
-        if (typeof(contentsInfo) == 'object') { // is info to pass directly to ngDialog
-            ngDialogInfo = contentsInfo;
-        } else if (contentsInfo.substr(0, 1) == '#') { // is ID of an element
-            ngDialogInfo = {template: contentsInfo.substr(1)};
-        } else if (contentsInfo.substr(-4) == '.html') { // is a link to an .html file
-            ngDialogInfo = {template: contentsInfo};
-        } else { // is a raw HTML string
-            ngDialogInfo = {template: contentsInfo, plain: true};
-        }
-
-        return ngDialog.open(ngDialogInfo);
-    };
-
-    /**
-     * Opens a persisted dialog. Persisted dialogs are dialogs that will be launched on reload
-     * of the current URL. They are accomplished by modifying the URL and adding a 'popover'
-     * query parameter.
-     *
-     * @param {string} directive The denormalized name of an angularjs directive. An element with
-     *                           this directive will be the contents of the dialog.
-     * @param {object} attributes Key value mapping of the HTML attributes to add to the dialog's
-     *                            contents element.
-     */
-    $scope.persist = function (directive, attributes) {
-        piwikDialogtogglerUrllistener.propagatePersistedDialog(directive, attributes);
-    };
-
-    /**
-     * Closes the currently open dialog window.
-     */
-    $scope.close = function () {
-        ngDialog.close();
-    };
-});
\ No newline at end of file
diff --git a/plugins/CoreHome/angularjs/dialogtoggler/dialogtoggler-urllistener-service.js b/plugins/CoreHome/angularjs/dialogtoggler/dialogtoggler-urllistener-service.js
deleted file mode 100644
index dc8be42323a67e8f697636d8aa3ea1b88c995351..0000000000000000000000000000000000000000
--- a/plugins/CoreHome/angularjs/dialogtoggler/dialogtoggler-urllistener-service.js
+++ /dev/null
@@ -1,82 +0,0 @@
-/*!
- * Piwik - free/libre analytics platform
- *
- * @link http://piwik.org
- * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
- */
-
-/**
- * AngularJS service that handles the popover query parameter for Piwik's angular code.
- *
- * If the popover parameter's first part is the name of an existing AngularJS directive,
- * a dialog is created using ngDialog with the contents being an element with that directive.
- * The other parts of the parameter are treated as attributes for the element, eg,
- * `"mydirective:myparam=val:myotherparam=val2"`.
- *
- * It should not be necessary to use this service directly, instead the piwik-dialogtoggler
- * directive should be used.
- *
- * TODO: popover as a query parameter refers less to dialogs and more to any popup window
- *       (ie, not necessarily modal). should replace it w/ 'dialog' or maybe 'modal'.
- */
-angular.module('piwikApp').factory('piwikDialogtogglerUrllistener', function ($rootScope, $location, $injector, $rootElement, ngDialog) {
-    var service = {},
-        dialogQueryParamName = 'popover';
-
-    function getHtmlFromDialogQueryParam(paramValue) {
-        var info = paramValue.split(':'),
-            directiveName = info.shift(),
-            dialogContent = '';
-
-        dialogContent += '<div ' + directiveName;
-        angular.forEach(info, function (argumentAssignment) {
-            var pair = argumentAssignment.split('='),
-                key = pair[0],
-                value = pair[1];
-            dialogContent += ' ' + key + '="' + decodeURIComponent(value) + '"';
-        });
-        dialogContent += '/>';
-
-        return dialogContent;
-    }
-
-    function directiveExists(directiveAttributeString) {
-        // NOTE: directiveNormalize is not exposed by angularjs and the devs don't seem to want to expose it:
-        //       https://github.com/angular/angular.js/issues/7955
-        //       so logic is duplicated here.
-        var PREFIX_REGEXP = /^(x[\:\-_]|data[\:\-_])/i,
-            directiveName = angular.element.camelCase(directiveAttributeString.replace(PREFIX_REGEXP, ''));
-
-        return $injector.has(directiveName + 'Directive');
-    }
-
-    service.checkUrlForDialog = function () {
-        var dialogParamValue = $location.search()[dialogQueryParamName];
-        if (dialogParamValue && directiveExists(dialogParamValue)) {
-            var dialog = ngDialog.open({
-                template: getHtmlFromDialogQueryParam(dialogParamValue),
-                plain: true,
-                className: ''
-            });
-
-            dialog.closePromise.then(function () {
-                $location.search(dialogQueryParamName, null);
-            });
-        }
-    };
-
-    service.propagatePersistedDialog = function (directive, attributes) {
-        var paramValue = directive;
-        angular.forEach(attributes, function (value, name) {
-            paramValue += ':' + name + '=' + encodeURIComponent(value);
-        });
-
-        $location.search(dialogQueryParamName, paramValue);
-    };
-
-    $rootScope.$on('$locationChangeSuccess', function () {
-        service.checkUrlForDialog();
-    });
-
-    return service;
-});
\ No newline at end of file
diff --git a/plugins/CoreHome/angularjs/dialogtoggler/dialogtoggler-urllistener.service.js b/plugins/CoreHome/angularjs/dialogtoggler/dialogtoggler-urllistener.service.js
new file mode 100644
index 0000000000000000000000000000000000000000..690f39c0c564bd1f3b79f5ead06bd8e3d4b87073
--- /dev/null
+++ b/plugins/CoreHome/angularjs/dialogtoggler/dialogtoggler-urllistener.service.js
@@ -0,0 +1,88 @@
+/*!
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+/**
+ * AngularJS service that handles the popover query parameter for Piwik's angular code.
+ *
+ * If the popover parameter's first part is the name of an existing AngularJS directive,
+ * a dialog is created using ngDialog with the contents being an element with that directive.
+ * The other parts of the parameter are treated as attributes for the element, eg,
+ * `"mydirective:myparam=val:myotherparam=val2"`.
+ *
+ * It should not be necessary to use this service directly, instead the piwik-dialogtoggler
+ * directive should be used.
+ *
+ * TODO: popover as a query parameter refers less to dialogs and more to any popup window
+ *       (ie, not necessarily modal). should replace it w/ 'dialog' or maybe 'modal'.
+ */
+(function () {
+    angular.module('piwikApp').factory('piwikDialogtogglerUrllistener', piwikDialogtogglerUrllistener);
+
+    piwikDialogtogglerUrllistener.$inject = ['$rootScope', '$location', '$injector', '$rootElement', 'ngDialog'];
+
+    function piwikDialogtogglerUrllistener($rootScope, $location, $injector, $rootElement, ngDialog) {
+        var service = {},
+            dialogQueryParamName = 'popover';
+
+        function getHtmlFromDialogQueryParam(paramValue) {
+            var info = paramValue.split(':'),
+                directiveName = info.shift(),
+                dialogContent = '';
+
+            dialogContent += '<div ' + directiveName;
+            angular.forEach(info, function (argumentAssignment) {
+                var pair = argumentAssignment.split('='),
+                    key = pair[0],
+                    value = pair[1];
+                dialogContent += ' ' + key + '="' + decodeURIComponent(value) + '"';
+            });
+            dialogContent += '/>';
+
+            return dialogContent;
+        }
+
+        function directiveExists(directiveAttributeString) {
+            // NOTE: directiveNormalize is not exposed by angularjs and the devs don't seem to want to expose it:
+            //       https://github.com/angular/angular.js/issues/7955
+            //       so logic is duplicated here.
+            var PREFIX_REGEXP = /^(x[\:\-_]|data[\:\-_])/i,
+                directiveName = angular.element.camelCase(directiveAttributeString.replace(PREFIX_REGEXP, ''));
+
+            return $injector.has(directiveName + 'Directive');
+        }
+
+        service.checkUrlForDialog = function () {
+            var dialogParamValue = $location.search()[dialogQueryParamName];
+            if (dialogParamValue && directiveExists(dialogParamValue)) {
+                var dialog = ngDialog.open({
+                    template: getHtmlFromDialogQueryParam(dialogParamValue),
+                    plain: true,
+                    className: ''
+                });
+
+                dialog.closePromise.then(function () {
+                    $location.search(dialogQueryParamName, null);
+                });
+            }
+        };
+
+        service.propagatePersistedDialog = function (directive, attributes) {
+            var paramValue = directive;
+            angular.forEach(attributes, function (value, name) {
+                paramValue += ':' + name + '=' + encodeURIComponent(value);
+            });
+
+            $location.search(dialogQueryParamName, paramValue);
+        };
+
+        $rootScope.$on('$locationChangeSuccess', function () {
+            service.checkUrlForDialog();
+        });
+
+        return service;
+    }
+})();
\ No newline at end of file
diff --git a/plugins/CoreHome/angularjs/dialogtoggler/dialogtoggler.controller.js b/plugins/CoreHome/angularjs/dialogtoggler/dialogtoggler.controller.js
new file mode 100644
index 0000000000000000000000000000000000000000..17e13d4a9895ae2314a3a99dacd76048aadf3c49
--- /dev/null
+++ b/plugins/CoreHome/angularjs/dialogtoggler/dialogtoggler.controller.js
@@ -0,0 +1,68 @@
+/*!
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+/**
+ * Controller for the piwikDialogToggler directive. Adds a couple methods to the
+ * scope allowing elements to open and close dialogs.
+ */
+(function () {
+    angular.module('piwikApp').controller('DialogTogglerController', DialogTogglerController);
+
+    DialogTogglerController.$inject = ['$scope', 'piwik', 'ngDialog', 'piwikDialogtogglerUrllistener'];
+
+    function DialogTogglerController($scope, piwik, ngDialog, piwikDialogtogglerUrllistener) {
+        /**
+         * Open a new dialog window using ngDialog.
+         *
+         * @param {object|string} contentsInfo If an object, it is assumed to be ngDialog open(...) config and is
+         *                                     passed to ngDialog.open unaltered.
+         *                                     If a string that beings with '#', we assume it is an ID of an element
+         *                                     with the dialog contents. (Note: ngDialog doesn't appear to support arbitrary
+         *                                     selectors).
+         *                                     If a string that ends with .html, we assume it is a link to a an angular
+         *                                     template.
+         *                                     Otherwise we assume it is a raw angular
+         * @return {object} Returns the result of ngDialog.open. Can be used to close the dialog or listen for
+         *                  when the dialog is closed.
+         */
+        $scope.open = function (contentsInfo) {
+            var ngDialogInfo;
+            if (typeof(contentsInfo) == 'object') { // is info to pass directly to ngDialog
+                ngDialogInfo = contentsInfo;
+            } else if (contentsInfo.substr(0, 1) == '#') { // is ID of an element
+                ngDialogInfo = {template: contentsInfo.substr(1)};
+            } else if (contentsInfo.substr(-4) == '.html') { // is a link to an .html file
+                ngDialogInfo = {template: contentsInfo};
+            } else { // is a raw HTML string
+                ngDialogInfo = {template: contentsInfo, plain: true};
+            }
+
+            return ngDialog.open(ngDialogInfo);
+        };
+
+        /**
+         * Opens a persisted dialog. Persisted dialogs are dialogs that will be launched on reload
+         * of the current URL. They are accomplished by modifying the URL and adding a 'popover'
+         * query parameter.
+         *
+         * @param {string} directive The denormalized name of an angularjs directive. An element with
+         *                           this directive will be the contents of the dialog.
+         * @param {object} attributes Key value mapping of the HTML attributes to add to the dialog's
+         *                            contents element.
+         */
+        $scope.persist = function (directive, attributes) {
+            piwikDialogtogglerUrllistener.propagatePersistedDialog(directive, attributes);
+        };
+
+        /**
+         * Closes the currently open dialog window.
+         */
+        $scope.close = function () {
+            ngDialog.close();
+        };
+    }
+})();
\ No newline at end of file
diff --git a/plugins/CoreHome/angularjs/dialogtoggler/dialogtoggler-directive.js b/plugins/CoreHome/angularjs/dialogtoggler/dialogtoggler.directive.js
similarity index 64%
rename from plugins/CoreHome/angularjs/dialogtoggler/dialogtoggler-directive.js
rename to plugins/CoreHome/angularjs/dialogtoggler/dialogtoggler.directive.js
index 4b7859fe1f5c80b3575b22c3eedb1fd0b0d51b22..2de7aa0a89d0798387ac664015b61c4a1dbbb000 100644
--- a/plugins/CoreHome/angularjs/dialogtoggler/dialogtoggler-directive.js
+++ b/plugins/CoreHome/angularjs/dialogtoggler/dialogtoggler.directive.js
@@ -18,9 +18,13 @@
  *     <a href="#" ng-click="close()">Close</a>
  * </div>
  */
-angular.module('piwikApp').directive('piwikDialogtoggler', function () {
-    return {
-        restrict: 'A',
-        controller: 'DialogTogglerController'
-    };
-});
\ No newline at end of file
+(function () {
+    angular.module('piwikApp').directive('piwikDialogtoggler', piwikDialogtoggler);
+
+    function piwikDialogtoggler() {
+        return {
+            restrict: 'A',
+            controller: 'DialogTogglerController'
+        };
+    }
+})();
\ No newline at end of file
diff --git a/plugins/CoreHome/angularjs/enrichedheadline/enrichedheadline-directive.js b/plugins/CoreHome/angularjs/enrichedheadline/enrichedheadline-directive.js
deleted file mode 100644
index 83956e51ee9ac3c79c436f44ffd37cd9404920c9..0000000000000000000000000000000000000000
--- a/plugins/CoreHome/angularjs/enrichedheadline/enrichedheadline-directive.js
+++ /dev/null
@@ -1,66 +0,0 @@
-/*!
- * Piwik - free/libre analytics platform
- *
- * @link http://piwik.org
- * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
- */
-
-/**
- * Usage:
- *
- * <h2 piwik-enriched-headline>All Websites Dashboard</h2>
- * -> uses "All Websites Dashboard" as featurename
- *
- * <h2 piwik-enriched-headline feature-name="All Websites Dashboard">All Websites Dashboard (Total: 309 Visits)</h2>
- * -> custom featurename
- *
- * <h2 piwik-enriched-headline help-url="http://piwik.org/guide">All Websites Dashboard</h2>
- * -> shows help icon and links to external url
- *
- * <h2 piwik-enriched-headline>All Websites Dashboard
- *     <div class="inlineHelp>My <strong>inline help</strong></div>
- * </h2>
- * -> shows help icon to display inline help on click. Note: You can combine inlinehelp and help-url
- */
-angular.module('piwikApp').directive('piwikEnrichedHeadline', function($document, piwik, $filter){
-    var defaults = {
-        helpUrl: ''
-    };
-
-    return {
-        transclude: true,
-        restrict: 'A',
-        scope: {
-            helpUrl: '@',
-            featureName: '@'
-        },
-        templateUrl: 'plugins/CoreHome/angularjs/enrichedheadline/enrichedheadline.html?cb=' + piwik.cacheBuster,
-        compile: function (element, attrs) {
-
-            for (var index in defaults) {
-               if (!attrs[index]) { attrs[index] = defaults[index]; }
-            }
-
-            return function (scope, element, attrs) {
-
-                var helpNode = $('[ng-transclude] .inlineHelp', element);
-
-                if ((!helpNode || !helpNode.length) && element.next()) {
-                    // hack for reports :(
-                    helpNode = element.next().find('.reportDocumentation');
-                }
-
-                if (helpNode && helpNode.length) {
-                    if ($.trim(helpNode.text())) {
-                        scope.inlineHelp = $.trim(helpNode.html());
-                    }
-                    helpNode.remove();
-                }
-
-                if (!attrs.featureName) {
-                    attrs.featureName = $.trim(element.text());
-                }
-            };
-        }
-    };
-});
\ No newline at end of file
diff --git a/plugins/CoreHome/angularjs/enrichedheadline/enrichedheadline.html b/plugins/CoreHome/angularjs/enrichedheadline/enrichedheadline.directive.html
similarity index 100%
rename from plugins/CoreHome/angularjs/enrichedheadline/enrichedheadline.html
rename to plugins/CoreHome/angularjs/enrichedheadline/enrichedheadline.directive.html
diff --git a/plugins/CoreHome/angularjs/enrichedheadline/enrichedheadline.directive.js b/plugins/CoreHome/angularjs/enrichedheadline/enrichedheadline.directive.js
new file mode 100644
index 0000000000000000000000000000000000000000..56780348213087f19f89eb8862e6c01417818c37
--- /dev/null
+++ b/plugins/CoreHome/angularjs/enrichedheadline/enrichedheadline.directive.js
@@ -0,0 +1,72 @@
+/*!
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+/**
+ * Usage:
+ *
+ * <h2 piwik-enriched-headline>All Websites Dashboard</h2>
+ * -> uses "All Websites Dashboard" as featurename
+ *
+ * <h2 piwik-enriched-headline feature-name="All Websites Dashboard">All Websites Dashboard (Total: 309 Visits)</h2>
+ * -> custom featurename
+ *
+ * <h2 piwik-enriched-headline help-url="http://piwik.org/guide">All Websites Dashboard</h2>
+ * -> shows help icon and links to external url
+ *
+ * <h2 piwik-enriched-headline>All Websites Dashboard
+ *     <div class="inlineHelp>My <strong>inline help</strong></div>
+ * </h2>
+ * -> shows help icon to display inline help on click. Note: You can combine inlinehelp and help-url
+ */
+(function () {
+    angular.module('piwikApp').directive('piwikEnrichedHeadline', piwikEnrichedHeadline);
+
+    piwikEnrichedHeadline.$inject = ['$document', 'piwik', '$filter'];
+
+    function piwikEnrichedHeadline($document, piwik, $filter){
+        var defaults = {
+            helpUrl: ''
+        };
+
+        return {
+            transclude: true,
+            restrict: 'A',
+            scope: {
+                helpUrl: '@',
+                featureName: '@'
+            },
+            templateUrl: 'plugins/CoreHome/angularjs/enrichedheadline/enrichedheadline.directive.html?cb=' + piwik.cacheBuster,
+            compile: function (element, attrs) {
+
+                for (var index in defaults) {
+                    if (!attrs[index]) { attrs[index] = defaults[index]; }
+                }
+
+                return function (scope, element, attrs) {
+
+                    var helpNode = $('[ng-transclude] .inlineHelp', element);
+
+                    if ((!helpNode || !helpNode.length) && element.next()) {
+                        // hack for reports :(
+                        helpNode = element.next().find('.reportDocumentation');
+                    }
+
+                    if (helpNode && helpNode.length) {
+                        if ($.trim(helpNode.text())) {
+                            scope.inlineHelp = $.trim(helpNode.html());
+                        }
+                        helpNode.remove();
+                    }
+
+                    if (!attrs.featureName) {
+                        attrs.featureName = $.trim(element.text());
+                    }
+                };
+            }
+        };
+    }
+})();
\ No newline at end of file
diff --git a/plugins/CoreHome/angularjs/enrichedheadline/enrichedheadline.less b/plugins/CoreHome/angularjs/enrichedheadline/enrichedheadline.directive.less
similarity index 100%
rename from plugins/CoreHome/angularjs/enrichedheadline/enrichedheadline.less
rename to plugins/CoreHome/angularjs/enrichedheadline/enrichedheadline.directive.less
diff --git a/plugins/CoreHome/angularjs/http404check.js b/plugins/CoreHome/angularjs/http404check.js
index 0748fa42c69904d3d1303b8a8c940daba872416b..f2e2a86edfd3adc88b911f2a4132feafe22e2928 100644
--- a/plugins/CoreHome/angularjs/http404check.js
+++ b/plugins/CoreHome/angularjs/http404check.js
@@ -1,44 +1,52 @@
-angular.module('piwikApp').factory('http404CheckInterceptor', function($q) {
+(function () {
+    angular.module('piwikApp').factory('http404CheckInterceptor', http404CheckInterceptor);
 
-    function isClientError(rejection)
-    {
-        if (rejection.status === 500) {
-            return true;
-        }
+    http404CheckInterceptor.$inject = ['$q'];
 
-        return rejection.status >= 400 && rejection.status < 408;
-    }
+    function http404CheckInterceptor($q) {
 
-    return {
-
-        'responseError': function(rejection) {
-            if (rejection &&
-                isClientError(rejection) &&
-                rejection.config &&
-                rejection.config.url &&
-                -1 !== rejection.config.url.indexOf('.html') &&
-                -1 !== rejection.config.url.indexOf('plugins')) {
-
-                var posEndUrl = rejection.config.url.indexOf('.html') + 5;
-                var url       = rejection.config.url.substr(0, posEndUrl);
-
-                var message = 'Please check your server configuration. You may want to whitelist "*.html" files from the "plugins" directory.';
-                message    += ' The HTTP status code is ' + rejection.status + ' for URL "' + url + '"';
-
-                var UI = require('piwik/UI');
-                var notification = new UI.Notification();
-                notification.show(message, {
-                    title: 'Failed to load HTML file:',
-                    context: 'error',
-                    id: 'Network_HtmlFileLoadingError'
-                });
+        function isClientError(rejection)
+        {
+            if (rejection.status === 500) {
+                return true;
             }
 
-            return $q.reject(rejection);
+            return rejection.status >= 400 && rejection.status < 408;
         }
-    };
-});
 
-angular.module('piwikApp').config(['$httpProvider',function($httpProvider) {
-    $httpProvider.interceptors.push('http404CheckInterceptor');
-}]);
+        return {
+
+            'responseError': function(rejection) {
+                if (rejection &&
+                    isClientError(rejection) &&
+                    rejection.config &&
+                    rejection.config.url &&
+                    -1 !== rejection.config.url.indexOf('.html') &&
+                    -1 !== rejection.config.url.indexOf('plugins')) {
+
+                    var posEndUrl = rejection.config.url.indexOf('.html') + 5;
+                    var url       = rejection.config.url.substr(0, posEndUrl);
+
+                    var message = 'Please check your server configuration. You may want to whitelist "*.html" files from the "plugins" directory.';
+                    message    += ' The HTTP status code is ' + rejection.status + ' for URL "' + url + '"';
+
+                    var UI = require('piwik/UI');
+                    var notification = new UI.Notification();
+                    notification.show(message, {
+                        title: 'Failed to load HTML file:',
+                        context: 'error',
+                        id: 'Network_HtmlFileLoadingError'
+                    });
+                }
+
+                return $q.reject(rejection);
+            }
+        };
+    }
+
+    angular.module('piwikApp').config(['$httpProvider',function($httpProvider) {
+        $httpProvider.interceptors.push('http404CheckInterceptor');
+    }]);
+
+
+})();
diff --git a/plugins/CoreHome/angularjs/menudropdown/menudropdown-directive.js b/plugins/CoreHome/angularjs/menudropdown/menudropdown-directive.js
deleted file mode 100644
index 4d21f7a4e1e35a75640f4dc17721c5a2d22adfc9..0000000000000000000000000000000000000000
--- a/plugins/CoreHome/angularjs/menudropdown/menudropdown-directive.js
+++ /dev/null
@@ -1,69 +0,0 @@
-/*!
- * Piwik - free/libre analytics platform
- *
- * @link http://piwik.org
- * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
- */
-
-/**
- * Usage:
- * <div piwik-menudropdown menu-title="MyMenuItem" tooltip="My Tooltip" show-search="false">
- *     <a class="item" href="/url">An Item</a>
- *     <a class="item disabled">Disabled</a>
- *     <a class="item active">Active item</a>
- *     <hr class="item separator"/>
- *     <a class="item disabled category">Category</a>
- *     <a class="item" href="/url"></a>
- * </div>
- */
-angular.module('piwikApp').directive('piwikMenudropdown', function(){
-
-    return {
-        transclude: true,
-        replace: true,
-        restrict: 'A',
-        scope: {
-            menuTitle: '@',
-            tooltip: '@',
-            showSearch: '=',
-            menuTitleChangeOnClick: '='
-        },
-        templateUrl: 'plugins/CoreHome/angularjs/menudropdown/menudropdown.html?cb=' + piwik.cacheBuster,
-        link: function(scope, element, attrs) {
-
-            element.find('.item').on('click', function () {
-                var $self = angular.element(this);
-
-                if ($self.hasClass('disabled') || $self.hasClass('separator')) {
-                    return;
-                }
-
-                if (scope.menuTitleChangeOnClick !== false) {
-                    scope.menuTitle = $self.text().replace(/[\u0000-\u2666]/g, function(c) {
-                        return '&#'+c.charCodeAt(0)+';';
-                    });
-                }
-                scope.$eval('view.showItems = false');
-                scope.$apply();
-
-                element.find('.item').removeClass('active');
-                $self.addClass('active');
-            });
-
-            scope.searchItems = function (searchTerm)
-            {
-                searchTerm = searchTerm.toLowerCase();
-
-                element.find('.item').each(function (index, node) {
-                    var $node = angular.element(node);
-
-                    if (-1 === $node.text().toLowerCase().indexOf(searchTerm)) {
-                        $node.hide();
-                    } else {
-                        $node.show();
-                    }
-                });
-            };
-        }
-    };
-});
diff --git a/plugins/CoreHome/angularjs/menudropdown/menudropdown.html b/plugins/CoreHome/angularjs/menudropdown/menudropdown.directive.html
similarity index 100%
rename from plugins/CoreHome/angularjs/menudropdown/menudropdown.html
rename to plugins/CoreHome/angularjs/menudropdown/menudropdown.directive.html
diff --git a/plugins/CoreHome/angularjs/menudropdown/menudropdown.directive.js b/plugins/CoreHome/angularjs/menudropdown/menudropdown.directive.js
new file mode 100644
index 0000000000000000000000000000000000000000..00fbf87d7bca577f4628b214bdcc368ce059b35d
--- /dev/null
+++ b/plugins/CoreHome/angularjs/menudropdown/menudropdown.directive.js
@@ -0,0 +1,73 @@
+/*!
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+/**
+ * Usage:
+ * <div piwik-menudropdown menu-title="MyMenuItem" tooltip="My Tooltip" show-search="false">
+ *     <a class="item" href="/url">An Item</a>
+ *     <a class="item disabled">Disabled</a>
+ *     <a class="item active">Active item</a>
+ *     <hr class="item separator"/>
+ *     <a class="item disabled category">Category</a>
+ *     <a class="item" href="/url"></a>
+ * </div>
+ */
+(function () {
+    angular.module('piwikApp').directive('piwikMenudropdown', piwikMenudropdown);
+
+    function piwikMenudropdown(){
+
+        return {
+            transclude: true,
+            replace: true,
+            restrict: 'A',
+            scope: {
+                menuTitle: '@',
+                tooltip: '@',
+                showSearch: '=',
+                menuTitleChangeOnClick: '='
+            },
+            templateUrl: 'plugins/CoreHome/angularjs/menudropdown/menudropdown.directive.html?cb=' + piwik.cacheBuster,
+            link: function(scope, element, attrs) {
+
+                element.find('.item').on('click', function () {
+                    var $self = angular.element(this);
+
+                    if ($self.hasClass('disabled') || $self.hasClass('separator')) {
+                        return;
+                    }
+
+                    if (scope.menuTitleChangeOnClick !== false) {
+                        scope.menuTitle = $self.text().replace(/[\u0000-\u2666]/g, function(c) {
+                            return '&#'+c.charCodeAt(0)+';';
+                        });
+                    }
+                    scope.$eval('view.showItems = false');
+                    scope.$apply();
+
+                    element.find('.item').removeClass('active');
+                    $self.addClass('active');
+                });
+
+                scope.searchItems = function (searchTerm)
+                {
+                    searchTerm = searchTerm.toLowerCase();
+
+                    element.find('.item').each(function (index, node) {
+                        var $node = angular.element(node);
+
+                        if (-1 === $node.text().toLowerCase().indexOf(searchTerm)) {
+                            $node.hide();
+                        } else {
+                            $node.show();
+                        }
+                    });
+                };
+            }
+        };
+    }
+})();
diff --git a/plugins/CoreHome/angularjs/menudropdown/menudropdown.less b/plugins/CoreHome/angularjs/menudropdown/menudropdown.directive.less
similarity index 100%
rename from plugins/CoreHome/angularjs/menudropdown/menudropdown.less
rename to plugins/CoreHome/angularjs/menudropdown/menudropdown.directive.less
diff --git a/plugins/CoreHome/angularjs/piwikAppConfig.js b/plugins/CoreHome/angularjs/piwikApp.config.js
similarity index 89%
rename from plugins/CoreHome/angularjs/piwikAppConfig.js
rename to plugins/CoreHome/angularjs/piwikApp.config.js
index 4053dfe54b8be4e183bec7e5daf1ed76e5a3603a..8d73dda3838afe490eeae035eba2b12a98f219e4 100644
--- a/plugins/CoreHome/angularjs/piwikAppConfig.js
+++ b/plugins/CoreHome/angularjs/piwikApp.config.js
@@ -1,6 +1,6 @@
-angular.module('piwikApp.config', []);
-
 (function () {
+    angular.module('piwikApp.config', []);
+
     if ('undefined' === (typeof piwik) || !piwik) {
         return;
     }
diff --git a/plugins/CoreHome/angularjs/piwikApp.js b/plugins/CoreHome/angularjs/piwikApp.js
index 3aafe9b87cf315152c03d78243e7f895550ac319..d505e348175153d2172107223369f07802d62131 100644
--- a/plugins/CoreHome/angularjs/piwikApp.js
+++ b/plugins/CoreHome/angularjs/piwikApp.js
@@ -4,15 +4,16 @@
  * @link http://piwik.org
  * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
  */
-
-angular.module('piwikApp', [
-    'ngSanitize',
-    'ngAnimate',
-    'ngCookies',
-    'ngDialog',
-    'piwikApp.config',
-    'piwikApp.service',
-    'piwikApp.directive',
-    'piwikApp.filter'
-]);
-angular.module('app', []);
\ No newline at end of file
+(function () {
+    angular.module('piwikApp', [
+        'ngSanitize',
+        'ngAnimate',
+        'ngCookies',
+        'ngDialog',
+        'piwikApp.config',
+        'piwikApp.service',
+        'piwikApp.directive',
+        'piwikApp.filter'
+    ]);
+    angular.module('app', []);
+})();
\ No newline at end of file
diff --git a/plugins/CoreHome/angularjs/siteselector/siteselector-controller.js b/plugins/CoreHome/angularjs/siteselector/siteselector-controller.js
deleted file mode 100644
index 143a83e402960e678f50251019430b373e0013dc..0000000000000000000000000000000000000000
--- a/plugins/CoreHome/angularjs/siteselector/siteselector-controller.js
+++ /dev/null
@@ -1,46 +0,0 @@
-/*!
- * Piwik - free/libre analytics platform
- *
- * @link http://piwik.org
- * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
- */
-
-angular.module('piwikApp').controller('SiteSelectorController', function($scope, siteSelectorModel, piwik, AUTOCOMPLETE_MIN_SITES){
-
-    $scope.model = siteSelectorModel;
-
-    $scope.autocompleteMinSites = AUTOCOMPLETE_MIN_SITES;
-    $scope.selectedSite = {id: '', name: ''};
-    $scope.activeSiteId = piwik.idSite;
-    $scope.selectedSiteNameHtml = '';
-
-    $scope.switchSite = function (site) {
-        $scope.selectedSite = {id: site.idsite, name: site.name};
-
-        if (!$scope.switchSiteOnSelect || $scope.activeSiteId == site.idsite) {
-            return;
-        }
-
-        $scope.model.loadSite(site.idsite);
-    };
-
-    $scope.getUrlAllSites = function () {
-        var newParameters = 'module=MultiSites&action=index';
-        return piwik.helper.getCurrentQueryStringWithParametersModified(newParameters);
-    };
-    $scope.getUrlForSiteId = function (idSite) {
-        var idSiteParam   = 'idSite=' + idSite;
-        var newParameters = 'segment=&' + idSiteParam;
-        var hash = piwik.broadcast.isHashExists() ? piwik.broadcast.getHashFromUrl() : "";
-        return piwik.helper.getCurrentQueryStringWithParametersModified(newParameters) +
-            '#' + piwik.helper.getQueryStringWithParametersModified(hash.substring(1), newParameters);
-    };
-
-    $scope.$watch('selectedSite', function (site) {
-        $scope.selectedSiteNameHtml = site.name.replace(/[\u0000-\u2666]/g, function(c) {
-            return '&#'+c.charCodeAt(0)+';';
-        });
-    });
-
-    siteSelectorModel.loadInitialSites();
-});
diff --git a/plugins/CoreHome/angularjs/siteselector/siteselector-directive.js b/plugins/CoreHome/angularjs/siteselector/siteselector-directive.js
deleted file mode 100644
index 38107ca63556fffde844984dbc5f07302711a98f..0000000000000000000000000000000000000000
--- a/plugins/CoreHome/angularjs/siteselector/siteselector-directive.js
+++ /dev/null
@@ -1,86 +0,0 @@
-/*!
- * Piwik - free/libre analytics platform
- *
- * @link http://piwik.org
- * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
- */
-
-/**
- * Usage:
- * <div piwik-siteselector>
- *
- * More advanced example
- * <div piwik-siteselector
- *      show-selected-site="true" show-all-sites-item="true" switch-site-on-select="true"
- *      all-sites-location="top|bottom" all-sites-text="test" show-selected-site="true"
- *      show-all-sites-item="true">
- *
- * Within a form
- * <div piwik-siteselector input-name="siteId">
- *
- * Events:
- * Triggers a `change` event on any change
- * <div piwik-siteselector id="mySelector">
- * $('#mySelector').on('change', function (event) { event.id/event.name })
- */
-angular.module('piwikApp').directive('piwikSiteselector', function($document, piwik, $filter){
-    var defaults = {
-        name: '',
-        siteid: piwik.idSite,
-        sitename: piwik.siteName,
-        allSitesLocation: 'bottom',
-        allSitesText: $filter('translate')('General_MultiSitesSummary'),
-        showSelectedSite: 'false',
-        showAllSitesItem: 'true',
-        switchSiteOnSelect: 'true'
-    };
-
-    return {
-        restrict: 'A',
-        scope: {
-            showSelectedSite: '=',
-            showAllSitesItem: '=',
-            switchSiteOnSelect: '=',
-            inputName: '@name',
-            allSitesText: '@',
-            allSitesLocation: '@'
-        },
-        require: "?ngModel",
-        templateUrl: 'plugins/CoreHome/angularjs/siteselector/siteselector.html?cb=' + piwik.cacheBuster,
-        controller: 'SiteSelectorController',
-        compile: function (element, attrs) {
-
-            for (var index in defaults) {
-               if (attrs[index] === undefined) {
-                   attrs[index] = defaults[index];
-               }
-            }
-
-            return function (scope, element, attrs, ngModel) {
-                scope.selectedSite = {id: attrs.siteid, name: attrs.sitename};
-                scope.model.loadInitialSites();
-
-                if (ngModel) {
-                    ngModel.$setViewValue(scope.selectedSite);
-                }
-
-                scope.$watch('selectedSite.id', function (newValue, oldValue, scope) {
-                    if (newValue != oldValue) {
-                        element.attr('siteid', newValue);
-                        element.trigger('change', scope.selectedSite);
-                    }
-                });
-
-                scope.$watch('selectedSite', function (newValue) {
-                    if (ngModel) {
-                        ngModel.$setViewValue(newValue);
-                    }
-                });
-
-                scope.$watch('view.showSitesList', function (newValue) {
-                    element.toggleClass('expanded', !! newValue);
-                });
-            };
-        }
-    };
-});
\ No newline at end of file
diff --git a/plugins/CoreHome/angularjs/siteselector/siteselector-model.js b/plugins/CoreHome/angularjs/siteselector/siteselector-model.js
deleted file mode 100644
index 664eaf4ac8339c4632afd5539baf6b56207f0a8b..0000000000000000000000000000000000000000
--- a/plugins/CoreHome/angularjs/siteselector/siteselector-model.js
+++ /dev/null
@@ -1,86 +0,0 @@
-/*!
- * Piwik - free/libre analytics platform
- *
- * @link http://piwik.org
- * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
- */
-
-angular.module('piwikApp').factory('siteSelectorModel', function (piwikApi, $filter, piwik) {
-
-    var model = {};
-    model.sites = [];
-    model.hasMultipleWebsites = false;
-    model.isLoading = false;
-    model.firstSiteName = '';
-
-    var initialSites = null;
-
-    model.updateWebsitesList = function (sites) {
-
-        if (!sites || !sites.length) {
-            model.sites = [];
-            return [];
-        }
-
-        angular.forEach(sites, function (site) {
-            if (site.group) site.name = '[' + site.group + '] ' + site.name;
-        });
-
-        model.sites = $filter('orderBy')(sites, '+name');
-
-        if (!model.firstSiteName) {
-            model.firstSiteName = model.sites[0].name;
-        }
-
-        model.hasMultipleWebsites = model.hasMultipleWebsites || sites.length > 1;
-
-        return model.sites;
-    };
-
-    model.searchSite = function (term) {
-
-        if (!term) {
-            model.loadInitialSites();
-            return;
-        }
-
-        if (model.isLoading) {
-            model.currentRequest.abort();
-        }
-
-        model.isLoading = true;
-
-        model.currentRequest = piwikApi.fetch({
-            method: 'SitesManager.getPatternMatchSites',
-            pattern: term
-        }).then(function (response) {
-            return model.updateWebsitesList(response);
-        })['finally'](function () {    // .finally() is not IE8 compatible see https://github.com/angular/angular.js/commit/f078762d48d0d5d9796dcdf2cb0241198677582c
-            model.isLoading = false;
-            model.currentRequest = null;
-        });
-
-        return model.currentRequest;
-    };
-
-    model.loadSite = function (idsite) {
-        if (idsite == 'all') {
-            piwik.broadcast.propagateNewPage('module=MultiSites&action=index');
-        } else {
-            piwik.broadcast.propagateNewPage('segment=&idSite=' + idsite, false);
-        }
-    };
-
-    model.loadInitialSites = function () {
-        if (initialSites) {
-            model.sites = initialSites;
-            return;
-        }
-
-        this.searchSite('%').then(function (websites) {
-            initialSites = websites;
-        });
-    };
-
-    return model;
-});
diff --git a/plugins/CoreHome/angularjs/siteselector/siteselector-model.service.js b/plugins/CoreHome/angularjs/siteselector/siteselector-model.service.js
new file mode 100644
index 0000000000000000000000000000000000000000..055777f367c90ea01bf93150417d3331f69bccef
--- /dev/null
+++ b/plugins/CoreHome/angularjs/siteselector/siteselector-model.service.js
@@ -0,0 +1,96 @@
+/*!
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+(function () {
+    angular.module('piwikApp').factory('siteSelectorModel', siteSelectorModel);
+
+    siteSelectorModel.$inject = ['piwikApi', '$filter', 'piwik'];
+
+    function siteSelectorModel(piwikApi, $filter, piwik) {
+
+        var initialSites = null;
+
+        var model = {
+            sites : [],
+            hasMultipleWebsites : false,
+            isLoading : false,
+            firstSiteName : '',
+            updateWebsitesList: updateWebsitesList,
+            searchSite: searchSite,
+            loadSite: loadSite,
+            loadInitialSites: loadInitialSites
+        };
+
+        return model;
+
+        function updateWebsitesList(sites) {
+
+            if (!sites || !sites.length) {
+                model.sites = [];
+                return [];
+            }
+
+            angular.forEach(sites, function (site) {
+                if (site.group) site.name = '[' + site.group + '] ' + site.name;
+            });
+
+            model.sites = $filter('orderBy')(sites, '+name');
+
+            if (!model.firstSiteName) {
+                model.firstSiteName = model.sites[0].name;
+            }
+
+            model.hasMultipleWebsites = model.hasMultipleWebsites || sites.length > 1;
+
+            return model.sites;
+        };
+
+        function searchSite(term) {
+
+            if (!term) {
+                loadInitialSites();
+                return;
+            }
+
+            if (model.isLoading) {
+                model.currentRequest.abort();
+            }
+
+            model.isLoading = true;
+
+            model.currentRequest = piwikApi.fetch({
+                method: 'SitesManager.getPatternMatchSites',
+                pattern: term
+            }).then(function (response) {
+                return updateWebsitesList(response);
+            })['finally'](function () {    // .finally() is not IE8 compatible see https://github.com/angular/angular.js/commit/f078762d48d0d5d9796dcdf2cb0241198677582c
+                model.isLoading = false;
+                model.currentRequest = null;
+            });
+
+            return model.currentRequest;
+        };
+
+        function loadSite(idsite) {
+            if (idsite == 'all') {
+                piwik.broadcast.propagateNewPage('module=MultiSites&action=index');
+            } else {
+                piwik.broadcast.propagateNewPage('segment=&idSite=' + idsite, false);
+            }
+        };
+
+        function loadInitialSites() {
+            if (initialSites) {
+                model.sites = initialSites;
+                return;
+            }
+
+            searchSite('%').then(function (websites) {
+                initialSites = websites;
+            });
+        };
+    }
+})();
\ No newline at end of file
diff --git a/plugins/CoreHome/angularjs/siteselector/siteselector.controller.js b/plugins/CoreHome/angularjs/siteselector/siteselector.controller.js
new file mode 100644
index 0000000000000000000000000000000000000000..1a5466900583f2c0b4e99076ced6052dfa07d769
--- /dev/null
+++ b/plugins/CoreHome/angularjs/siteselector/siteselector.controller.js
@@ -0,0 +1,51 @@
+/*!
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+(function () {
+    angular.module('piwikApp').controller('SiteSelectorController', SiteSelectorController);
+
+    SiteSelectorController.$inject = ['$scope', 'siteSelectorModel', 'piwik', 'AUTOCOMPLETE_MIN_SITES'];
+
+    function SiteSelectorController($scope, siteSelectorModel, piwik, AUTOCOMPLETE_MIN_SITES){
+
+        $scope.model = siteSelectorModel;
+
+        $scope.autocompleteMinSites = AUTOCOMPLETE_MIN_SITES;
+        $scope.selectedSite = {id: '', name: ''};
+        $scope.activeSiteId = piwik.idSite;
+        $scope.selectedSiteNameHtml = '';
+
+        $scope.switchSite = function (site) {
+            $scope.selectedSite = {id: site.idsite, name: site.name};
+
+            if (!$scope.switchSiteOnSelect || $scope.activeSiteId == site.idsite) {
+                return;
+            }
+
+            $scope.model.loadSite(site.idsite);
+        };
+
+        $scope.getUrlAllSites = function () {
+            var newParameters = 'module=MultiSites&action=index';
+            return piwik.helper.getCurrentQueryStringWithParametersModified(newParameters);
+        };
+        $scope.getUrlForSiteId = function (idSite) {
+            var idSiteParam   = 'idSite=' + idSite;
+            var newParameters = 'segment=&' + idSiteParam;
+            var hash = piwik.broadcast.isHashExists() ? piwik.broadcast.getHashFromUrl() : "";
+            return piwik.helper.getCurrentQueryStringWithParametersModified(newParameters) +
+            '#' + piwik.helper.getQueryStringWithParametersModified(hash.substring(1), newParameters);
+        };
+
+        $scope.$watch('selectedSite', function (site) {
+            $scope.selectedSiteNameHtml = site.name.replace(/[\u0000-\u2666]/g, function(c) {
+                return '&#'+c.charCodeAt(0)+';';
+            });
+        });
+
+        siteSelectorModel.loadInitialSites();
+    }
+})();
\ No newline at end of file
diff --git a/plugins/CoreHome/angularjs/siteselector/siteselector.html b/plugins/CoreHome/angularjs/siteselector/siteselector.directive.html
similarity index 100%
rename from plugins/CoreHome/angularjs/siteselector/siteselector.html
rename to plugins/CoreHome/angularjs/siteselector/siteselector.directive.html
diff --git a/plugins/CoreHome/angularjs/siteselector/siteselector.directive.js b/plugins/CoreHome/angularjs/siteselector/siteselector.directive.js
new file mode 100644
index 0000000000000000000000000000000000000000..450813f2e14a631705144688f91162769a172af6
--- /dev/null
+++ b/plugins/CoreHome/angularjs/siteselector/siteselector.directive.js
@@ -0,0 +1,92 @@
+/*!
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+/**
+ * Usage:
+ * <div piwik-siteselector>
+ *
+ * More advanced example
+ * <div piwik-siteselector
+ *      show-selected-site="true" show-all-sites-item="true" switch-site-on-select="true"
+ *      all-sites-location="top|bottom" all-sites-text="test" show-selected-site="true"
+ *      show-all-sites-item="true">
+ *
+ * Within a form
+ * <div piwik-siteselector input-name="siteId">
+ *
+ * Events:
+ * Triggers a `change` event on any change
+ * <div piwik-siteselector id="mySelector">
+ * $('#mySelector').on('change', function (event) { event.id/event.name })
+ */
+(function () {
+    angular.module('piwikApp').directive('piwikSiteselector', piwikSiteselector);
+
+    piwikSiteselector.$inject = ['$document', 'piwik', '$filter'];
+
+    function piwikSiteselector($document, piwik, $filter){
+        var defaults = {
+            name: '',
+            siteid: piwik.idSite,
+            sitename: piwik.siteName,
+            allSitesLocation: 'bottom',
+            allSitesText: $filter('translate')('General_MultiSitesSummary'),
+            showSelectedSite: 'false',
+            showAllSitesItem: 'true',
+            switchSiteOnSelect: 'true'
+        };
+
+        return {
+            restrict: 'A',
+            scope: {
+                showSelectedSite: '=',
+                showAllSitesItem: '=',
+                switchSiteOnSelect: '=',
+                inputName: '@name',
+                allSitesText: '@',
+                allSitesLocation: '@'
+            },
+            require: "?ngModel",
+            templateUrl: 'plugins/CoreHome/angularjs/siteselector/siteselector.directive.html?cb=' + piwik.cacheBuster,
+            controller: 'SiteSelectorController',
+            compile: function (element, attrs) {
+
+                for (var index in defaults) {
+                    if (attrs[index] === undefined) {
+                        attrs[index] = defaults[index];
+                    }
+                }
+
+                return function (scope, element, attrs, ngModel) {
+                    scope.selectedSite = {id: attrs.siteid, name: attrs.sitename};
+                    scope.model.loadInitialSites();
+
+                    if (ngModel) {
+                        ngModel.$setViewValue(scope.selectedSite);
+                    }
+
+                    scope.$watch('selectedSite.id', function (newValue, oldValue, scope) {
+                        if (newValue != oldValue) {
+                            element.attr('siteid', newValue);
+                            element.trigger('change', scope.selectedSite);
+                        }
+                    });
+
+                    scope.$watch('selectedSite', function (newValue) {
+                        if (ngModel) {
+                            ngModel.$setViewValue(newValue);
+                        }
+                    });
+
+                    scope.$watch('view.showSitesList', function (newValue) {
+                        element.toggleClass('expanded', !! newValue);
+                    });
+                };
+            }
+        };
+    }
+})();
\ No newline at end of file
diff --git a/plugins/CoreHome/angularjs/siteselector/siteselector.less b/plugins/CoreHome/angularjs/siteselector/siteselector.directive.less
similarity index 100%
rename from plugins/CoreHome/angularjs/siteselector/siteselector.less
rename to plugins/CoreHome/angularjs/siteselector/siteselector.directive.less
diff --git a/plugins/CoreUpdater/templates/layout.twig b/plugins/CoreUpdater/templates/layout.twig
index 913003619af857f09f121bfd8110949e86a618bd..5c29c59304c0fd2094f8ae232295fddd6b41b147 100644
--- a/plugins/CoreUpdater/templates/layout.twig
+++ b/plugins/CoreUpdater/templates/layout.twig
@@ -25,12 +25,12 @@
     <script type="text/javascript" src="libs/angularjs/angular-animate.min.js"></script>
     <script type="text/javascript" src="libs/angularjs/angular-cookies.min.js"></script>
     <script type="text/javascript" src="libs/angularjs/ngDialog/js/ngDialog.min.js"></script>
-    <script type="text/javascript" src="plugins/CoreHome/angularjs/common/services/service.js"></script>
-    <script type="text/javascript" src="plugins/CoreHome/angularjs/common/filters/filter.js"></script>
+    <script type="text/javascript" src="plugins/CoreHome/angularjs/common/services/service.module.js"></script>
+    <script type="text/javascript" src="plugins/CoreHome/angularjs/common/filters/filter.module.js"></script>
     <script type="text/javascript" src="plugins/CoreHome/angularjs/common/filters/translate.js"></script>
-    <script type="text/javascript" src="plugins/CoreHome/angularjs/common/directives/directive.js"></script>
+    <script type="text/javascript" src="plugins/CoreHome/angularjs/common/directives/directive.module.js"></script>
     <script type="text/javascript" src="plugins/CoreHome/angularjs/common/directives/focus-anywhere-but-here.js"></script>
-    <script type="text/javascript" src="plugins/CoreHome/angularjs/piwikAppConfig.js"></script>
+    <script type="text/javascript" src="plugins/CoreHome/angularjs/piwikApp.config.js"></script>
     <script type="text/javascript" src="plugins/CoreHome/angularjs/piwikApp.js"></script>
     <script type="text/javascript" src="plugins/Installation/javascripts/installation.js"></script>
 
diff --git a/plugins/Feedback/Feedback.php b/plugins/Feedback/Feedback.php
index 7180568089169a32b5ab0fc750c200810ae082e3..0bf58287a89de19a2a1aea73e01c833b22e68120 100644
--- a/plugins/Feedback/Feedback.php
+++ b/plugins/Feedback/Feedback.php
@@ -29,15 +29,14 @@ class Feedback extends \Piwik\Plugin
     public function getStylesheetFiles(&$stylesheets)
     {
         $stylesheets[] = "plugins/Feedback/stylesheets/feedback.less";
-
-        $stylesheets[] = "plugins/Feedback/angularjs/ratefeature/ratefeature.less";
+        $stylesheets[] = "plugins/Feedback/angularjs/ratefeature/ratefeature.directive.less";
     }
 
     public function getJsFiles(&$jsFiles)
     {
-        $jsFiles[] = "plugins/Feedback/angularjs/ratefeature/ratefeature-model.js";
-        $jsFiles[] = "plugins/Feedback/angularjs/ratefeature/ratefeature-controller.js";
-        $jsFiles[] = "plugins/Feedback/angularjs/ratefeature/ratefeature-directive.js";
+        $jsFiles[] = "plugins/Feedback/angularjs/ratefeature/ratefeature-model.service.js";
+        $jsFiles[] = "plugins/Feedback/angularjs/ratefeature/ratefeature.controller.js";
+        $jsFiles[] = "plugins/Feedback/angularjs/ratefeature/ratefeature.directive.js";
     }
 
     public function getClientSideTranslationKeys(&$translationKeys)
diff --git a/plugins/Feedback/angularjs/ratefeature/ratefeature-controller.js b/plugins/Feedback/angularjs/ratefeature/ratefeature-controller.js
deleted file mode 100644
index b4dbd1a70a6fa4a1f7e3a8a1d9e707f901ae6af4..0000000000000000000000000000000000000000
--- a/plugins/Feedback/angularjs/ratefeature/ratefeature-controller.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/*!
- * Piwik - free/libre analytics platform
- *
- * @link http://piwik.org
- * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
- */
-
-angular.module('piwikApp').controller('RateFeatureController', function($scope, rateFeatureModel, $filter){
-
-    $scope.dislikeFeature = function () {
-        $scope.like = false;
-    };
-
-    $scope.likeFeature = function () {
-        $scope.like = true;
-    };
-
-    $scope.sendFeedback = function (message) {
-        rateFeatureModel.sendFeedbackForFeature($scope.title, $scope.like, message);
-        $scope.ratingDone = true;
-        // alert($filter('translate')('Feedback_ThankYou'));
-    };
-});
diff --git a/plugins/Feedback/angularjs/ratefeature/ratefeature-directive.js b/plugins/Feedback/angularjs/ratefeature/ratefeature-directive.js
deleted file mode 100644
index c5f4c32a58a42e9401cb5f2c2af9d2a5c0ddb582..0000000000000000000000000000000000000000
--- a/plugins/Feedback/angularjs/ratefeature/ratefeature-directive.js
+++ /dev/null
@@ -1,22 +0,0 @@
-/*!
- * Piwik - free/libre analytics platform
- *
- * @link http://piwik.org
- * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
- */
-
-/**
- * Usage:
- * <div piwik-rate-feature title="My Feature Name">
- */
-angular.module('piwikApp').directive('piwikRateFeature', function($document, piwik, $filter){
-
-    return {
-        restrict: 'A',
-        scope: {
-            title: '@'
-        },
-        templateUrl: 'plugins/Feedback/angularjs/ratefeature/ratefeature.html?cb=' + piwik.cacheBuster,
-        controller: 'RateFeatureController'
-    };
-});
\ No newline at end of file
diff --git a/plugins/Feedback/angularjs/ratefeature/ratefeature-model.js b/plugins/Feedback/angularjs/ratefeature/ratefeature-model.js
deleted file mode 100644
index b2c9f9009c27cab385ae0c5768ad73d51ad1047b..0000000000000000000000000000000000000000
--- a/plugins/Feedback/angularjs/ratefeature/ratefeature-model.js
+++ /dev/null
@@ -1,22 +0,0 @@
-/*!
- * Piwik - free/libre analytics platform
- *
- * @link http://piwik.org
- * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
- */
-
-angular.module('piwikApp').factory('rateFeatureModel', function (piwikApi) {
-
-    var model = {};
-
-    model.sendFeedbackForFeature = function (featureName, like, message) {
-        return piwikApi.fetch({
-            method: 'Feedback.sendFeedbackForFeature',
-            featureName: featureName,
-            like: like ? '1' : '0',
-            message: message + ''
-        });
-    };
-
-    return model;
-});
diff --git a/plugins/Feedback/angularjs/ratefeature/ratefeature-model.service.js b/plugins/Feedback/angularjs/ratefeature/ratefeature-model.service.js
new file mode 100644
index 0000000000000000000000000000000000000000..13141b57db97d47ba8bbde89fda00d4619dbee33
--- /dev/null
+++ b/plugins/Feedback/angularjs/ratefeature/ratefeature-model.service.js
@@ -0,0 +1,29 @@
+/*!
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+(function () {
+    angular.module('piwikApp').factory('rateFeatureModel', rateFeatureModel);
+
+    rateFeatureModel.$inject = ['piwikApi'];
+
+    function rateFeatureModel(piwikApi) {
+
+        return {
+            sendFeedbackForFeature: sendFeedbackForFeature
+        };
+
+        function sendFeedbackForFeature (featureName, like, message) {
+            return piwikApi.fetch({
+                method: 'Feedback.sendFeedbackForFeature',
+                featureName: featureName,
+                like: like ? '1' : '0',
+                message: message + ''
+            });
+        };
+
+    }
+})();
diff --git a/plugins/Feedback/angularjs/ratefeature/ratefeature.controller.js b/plugins/Feedback/angularjs/ratefeature/ratefeature.controller.js
new file mode 100644
index 0000000000000000000000000000000000000000..ccee1fb0e81bfcaca1463fda629a8591fdffd348
--- /dev/null
+++ b/plugins/Feedback/angularjs/ratefeature/ratefeature.controller.js
@@ -0,0 +1,27 @@
+/*!
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+(function () {
+    angular.module('piwikApp').controller('RateFeatureController', RateFeatureController);
+
+    RateFeatureController.$inject = ['$scope', 'rateFeatureModel'];
+
+    function RateFeatureController($scope, rateFeatureModel){
+
+        $scope.dislikeFeature = function () {
+            $scope.like = false;
+        };
+
+        $scope.likeFeature = function () {
+            $scope.like = true;
+        };
+
+        $scope.sendFeedback = function (message) {
+            rateFeatureModel.sendFeedbackForFeature($scope.title, $scope.like, message);
+            $scope.ratingDone = true;
+        };
+    }
+})();
diff --git a/plugins/Feedback/angularjs/ratefeature/ratefeature.html b/plugins/Feedback/angularjs/ratefeature/ratefeature.directive.html
similarity index 100%
rename from plugins/Feedback/angularjs/ratefeature/ratefeature.html
rename to plugins/Feedback/angularjs/ratefeature/ratefeature.directive.html
diff --git a/plugins/Feedback/angularjs/ratefeature/ratefeature.directive.js b/plugins/Feedback/angularjs/ratefeature/ratefeature.directive.js
new file mode 100644
index 0000000000000000000000000000000000000000..6ec2768abe6d6079cb5afff2287c39e9fdbcb18a
--- /dev/null
+++ b/plugins/Feedback/angularjs/ratefeature/ratefeature.directive.js
@@ -0,0 +1,28 @@
+/*!
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+/**
+ * Usage:
+ * <div piwik-rate-feature title="My Feature Name">
+ */
+(function () {
+    angular.module('piwikApp').directive('piwikRateFeature', piwikRateFeature);
+
+    piwikRateFeature.$inject = ['piwik'];
+
+    function piwikRateFeature(piwik){
+
+        return {
+            restrict: 'A',
+            scope: {
+                title: '@'
+            },
+            templateUrl: 'plugins/Feedback/angularjs/ratefeature/ratefeature.directive.html?cb=' + piwik.cacheBuster,
+            controller: 'RateFeatureController'
+        };
+    }
+})();
\ No newline at end of file
diff --git a/plugins/Feedback/angularjs/ratefeature/ratefeature.less b/plugins/Feedback/angularjs/ratefeature/ratefeature.directive.less
similarity index 100%
rename from plugins/Feedback/angularjs/ratefeature/ratefeature.less
rename to plugins/Feedback/angularjs/ratefeature/ratefeature.directive.less
diff --git a/plugins/Installation/templates/layout.twig b/plugins/Installation/templates/layout.twig
index 012eca5fa40a03f662a87a5833a96a48b5008885..e840563bf12e14e17718e6f983d303f7fda299a0 100644
--- a/plugins/Installation/templates/layout.twig
+++ b/plugins/Installation/templates/layout.twig
@@ -16,12 +16,12 @@
     <script type="text/javascript" src="libs/angularjs/angular-animate.min.js"></script>
     <script type="text/javascript" src="libs/angularjs/angular-cookies.min.js"></script>
     <script type="text/javascript" src="libs/angularjs/ngDialog/js/ngDialog.min.js"></script>
-    <script type="text/javascript" src="plugins/CoreHome/angularjs/common/services/service.js"></script>
-    <script type="text/javascript" src="plugins/CoreHome/angularjs/common/filters/filter.js"></script>
+    <script type="text/javascript" src="plugins/CoreHome/angularjs/common/services/service.module.js"></script>
+    <script type="text/javascript" src="plugins/CoreHome/angularjs/common/filters/filter.module.js"></script>
     <script type="text/javascript" src="plugins/CoreHome/angularjs/common/filters/translate.js"></script>
-    <script type="text/javascript" src="plugins/CoreHome/angularjs/common/directives/directive.js"></script>
+    <script type="text/javascript" src="plugins/CoreHome/angularjs/common/directives/directive.module.js"></script>
     <script type="text/javascript" src="plugins/CoreHome/angularjs/common/directives/focus-anywhere-but-here.js"></script>
-    <script type="text/javascript" src="plugins/CoreHome/angularjs/piwikAppConfig.js"></script>
+    <script type="text/javascript" src="plugins/CoreHome/angularjs/piwikApp.config.js"></script>
     <script type="text/javascript" src="plugins/CoreHome/angularjs/piwikApp.js"></script>
     <script type="text/javascript" src="plugins/Installation/javascripts/installation.js"></script>
 
diff --git a/plugins/LanguagesManager/LanguagesManager.php b/plugins/LanguagesManager/LanguagesManager.php
index ff53082736a839cbe92400b4c8ddb0e6945ce68f..66fba008e59e361ededd3871e22c146f8ac41c04 100644
--- a/plugins/LanguagesManager/LanguagesManager.php
+++ b/plugins/LanguagesManager/LanguagesManager.php
@@ -46,9 +46,9 @@ class LanguagesManager extends \Piwik\Plugin
 
     public function getJsFiles(&$jsFiles)
     {
-        $jsFiles[] = "plugins/LanguagesManager/angularjs/languageselector/languageselector-directive.js";
-        $jsFiles[] = "plugins/LanguagesManager/angularjs/translationsearch/translationsearch-controller.js";
-        $jsFiles[] = "plugins/LanguagesManager/angularjs/translationsearch/translationsearch-directive.js";
+        $jsFiles[] = "plugins/LanguagesManager/angularjs/languageselector/languageselector.directive.js";
+        $jsFiles[] = "plugins/LanguagesManager/angularjs/translationsearch/translationsearch.controller.js";
+        $jsFiles[] = "plugins/LanguagesManager/angularjs/translationsearch/translationsearch.directive.js";
     }
 
     /**
@@ -60,8 +60,8 @@ class LanguagesManager extends \Piwik\Plugin
     {
         // piwik object & scripts aren't loaded in 'other' topbars
         $str .= "<script type='text/javascript'>if (!window.piwik) window.piwik={};</script>";
-        $str .= "<script type='text/javascript' src='plugins/CoreHome/angularjs/menudropdown/menudropdown-directive.js'></script>";
-        $str .= "<script type='text/javascript' src='plugins/LanguagesManager/angularjs/languageselector/languageselector-directive.js'></script>";
+        $str .= "<script type='text/javascript' src='plugins/CoreHome/angularjs/menudropdown/menudropdown.directive.js'></script>";
+        $str .= "<script type='text/javascript' src='plugins/LanguagesManager/angularjs/languageselector/languageselector.directive.js'></script>";
         $str .= $this->getLanguagesSelector();
     }
 
diff --git a/plugins/LanguagesManager/angularjs/languageselector/languageselector-directive.js b/plugins/LanguagesManager/angularjs/languageselector/languageselector-directive.js
deleted file mode 100644
index f50298330e66bd5458bc5f9d3518fc4428558e3b..0000000000000000000000000000000000000000
--- a/plugins/LanguagesManager/angularjs/languageselector/languageselector-directive.js
+++ /dev/null
@@ -1,32 +0,0 @@
-/*!
- * Piwik - free/libre analytics platform
- *
- * @link http://piwik.org
- * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
- */
-
-/**
- * Usage:
- * <div class="languageSelection">
- * </div>
- */
-angular.module('piwikApp').directive('languageSelection', function() {
-
-    return {
-        restrict: 'C',
-        link: function(scope, element, attr, ctrl) {
-
-            function postLanguageChange () {
-                var value = $(this).attr('value');
-                if (value) {
-                    element.find('#language').val(value).parents('form').submit();
-                }
-            }
-
-            element.on('click', 'a[value]', postLanguageChange);
-            scope.$on('$destroy', function() {
-                element.off('click', 'a[value]', postLanguageChange);
-            });
-        }
-    };
-});
\ No newline at end of file
diff --git a/plugins/LanguagesManager/angularjs/languageselector/languageselector.directive.js b/plugins/LanguagesManager/angularjs/languageselector/languageselector.directive.js
new file mode 100644
index 0000000000000000000000000000000000000000..2dec8b24070f3ebb5c0508098a84e8580877ac52
--- /dev/null
+++ b/plugins/LanguagesManager/angularjs/languageselector/languageselector.directive.js
@@ -0,0 +1,36 @@
+/*!
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+/**
+ * Usage:
+ * <div class="languageSelection">
+ * </div>
+ */
+(function () {
+    angular.module('piwikApp').directive('languageSelection', languageSelection);
+
+    function languageSelection() {
+
+        return {
+            restrict: 'C',
+            link: function(scope, element, attr, ctrl) {
+
+                function postLanguageChange () {
+                    var value = $(this).attr('value');
+                    if (value) {
+                        element.find('#language').val(value).parents('form').submit();
+                    }
+                }
+
+                element.on('click', 'a[value]', postLanguageChange);
+                scope.$on('$destroy', function() {
+                    element.off('click', 'a[value]', postLanguageChange);
+                });
+            }
+        };
+    }
+})();
\ No newline at end of file
diff --git a/plugins/LanguagesManager/angularjs/translationsearch/translationsearch-controller.js b/plugins/LanguagesManager/angularjs/translationsearch/translationsearch-controller.js
deleted file mode 100644
index ea9dbc065089e6b13c348f8457f7eab22d7f2f6f..0000000000000000000000000000000000000000
--- a/plugins/LanguagesManager/angularjs/translationsearch/translationsearch-controller.js
+++ /dev/null
@@ -1,21 +0,0 @@
-/*!
- * Piwik - free/libre analytics platform
- *
- * @link http://piwik.org
- * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
- */
-
-angular.module('piwikApp').controller('TranslationSearchController', function($scope, piwikApi) {
-
-    $scope.existingTranslations = [];
-
-    piwikApi.fetch({
-        method: 'LanguagesManager.getTranslationsForLanguage',
-        languageCode: 'en'
-    }).then(function (response) {
-        if (response) {
-            $scope.existingTranslations = response;
-        }
-    });
-
-});
diff --git a/plugins/LanguagesManager/angularjs/translationsearch/translationsearch-directive.js b/plugins/LanguagesManager/angularjs/translationsearch/translationsearch-directive.js
deleted file mode 100644
index 7485ab52cac01ea9e928d6434e7732f44bd3ac70..0000000000000000000000000000000000000000
--- a/plugins/LanguagesManager/angularjs/translationsearch/translationsearch-directive.js
+++ /dev/null
@@ -1,24 +0,0 @@
-/*!
- * Piwik - free/libre analytics platform
- *
- * @link http://piwik.org
- * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
- */
-
-/**
- * Usage:
- *
- * <div piwik-translation-search></div>
- *
- * Will show a text box which allows the user to search for translation keys and actual translations. Currently,
- * only english is supported.
- */
-angular.module('piwikApp').directive('piwikTranslationSearch', function($document, piwik, $filter){
-
-    return {
-        restrict: 'A',
-        scope: {},
-        templateUrl: 'plugins/LanguagesManager/angularjs/translationsearch/translationsearch.html?cb=' + piwik.cacheBuster,
-        controller: 'TranslationSearchController'
-    };
-});
\ No newline at end of file
diff --git a/plugins/LanguagesManager/angularjs/translationsearch/translationsearch.controller.js b/plugins/LanguagesManager/angularjs/translationsearch/translationsearch.controller.js
new file mode 100644
index 0000000000000000000000000000000000000000..860b7e77f099162a27e3ccd854a6b5854612abbe
--- /dev/null
+++ b/plugins/LanguagesManager/angularjs/translationsearch/translationsearch.controller.js
@@ -0,0 +1,29 @@
+/*!
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+(function () {
+    angular.module('piwikApp').controller('TranslationSearchController', TranslationSearchController);
+
+    TranslationSearchController.$inject = ['$scope', 'piwikApi'];
+
+    function TranslationSearchController($scope, piwikApi) {
+
+        $scope.existingTranslations = [];
+
+        fetchTranslations();
+
+        function fetchTranslations() {
+            piwikApi.fetch({
+                method: 'LanguagesManager.getTranslationsForLanguage',
+                languageCode: 'en'
+            }).then(function (response) {
+                if (response) {
+                    $scope.existingTranslations = response;
+                }
+            });
+        }
+    }
+})();
\ No newline at end of file
diff --git a/plugins/LanguagesManager/angularjs/translationsearch/translationsearch.html b/plugins/LanguagesManager/angularjs/translationsearch/translationsearch.directive.html
similarity index 100%
rename from plugins/LanguagesManager/angularjs/translationsearch/translationsearch.html
rename to plugins/LanguagesManager/angularjs/translationsearch/translationsearch.directive.html
diff --git a/plugins/LanguagesManager/angularjs/translationsearch/translationsearch.directive.js b/plugins/LanguagesManager/angularjs/translationsearch/translationsearch.directive.js
new file mode 100644
index 0000000000000000000000000000000000000000..89275c970b37cc606eab17bb2b4fbdf9c67a3503
--- /dev/null
+++ b/plugins/LanguagesManager/angularjs/translationsearch/translationsearch.directive.js
@@ -0,0 +1,30 @@
+/*!
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+/**
+ * Usage:
+ *
+ * <div piwik-translation-search></div>
+ *
+ * Will show a text box which allows the user to search for translation keys and actual translations. Currently,
+ * only english is supported.
+ */
+(function () {
+    angular.module('piwikApp').directive('piwikTranslationSearch', piwikTranslationSearch);
+
+    piwikTranslationSearch.$inject = ['piwik'];
+
+    function piwikTranslationSearch(piwik){
+
+        return {
+            restrict: 'A',
+            scope: {},
+            templateUrl: 'plugins/LanguagesManager/angularjs/translationsearch/translationsearch.directive.html?cb=' + piwik.cacheBuster,
+            controller: 'TranslationSearchController'
+        };
+    }
+})();
\ No newline at end of file
diff --git a/plugins/Morpheus/stylesheets/base.less b/plugins/Morpheus/stylesheets/base.less
index c994155077a23fd9a86c5114012625c2d2c22452..4d7ca9a78815138da90e8051254b5bee0ef3189a 100644
--- a/plugins/Morpheus/stylesheets/base.less
+++ b/plugins/Morpheus/stylesheets/base.less
@@ -23,8 +23,8 @@
 
 @import "uibase/_headerMessage.less";
 
-@import "../../CoreHome/angularjs/siteselector/siteselector.less";
-@import "../../CoreHome/angularjs/menudropdown/menudropdown.less";
+@import "../../CoreHome/angularjs/siteselector/siteselector.directive.less";
+@import "../../CoreHome/angularjs/menudropdown/menudropdown.directive.less";
 
 @import "uibase/_periodSelect.less";
 
diff --git a/plugins/MultiSites/MultiSites.php b/plugins/MultiSites/MultiSites.php
index e0b78fd0274d678377475a0e0960c53558e3280b..22b77063ecb731dc62569e188dc0c7d6f826a2bd 100644
--- a/plugins/MultiSites/MultiSites.php
+++ b/plugins/MultiSites/MultiSites.php
@@ -72,15 +72,15 @@ class MultiSites extends \Piwik\Plugin
 
     public function getJsFiles(&$jsFiles)
     {
-        $jsFiles[] = "plugins/MultiSites/angularjs/dashboard/dashboard-model.js";
-        $jsFiles[] = "plugins/MultiSites/angularjs/dashboard/dashboard-controller.js";
-        $jsFiles[] = "plugins/MultiSites/angularjs/dashboard/dashboard-filter.js";
-        $jsFiles[] = "plugins/MultiSites/angularjs/dashboard/dashboard-directive.js";
-        $jsFiles[] = "plugins/MultiSites/angularjs/site/site-directive.js";
+        $jsFiles[] = "plugins/MultiSites/angularjs/dashboard/dashboard-model.service.js";
+        $jsFiles[] = "plugins/MultiSites/angularjs/dashboard/dashboard.controller.js";
+        $jsFiles[] = "plugins/MultiSites/angularjs/dashboard/dashboard-group.filter.js";
+        $jsFiles[] = "plugins/MultiSites/angularjs/dashboard/dashboard.directive.js";
+        $jsFiles[] = "plugins/MultiSites/angularjs/site/site.directive.js";
     }
 
     public function getStylesheetFiles(&$stylesheets)
     {
-        $stylesheets[] = "plugins/MultiSites/angularjs/dashboard/dashboard.less";
+        $stylesheets[] = "plugins/MultiSites/angularjs/dashboard/dashboard.directive.less";
     }
 }
diff --git a/plugins/MultiSites/angularjs/dashboard/dashboard-controller.js b/plugins/MultiSites/angularjs/dashboard/dashboard-controller.js
deleted file mode 100644
index e38abbbdfc7d0edf5977fb12a9267ff75184ff7a..0000000000000000000000000000000000000000
--- a/plugins/MultiSites/angularjs/dashboard/dashboard-controller.js
+++ /dev/null
@@ -1,34 +0,0 @@
-/*!
- * Piwik - free/libre analytics platform
- *
- * @link http://piwik.org
- * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
- */
-
-angular.module('piwikApp').controller('MultiSitesDashboardController', function($scope, piwik, multisitesDashboardModel){
-
-    $scope.model = multisitesDashboardModel;
-
-    $scope.reverse = true;
-    $scope.predicate = 'nb_visits';
-    $scope.evolutionSelector = 'visits_evolution';
-    $scope.hasSuperUserAccess = piwik.hasSuperUserAccess;
-    $scope.date = piwik.broadcast.getValueFromUrl('date');
-    $scope.url  = piwik.piwik_url;
-    $scope.period = piwik.period;
-
-    $scope.sortBy = function (metric) {
-
-        var reverse = $scope.reverse;
-        if ($scope.predicate == metric) {
-            reverse = !reverse;
-        }
-
-        $scope.predicate = metric;
-        $scope.reverse   = reverse;
-    };
-
-    this.refresh = function (interval) {
-        multisitesDashboardModel.fetchAllSites(interval);
-    };
-});
diff --git a/plugins/MultiSites/angularjs/dashboard/dashboard-directive.js b/plugins/MultiSites/angularjs/dashboard/dashboard-directive.js
deleted file mode 100644
index 6f24c833e118a5728057b58612e40c3550abb97f..0000000000000000000000000000000000000000
--- a/plugins/MultiSites/angularjs/dashboard/dashboard-directive.js
+++ /dev/null
@@ -1,40 +0,0 @@
-/*!
- * Piwik - free/libre analytics platform
- *
- * @link http://piwik.org
- * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
- */
-
-/**
- * Renders the multisites dashboard
- * Example usage:
- *
- * <div piwik-multisites-dashboard
- *      display-revenue-column="true"
- *      show-sparklines="true"
- *      date-sparkline="true"
- *      page-size="50"
- *      auto-refresh-today-report="500" // or 0 to disable
- * ></div>
- */
-angular.module('piwikApp').directive('piwikMultisitesDashboard', function($document, piwik, $filter){
-
-    return {
-        restrict: 'A',
-        scope: {
-            displayRevenueColumn: '@',
-            showSparklines: '@',
-            dateSparkline: '@'
-        },
-        templateUrl: 'plugins/MultiSites/angularjs/dashboard/dashboard.html?cb=' + piwik.cacheBuster,
-        controller: 'MultiSitesDashboardController',
-        link: function (scope, element, attrs, controller) {
-
-            if (attrs.pageSize) {
-                scope.model.pageSize = attrs.pageSize;
-            }
-
-            controller.refresh(attrs.autoRefreshTodayReport);
-        }
-    };
-});
\ No newline at end of file
diff --git a/plugins/MultiSites/angularjs/dashboard/dashboard-filter.js b/plugins/MultiSites/angularjs/dashboard/dashboard-filter.js
deleted file mode 100644
index 1ef4b2fd0d5e7cee10a8edc79a892afe75c2ff83..0000000000000000000000000000000000000000
--- a/plugins/MultiSites/angularjs/dashboard/dashboard-filter.js
+++ /dev/null
@@ -1,63 +0,0 @@
-/*!
- * Piwik - free/libre analytics platform
- *
- * @link http://piwik.org
- * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
- */
-
-/**
- * Filters a given list of websites and groups and makes sure only the websites within a given offset and limit are
- * displayed. It also makes sure sites are displayed under the groups. That means it flattens a structure like this:
- *
- * - website1
- * - website2
- * - website3.sites // this is a group
- *    - website4
- *    - website5
- * - website6
- *
- * to the following structure
- * - website1
- * - website2
- * - website3.sites // this is a group
- * - website4
- * - website5
- * - website6
- */
-angular.module('piwikApp').filter('multiSitesGroupFilter', function() {
-    return function(websites, from, to) {
-        var offsetEnd = parseInt(from, 10) + parseInt(to, 10);
-        var groups    = {};
-
-        var sites = [];
-        for (var index = 0; index < websites.length; index++) {
-            var website = websites[index];
-
-            sites.push(website);
-            if (website.sites && website.sites.length) {
-                groups[website.label] = website;
-                for (var innerIndex = 0; innerIndex < website.sites.length; innerIndex++) {
-                    sites.push(website.sites[innerIndex]);
-                }
-            }
-
-            if (sites.length >= offsetEnd) {
-                break;
-            }
-        }
-
-        // if the first site is a website having a group, then try to find the related group and prepend it to the list
-        // of sites to make sure we always display the name of the group that belongs to a website.
-        var filteredSites = sites.slice(from, offsetEnd);
-
-        if (filteredSites.length && filteredSites[0] && filteredSites[0].group) {
-            var groupName = filteredSites[0].group;
-            if (groups[groupName]) {
-                filteredSites.unshift(groups[groupName]);
-            }
-        }
-
-        return filteredSites;
-    };
-});
-
diff --git a/plugins/MultiSites/angularjs/dashboard/dashboard-group.filter.js b/plugins/MultiSites/angularjs/dashboard/dashboard-group.filter.js
new file mode 100644
index 0000000000000000000000000000000000000000..b8f9040e9b6da0179076c8437fc772752b03c8de
--- /dev/null
+++ b/plugins/MultiSites/angularjs/dashboard/dashboard-group.filter.js
@@ -0,0 +1,67 @@
+/*!
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+/**
+ * Filters a given list of websites and groups and makes sure only the websites within a given offset and limit are
+ * displayed. It also makes sure sites are displayed under the groups. That means it flattens a structure like this:
+ *
+ * - website1
+ * - website2
+ * - website3.sites // this is a group
+ *    - website4
+ *    - website5
+ * - website6
+ *
+ * to the following structure
+ * - website1
+ * - website2
+ * - website3.sites // this is a group
+ * - website4
+ * - website5
+ * - website6
+ */
+(function () {
+    angular.module('piwikApp').filter('multiSitesGroupFilter', multiSitesGroupFilter);
+
+    function multiSitesGroupFilter() {
+        return function(websites, from, to) {
+            var offsetEnd = parseInt(from, 10) + parseInt(to, 10);
+            var groups    = {};
+
+            var sites = [];
+            for (var index = 0; index < websites.length; index++) {
+                var website = websites[index];
+
+                sites.push(website);
+                if (website.sites && website.sites.length) {
+                    groups[website.label] = website;
+                    for (var innerIndex = 0; innerIndex < website.sites.length; innerIndex++) {
+                        sites.push(website.sites[innerIndex]);
+                    }
+                }
+
+                if (sites.length >= offsetEnd) {
+                    break;
+                }
+            }
+
+            // if the first site is a website having a group, then try to find the related group and prepend it to the list
+            // of sites to make sure we always display the name of the group that belongs to a website.
+            var filteredSites = sites.slice(from, offsetEnd);
+
+            if (filteredSites.length && filteredSites[0] && filteredSites[0].group) {
+                var groupName = filteredSites[0].group;
+                if (groups[groupName]) {
+                    filteredSites.unshift(groups[groupName]);
+                }
+            }
+
+            return filteredSites;
+        };
+    }
+})();
+
diff --git a/plugins/MultiSites/angularjs/dashboard/dashboard-model.js b/plugins/MultiSites/angularjs/dashboard/dashboard-model.js
deleted file mode 100644
index f2c46c61d4ffcd00da5c5470b9a77ce90884d7f0..0000000000000000000000000000000000000000
--- a/plugins/MultiSites/angularjs/dashboard/dashboard-model.js
+++ /dev/null
@@ -1,300 +0,0 @@
-/**
- * Model for Multisites Dashboard aka All Websites Dashboard.
- *
- */
-angular.module('piwikApp').factory('multisitesDashboardModel', function (piwikApi, $filter, $timeout) {
-    /**
-     *
-     * this is the list of all available sites. For performance reason this list is different to model.sites. ngRepeat
-     * won't operate on the whole list this way. The allSites array contains websites and groups in the following
-     * structure
-     *
-     * - website1
-     * - website2
-     * - website3.sites = [ // this is a group
-     *    - website4
-     *    - website5
-     * ]
-     * - website6
-     *
-     * This structure allows us to display the sites within a group directly under the group without big logic and also
-     * allows us to calculate the summary for each group easily
-    */
-    var allSitesByGroup = [];
-
-    var model       = {};
-    // those sites are going to be displayed
-    model.sites     = [];
-    model.isLoading = false;
-    model.pageSize  = 5;
-    model.currentPage  = 0;
-    model.totalVisits  = '?';
-    model.totalActions = '?';
-    model.totalRevenue = '?';
-    model.searchTerm   = '';
-    model.lastVisits   = '?';
-    model.lastVisitsDate = '?';
-
-    fetchPreviousSummary();
-
-    // create a new group object which has similar structure than a website
-    function createGroup(name){
-        return {
-            label: name,
-            sites: [],
-            nb_visits: 0,
-            nb_pageviews: 0,
-            revenue: 0,
-            isGroup: true
-        };
-    }
-
-    // create a new group with empty site to make sure we do not change the original group in $allSites
-    function copyGroup(group)
-    {
-        return {
-            label: group.label,
-            sites: [],
-            nb_visits: group.nb_visits,
-            nb_pageviews: group.nb_pageviews,
-            revenue: group.revenue,
-            isGroup: true
-        };
-    }
-
-    function onError () {
-        model.errorLoadingSites = true;
-        model.sites     = [];
-        allSitesByGroup = [];
-    }
-
-    function calculateMetricsForEachGroup(groups)
-    {
-        angular.forEach(groups, function (group) {
-            angular.forEach(group.sites, function (site) {
-                var revenue = 0;
-                if (site.revenue) {
-                    revenue = (site.revenue+'').match(/(\d+\.?\d*)/); // convert $ 0.00 to 0.00 or 5€ to 5
-                }
-
-                group.nb_visits    += parseInt(site.nb_visits, 10);
-                group.nb_pageviews += parseInt(site.nb_pageviews, 10);
-                if (revenue.length) {
-                    group.revenue += parseInt(revenue[0], 10);
-                }
-            });
-        });
-    }
-
-    function createGroupsAndMoveSitesIntoRelatedGroup(allSitesUnordered, reportMetadata)
-    {
-        var sitesByGroup = [];
-        var groups = {};
-
-        // we do 3 things (complete site information, create groups, move sites into group) in one step for
-        // performance reason, there can be > 20k sites
-        angular.forEach(allSitesUnordered, function (site, index) {
-            site.idsite   = reportMetadata[index].idsite;
-            site.group    = reportMetadata[index].group;
-            site.main_url = reportMetadata[index].main_url;
-            // casting evolution to int fixes sorting, see: https://github.com/piwik/piwik/issues/4885
-            site.visits_evolution    = parseInt(site.visits_evolution, 10);
-            site.pageviews_evolution = parseInt(site.pageviews_evolution, 10);
-            site.revenue_evolution   = parseInt(site.revenue_evolution, 10);
-
-            if (site.group) {
-
-                if (!groups[site.group]) {
-                    var group = createGroup(site.group);
-
-                    groups[site.group] = group;
-                    sitesByGroup.push(group);
-                }
-
-                groups[site.group].sites.push(site);
-
-            } else {
-                sitesByGroup.push(site);
-            }
-        });
-
-        calculateMetricsForEachGroup(groups);
-
-        return sitesByGroup;
-    }
-
-    function getSumTotalActions(allSitesUnordered)
-    {
-        var totalActions = 0;
-
-        if (allSitesUnordered && allSitesUnordered.length) {
-            for (var index in allSitesUnordered) {
-                var site = allSitesUnordered[index];
-                if (site && site.nb_pageviews) {
-                    totalActions += parseInt(site.nb_pageviews, 10);
-                }
-            }
-        }
-
-        return totalActions;
-    }
-
-    model.updateWebsitesList = function (processedReport) {
-        if (!processedReport) {
-            onError();
-            return;
-        }
-
-        var allSitesUnordered = processedReport.reportData;
-
-        model.totalActions = getSumTotalActions(allSitesUnordered);
-        model.totalVisits  = processedReport.reportTotal.nb_visits;
-        model.totalRevenue = processedReport.reportTotal.revenue;
-
-        allSitesByGroup = createGroupsAndMoveSitesIntoRelatedGroup(allSitesUnordered, processedReport.reportMetadata);
-
-        if (!allSitesByGroup.length) {
-            return;
-        }
-
-        if (model.searchTerm) {
-            model.searchSite(model.searchTerm);
-        } else {
-            model.sites = allSitesByGroup;
-        }
-    };
-
-    model.getNumberOfFilteredSites = function () {
-        var numSites = model.sites.length;
-
-        var groupNames = {};
-
-        for (var index = 0; index < model.sites.length; index++) {
-            var site = model.sites[index];
-            if (site && site.isGroup) {
-                numSites += site.sites.length;
-            }
-        }
-
-        return numSites;
-    };
-
-    model.getNumberOfPages = function () {
-        return Math.ceil(model.getNumberOfFilteredSites() / model.pageSize - 1);
-    };
-
-    model.getCurrentPagingOffsetStart = function() {
-        return Math.ceil(model.currentPage * model.pageSize);
-    };
-
-    model.getCurrentPagingOffsetEnd = function() {
-        var end = model.getCurrentPagingOffsetStart() + parseInt(model.pageSize, 10);
-        var max = model.getNumberOfFilteredSites();
-        if (end > max) {
-            end = max;
-        }
-        return parseInt(end, 10);
-    };
-
-    model.previousPage = function () {
-        model.currentPage = model.currentPage - 1;
-    };
-
-    model.nextPage = function () {
-        model.currentPage = model.currentPage + 1;
-    };
-
-    function nestedSearch(sitesByGroup, term)
-    {
-        var filteredSites = [];
-
-        term = term.toLowerCase();
-
-        for (var index in sitesByGroup) {
-            var site = sitesByGroup[index];
-            if (site.isGroup) {
-                var matchingSites = nestedSearch(site.sites, term);
-                if (matchingSites.length || (''+site.label).toLowerCase().indexOf(term) > -1) {
-                    var clonedGroup   = copyGroup(site);
-                    clonedGroup.sites = matchingSites;
-                    filteredSites.push(clonedGroup);
-                }
-            } else if ((''+site.label).toLowerCase().indexOf(term) > -1) {
-                filteredSites.push(site);
-            } else if (site.group && (''+site.group).toLowerCase().indexOf(term) > -1) {
-                filteredSites.push(site);
-            }
-        }
-
-        return filteredSites;
-    }
-
-    model.searchSite = function (term) {
-        model.searchTerm  = term;
-        model.currentPage = 0;
-        model.sites       = nestedSearch(allSitesByGroup, term);
-    };
-
-    function fetchPreviousSummary () {
-        piwikApi.fetch({
-            method: 'API.getLastDate'
-        }).then(function (response) {
-            if (response && response.value) {
-                return response.value;
-            }
-        }).then(function (lastDate) {
-            if (!lastDate) {
-                return;
-            }
-
-            model.lastVisitsDate = lastDate;
-
-            return piwikApi.fetch({
-                method: 'API.getProcessedReport',
-                apiModule: 'MultiSites',
-                apiAction: 'getAll',
-                hideMetricsDoc: '1',
-                filter_limit: '0',
-                showColumns: 'label,nb_visits',
-                enhanced: 1,
-                date: lastDate
-            });
-        }).then(function (response) {
-            if (response && response.reportTotal) {
-                model.lastVisits = response.reportTotal.nb_visits;
-            }
-        });
-    }
-
-    model.fetchAllSites = function (refreshInterval) {
-
-        if (model.isLoading) {
-            piwikApi.abort();
-        }
-
-        model.isLoading = true;
-        model.errorLoadingSites = false;
-
-        return piwikApi.fetch({
-            method: 'API.getProcessedReport',
-            apiModule: 'MultiSites',
-            apiAction: 'getAll',
-            hideMetricsDoc: '1',
-            filter_limit: '-1',
-            showColumns: 'label,nb_visits,nb_pageviews,visits_evolution,pageviews_evolution,revenue_evolution,nb_actions,revenue',
-            enhanced: 1
-        }).then(function (response) {
-            model.updateWebsitesList(response);
-        }, onError)['finally'](function () {
-            model.isLoading = false;
-
-            if (refreshInterval && refreshInterval > 0) {
-                $timeout(function () {
-                    model.fetchAllSites(refreshInterval);
-                }, refreshInterval * 1000);
-            }
-        });
-    };
-
-    return model;
-});
diff --git a/plugins/MultiSites/angularjs/dashboard/dashboard-model.service.js b/plugins/MultiSites/angularjs/dashboard/dashboard-model.service.js
new file mode 100644
index 0000000000000000000000000000000000000000..f2261aad1c7e291189a31100a05ffd6428261ba6
--- /dev/null
+++ b/plugins/MultiSites/angularjs/dashboard/dashboard-model.service.js
@@ -0,0 +1,315 @@
+/**
+ * Model for Multisites Dashboard aka All Websites Dashboard.
+ */
+(function () {
+    angular.module('piwikApp').factory('multisitesDashboardModel', multisitesDashboardModel);
+
+    multisitesDashboardModel.$inject = ['piwikApi', '$filter', '$timeout'];
+
+    function multisitesDashboardModel(piwikApi, $filter, $timeout) {
+        /**
+         *
+         * this is the list of all available sites. For performance reason this list is different to model.sites. ngRepeat
+         * won't operate on the whole list this way. The allSites array contains websites and groups in the following
+         * structure
+         *
+         * - website1
+         * - website2
+         * - website3.sites = [ // this is a group
+         *    - website4
+         *    - website5
+         * ]
+         * - website6
+         *
+         * This structure allows us to display the sites within a group directly under the group without big logic and also
+         * allows us to calculate the summary for each group easily
+         */
+        var allSitesByGroup = [];
+
+        // those sites are going to be displayed
+        var model = {
+            sites        : [],
+            isLoading    : false,
+            pageSize     : 5,
+            currentPage  : 0,
+            totalVisits  : '?',
+            totalActions : '?',
+            totalRevenue : '?',
+            searchTerm   : '',
+            lastVisits   : '?',
+            lastVisitsDate : '?',
+            updateWebsitesList: updateWebsitesList,
+            getNumberOfFilteredSites: getNumberOfFilteredSites,
+            getNumberOfPages: getNumberOfPages,
+            getCurrentPagingOffsetStart: getCurrentPagingOffsetStart,
+            getCurrentPagingOffsetEnd: getCurrentPagingOffsetEnd,
+            previousPage: previousPage,
+            nextPage: nextPage,
+            searchSite: searchSite,
+            fetchAllSites: fetchAllSites
+        };
+
+        fetchPreviousSummary();
+
+        return model;
+
+        // create a new group object which has similar structure than a website
+        function createGroup(name){
+            return {
+                label: name,
+                sites: [],
+                nb_visits: 0,
+                nb_pageviews: 0,
+                revenue: 0,
+                isGroup: true
+            };
+        }
+
+        // create a new group with empty site to make sure we do not change the original group in $allSites
+        function copyGroup(group)
+        {
+            return {
+                label: group.label,
+                sites: [],
+                nb_visits: group.nb_visits,
+                nb_pageviews: group.nb_pageviews,
+                revenue: group.revenue,
+                isGroup: true
+            };
+        }
+
+        function onError () {
+            model.errorLoadingSites = true;
+            model.sites     = [];
+            allSitesByGroup = [];
+        }
+
+        function calculateMetricsForEachGroup(groups)
+        {
+            angular.forEach(groups, function (group) {
+                angular.forEach(group.sites, function (site) {
+                    var revenue = 0;
+                    if (site.revenue) {
+                        revenue = (site.revenue+'').match(/(\d+\.?\d*)/); // convert $ 0.00 to 0.00 or 5€ to 5
+                    }
+
+                    group.nb_visits    += parseInt(site.nb_visits, 10);
+                    group.nb_pageviews += parseInt(site.nb_pageviews, 10);
+                    if (revenue.length) {
+                        group.revenue += parseInt(revenue[0], 10);
+                    }
+                });
+            });
+        }
+
+        function createGroupsAndMoveSitesIntoRelatedGroup(allSitesUnordered, reportMetadata)
+        {
+            var sitesByGroup = [];
+            var groups = {};
+
+            // we do 3 things (complete site information, create groups, move sites into group) in one step for
+            // performance reason, there can be > 20k sites
+            angular.forEach(allSitesUnordered, function (site, index) {
+                site.idsite   = reportMetadata[index].idsite;
+                site.group    = reportMetadata[index].group;
+                site.main_url = reportMetadata[index].main_url;
+                // casting evolution to int fixes sorting, see: https://github.com/piwik/piwik/issues/4885
+                site.visits_evolution    = parseInt(site.visits_evolution, 10);
+                site.pageviews_evolution = parseInt(site.pageviews_evolution, 10);
+                site.revenue_evolution   = parseInt(site.revenue_evolution, 10);
+
+                if (site.group) {
+
+                    if (!groups[site.group]) {
+                        var group = createGroup(site.group);
+
+                        groups[site.group] = group;
+                        sitesByGroup.push(group);
+                    }
+
+                    groups[site.group].sites.push(site);
+
+                } else {
+                    sitesByGroup.push(site);
+                }
+            });
+
+            calculateMetricsForEachGroup(groups);
+
+            return sitesByGroup;
+        }
+
+        function getSumTotalActions(allSitesUnordered)
+        {
+            var totalActions = 0;
+
+            if (allSitesUnordered && allSitesUnordered.length) {
+                for (var index in allSitesUnordered) {
+                    var site = allSitesUnordered[index];
+                    if (site && site.nb_pageviews) {
+                        totalActions += parseInt(site.nb_pageviews, 10);
+                    }
+                }
+            }
+
+            return totalActions;
+        }
+
+        function updateWebsitesList(processedReport) {
+            if (!processedReport) {
+                onError();
+                return;
+            }
+
+            var allSitesUnordered = processedReport.reportData;
+
+            model.totalActions = getSumTotalActions(allSitesUnordered);
+            model.totalVisits  = processedReport.reportTotal.nb_visits;
+            model.totalRevenue = processedReport.reportTotal.revenue;
+
+            allSitesByGroup = createGroupsAndMoveSitesIntoRelatedGroup(allSitesUnordered, processedReport.reportMetadata);
+
+            if (!allSitesByGroup.length) {
+                return;
+            }
+
+            if (model.searchTerm) {
+                searchSite(model.searchTerm);
+            } else {
+                model.sites = allSitesByGroup;
+            }
+        }
+
+        function getNumberOfFilteredSites () {
+            var numSites = model.sites.length;
+
+            var groupNames = {};
+
+            for (var index = 0; index < model.sites.length; index++) {
+                var site = model.sites[index];
+                if (site && site.isGroup) {
+                    numSites += site.sites.length;
+                }
+            }
+
+            return numSites;
+        }
+
+        function getNumberOfPages() {
+            return Math.ceil(getNumberOfFilteredSites() / model.pageSize - 1);
+        }
+
+        function getCurrentPagingOffsetStart() {
+            return Math.ceil(model.currentPage * model.pageSize);
+        }
+
+        function getCurrentPagingOffsetEnd() {
+            var end = getCurrentPagingOffsetStart() + parseInt(model.pageSize, 10);
+            var max = getNumberOfFilteredSites();
+            if (end > max) {
+                end = max;
+            }
+            return parseInt(end, 10);
+        }
+
+        function previousPage() {
+            model.currentPage = model.currentPage - 1;
+        }
+
+        function nextPage() {
+            model.currentPage = model.currentPage + 1;
+        }
+
+        function nestedSearch(sitesByGroup, term)
+        {
+            var filteredSites = [];
+
+            term = term.toLowerCase();
+
+            for (var index in sitesByGroup) {
+                var site = sitesByGroup[index];
+                if (site.isGroup) {
+                    var matchingSites = nestedSearch(site.sites, term);
+                    if (matchingSites.length || (''+site.label).toLowerCase().indexOf(term) > -1) {
+                        var clonedGroup   = copyGroup(site);
+                        clonedGroup.sites = matchingSites;
+                        filteredSites.push(clonedGroup);
+                    }
+                } else if ((''+site.label).toLowerCase().indexOf(term) > -1) {
+                    filteredSites.push(site);
+                } else if (site.group && (''+site.group).toLowerCase().indexOf(term) > -1) {
+                    filteredSites.push(site);
+                }
+            }
+
+            return filteredSites;
+        }
+
+        function searchSite (term) {
+            model.searchTerm  = term;
+            model.currentPage = 0;
+            model.sites       = nestedSearch(allSitesByGroup, term);
+        }
+
+        function fetchPreviousSummary () {
+            piwikApi.fetch({
+                method: 'API.getLastDate'
+            }).then(function (response) {
+                if (response && response.value) {
+                    return response.value;
+                }
+            }).then(function (lastDate) {
+                if (!lastDate) {
+                    return;
+                }
+
+                model.lastVisitsDate = lastDate;
+
+                return piwikApi.fetch({
+                    method: 'API.getProcessedReport',
+                    apiModule: 'MultiSites',
+                    apiAction: 'getAll',
+                    hideMetricsDoc: '1',
+                    filter_limit: '0',
+                    showColumns: 'label,nb_visits',
+                    enhanced: 1,
+                    date: lastDate
+                });
+            }).then(function (response) {
+                if (response && response.reportTotal) {
+                    model.lastVisits = response.reportTotal.nb_visits;
+                }
+            });
+        }
+
+        function fetchAllSites(refreshInterval) {
+
+            if (model.isLoading) {
+                piwikApi.abort();
+            }
+
+            model.isLoading = true;
+            model.errorLoadingSites = false;
+
+            return piwikApi.fetch({
+                method: 'API.getProcessedReport',
+                apiModule: 'MultiSites',
+                apiAction: 'getAll',
+                hideMetricsDoc: '1',
+                filter_limit: '-1',
+                showColumns: 'label,nb_visits,nb_pageviews,visits_evolution,pageviews_evolution,revenue_evolution,nb_actions,revenue',
+                enhanced: 1
+            }).then(function (response) {
+                updateWebsitesList(response);
+            }, onError)['finally'](function () {
+                model.isLoading = false;
+
+                if (refreshInterval && refreshInterval > 0) {
+                    $timeout(function () {
+                        fetchAllSites(refreshInterval);
+                    }, refreshInterval * 1000);
+                }
+            });
+        }
+    }
+})();
diff --git a/plugins/MultiSites/angularjs/dashboard/dashboard.controller.js b/plugins/MultiSites/angularjs/dashboard/dashboard.controller.js
new file mode 100644
index 0000000000000000000000000000000000000000..2f28a6e8878c3d4838833d5dcad704d90bf1a83c
--- /dev/null
+++ b/plugins/MultiSites/angularjs/dashboard/dashboard.controller.js
@@ -0,0 +1,39 @@
+/*!
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+(function () {
+    angular.module('piwikApp').controller('MultiSitesDashboardController', MultiSitesDashboardController);
+
+    MultiSitesDashboardController.$inject = ['$scope', 'piwik', 'multisitesDashboardModel'];
+
+    function MultiSitesDashboardController($scope, piwik, multisitesDashboardModel){
+
+        $scope.model = multisitesDashboardModel;
+
+        $scope.reverse = true;
+        $scope.predicate = 'nb_visits';
+        $scope.evolutionSelector = 'visits_evolution';
+        $scope.hasSuperUserAccess = piwik.hasSuperUserAccess;
+        $scope.date = piwik.broadcast.getValueFromUrl('date');
+        $scope.url  = piwik.piwik_url;
+        $scope.period = piwik.period;
+
+        $scope.sortBy = function (metric) {
+
+            var reverse = $scope.reverse;
+            if ($scope.predicate == metric) {
+                reverse = !reverse;
+            }
+
+            $scope.predicate = metric;
+            $scope.reverse   = reverse;
+        };
+
+        this.refresh = function (interval) {
+            multisitesDashboardModel.fetchAllSites(interval);
+        };
+    }
+})();
diff --git a/plugins/MultiSites/angularjs/dashboard/dashboard.html b/plugins/MultiSites/angularjs/dashboard/dashboard.directive.html
similarity index 100%
rename from plugins/MultiSites/angularjs/dashboard/dashboard.html
rename to plugins/MultiSites/angularjs/dashboard/dashboard.directive.html
diff --git a/plugins/MultiSites/angularjs/dashboard/dashboard.directive.js b/plugins/MultiSites/angularjs/dashboard/dashboard.directive.js
new file mode 100644
index 0000000000000000000000000000000000000000..140bda671bba35c755856699abfdb68cc7b9c2e7
--- /dev/null
+++ b/plugins/MultiSites/angularjs/dashboard/dashboard.directive.js
@@ -0,0 +1,46 @@
+/*!
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+/**
+ * Renders the multisites dashboard
+ * Example usage:
+ *
+ * <div piwik-multisites-dashboard
+ *      display-revenue-column="true"
+ *      show-sparklines="true"
+ *      date-sparkline="true"
+ *      page-size="50"
+ *      auto-refresh-today-report="500" // or 0 to disable
+ * ></div>
+ */
+(function () {
+    angular.module('piwikApp').directive('piwikMultisitesDashboard', piwikMultisitesDashboard);
+
+    piwikMultisitesDashboard.$inject = ['piwik'];
+
+    function piwikMultisitesDashboard(piwik){
+
+        return {
+            restrict: 'A',
+            scope: {
+                displayRevenueColumn: '@',
+                showSparklines: '@',
+                dateSparkline: '@'
+            },
+            templateUrl: 'plugins/MultiSites/angularjs/dashboard/dashboard.directive.html?cb=' + piwik.cacheBuster,
+            controller: 'MultiSitesDashboardController',
+            link: function (scope, element, attrs, controller) {
+
+                if (attrs.pageSize) {
+                    scope.model.pageSize = attrs.pageSize;
+                }
+
+                controller.refresh(attrs.autoRefreshTodayReport);
+            }
+        };
+    }
+})();
\ No newline at end of file
diff --git a/plugins/MultiSites/angularjs/dashboard/dashboard.less b/plugins/MultiSites/angularjs/dashboard/dashboard.directive.less
similarity index 100%
rename from plugins/MultiSites/angularjs/dashboard/dashboard.less
rename to plugins/MultiSites/angularjs/dashboard/dashboard.directive.less
diff --git a/plugins/MultiSites/angularjs/site/site-directive.js b/plugins/MultiSites/angularjs/site/site-directive.js
deleted file mode 100644
index 66863d6a065205574af2c8bb26f5596012f14d6a..0000000000000000000000000000000000000000
--- a/plugins/MultiSites/angularjs/site/site-directive.js
+++ /dev/null
@@ -1,54 +0,0 @@
-/*!
- * Piwik - free/libre analytics platform
- *
- * @link http://piwik.org
- * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
- */
-
-/**
- * Renders a single website row, for instance to be used within the MultiSites Dashboard.
- *
- * Usage:
- * <div piwik-multisites-site>
- *     website="{label: 'Name', main_url: 'http://...', idsite: '...'}"
- *     evolution-metric="visits_evolution"
- *     show-sparklines="true"
- *     date-sparkline="2014-01-01,2014-02-02"
- *     display-revenue-column="true"
- *     </div>
- */
-angular.module('piwikApp').directive('piwikMultisitesSite', function($document, piwik, $filter){
-
-    return {
-        restrict: 'AC',
-        replace: true,
-        scope: {
-            website: '=',
-            evolutionMetric: '=',
-            showSparklines: '=',
-            dateSparkline: '=',
-            displayRevenueColumn: '=',
-            metric: '='
-        },
-        templateUrl: 'plugins/MultiSites/angularjs/site/site.html?cb=' + piwik.cacheBuster,
-        controller: function ($scope) {
-
-            $scope.period   = piwik.period;
-            $scope.date     = piwik.broadcast.getValueFromUrl('date');
-
-            this.getWebsite = function () {
-                return $scope.website;
-            };
-
-            $scope.sparklineImage = function(website){
-                var append = '';
-                var token_auth = piwik.broadcast.getValueFromUrl('token_auth');
-                if (token_auth.length) {
-                    append = '&token_auth=' + token_auth;
-                }
-
-                return piwik.piwik_url + '?module=MultiSites&action=getEvolutionGraph&period=' + $scope.period + '&date=' + $scope.dateSparkline + '&evolutionBy=' +$scope.metric + '&columns=' + $scope.metric + '&idSite=' + website.idsite + '&idsite=' + website.idsite + '&viewDataTable=sparkline' + append + '&colors=' + encodeURIComponent(JSON.stringify(piwik.getSparklineColors()));
-            };
-        }
-    };
-});
\ No newline at end of file
diff --git a/plugins/MultiSites/angularjs/site/site.html b/plugins/MultiSites/angularjs/site/site.directive.html
similarity index 100%
rename from plugins/MultiSites/angularjs/site/site.html
rename to plugins/MultiSites/angularjs/site/site.directive.html
diff --git a/plugins/MultiSites/angularjs/site/site.directive.js b/plugins/MultiSites/angularjs/site/site.directive.js
new file mode 100644
index 0000000000000000000000000000000000000000..f0aa88fa235d1c3afcf931b0493ee6d0a831037a
--- /dev/null
+++ b/plugins/MultiSites/angularjs/site/site.directive.js
@@ -0,0 +1,60 @@
+/*!
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+/**
+ * Renders a single website row, for instance to be used within the MultiSites Dashboard.
+ *
+ * Usage:
+ * <div piwik-multisites-site>
+ *     website="{label: 'Name', main_url: 'http://...', idsite: '...'}"
+ *     evolution-metric="visits_evolution"
+ *     show-sparklines="true"
+ *     date-sparkline="2014-01-01,2014-02-02"
+ *     display-revenue-column="true"
+ *     </div>
+ */
+(function () {
+    angular.module('piwikApp').directive('piwikMultisitesSite', piwikMultisitesSite);
+
+    piwikMultisitesSite.$inject = ['piwik'];
+
+    function piwikMultisitesSite(piwik){
+
+        return {
+            restrict: 'AC',
+            replace: true,
+            scope: {
+                website: '=',
+                evolutionMetric: '=',
+                showSparklines: '=',
+                dateSparkline: '=',
+                displayRevenueColumn: '=',
+                metric: '='
+            },
+            templateUrl: 'plugins/MultiSites/angularjs/site/site.directive.html?cb=' + piwik.cacheBuster,
+            controller: function ($scope) {
+
+                $scope.period   = piwik.period;
+                $scope.date     = piwik.broadcast.getValueFromUrl('date');
+
+                this.getWebsite = function () {
+                    return $scope.website;
+                };
+
+                $scope.sparklineImage = function(website){
+                    var append = '';
+                    var token_auth = piwik.broadcast.getValueFromUrl('token_auth');
+                    if (token_auth.length) {
+                        append = '&token_auth=' + token_auth;
+                    }
+
+                    return piwik.piwik_url + '?module=MultiSites&action=getEvolutionGraph&period=' + $scope.period + '&date=' + $scope.dateSparkline + '&evolutionBy=' +$scope.metric + '&columns=' + $scope.metric + '&idSite=' + website.idsite + '&idsite=' + website.idsite + '&viewDataTable=sparkline' + append + '&colors=' + encodeURIComponent(JSON.stringify(piwik.getSparklineColors()));
+                };
+            }
+        };
+    }
+})();
\ No newline at end of file
diff --git a/plugins/SitesManager/SitesManager.php b/plugins/SitesManager/SitesManager.php
index f6eba48b413930faa1e37e23c4603c5685dcfbd1..5b26868c352ae8f383b40ab54eb6fd925d543856 100644
--- a/plugins/SitesManager/SitesManager.php
+++ b/plugins/SitesManager/SitesManager.php
@@ -44,10 +44,15 @@ class SitesManager extends \Piwik\Plugin
      */
     public function getJsFiles(&$jsFiles)
     {
-        $jsFiles[] = "plugins/SitesManager/javascripts/sites-manager-recipes.js";
-        $jsFiles[] = "plugins/SitesManager/javascripts/sites-manager-directives.js";
-        $jsFiles[] = "plugins/SitesManager/javascripts/sites-manager-controller.js";
-        $jsFiles[] = "plugins/SitesManager/javascripts/sites-manager-site-controller.js";
+        $jsFiles[] = "plugins/SitesManager/angularjs/sites-manager/api-helper.service.js";
+        $jsFiles[] = "plugins/SitesManager/angularjs/sites-manager/api-site.service.js";
+        $jsFiles[] = "plugins/SitesManager/angularjs/sites-manager/api-core.service.js";
+        $jsFiles[] = "plugins/SitesManager/angularjs/sites-manager/api-coreadmin.service.js";
+        $jsFiles[] = "plugins/SitesManager/angularjs/sites-manager/multiline-field.directive.js";
+        $jsFiles[] = "plugins/SitesManager/angularjs/sites-manager/edit-trigger.directive.js";
+        $jsFiles[] = "plugins/SitesManager/angularjs/sites-manager/scroll.directive.js";
+        $jsFiles[] = "plugins/SitesManager/angularjs/sites-manager/sites-manager.controller.js";
+        $jsFiles[] = "plugins/SitesManager/angularjs/sites-manager/sites-manager-site.controller.js";
     }
 
     /**
diff --git a/plugins/SitesManager/angularjs/sites-manager/api-core.service.js b/plugins/SitesManager/angularjs/sites-manager/api-core.service.js
new file mode 100644
index 0000000000000000000000000000000000000000..f78d813d6115123181d96ebccc0549059969b154
--- /dev/null
+++ b/plugins/SitesManager/angularjs/sites-manager/api-core.service.js
@@ -0,0 +1,26 @@
+/*!
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+(function () {
+    // can probably be shared
+    angular.module('piwikApp').factory('coreAPI', CoreAPIFactory);
+
+    CoreAPIFactory.$inject = ['sitesManagerApiHelper'];
+
+    function CoreAPIFactory(sitesManagerApiHelper) {
+
+        var api = sitesManagerApiHelper;
+
+        return {
+            getIpFromHeader: getIpFromHeader()
+        };
+
+        function getIpFromHeader() {
+            return api.fetchApi('API.getIpFromHeader', api.valueAdaptor);
+        }
+    }
+
+})();
diff --git a/plugins/SitesManager/angularjs/sites-manager/api-coreadmin.service.js b/plugins/SitesManager/angularjs/sites-manager/api-coreadmin.service.js
new file mode 100644
index 0000000000000000000000000000000000000000..58cb1ed6ff475b8f49ee98a26a7bae59417eed82
--- /dev/null
+++ b/plugins/SitesManager/angularjs/sites-manager/api-coreadmin.service.js
@@ -0,0 +1,25 @@
+/*!
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+(function () {
+    // can probably be shared
+    angular.module('piwikApp').factory('coreAdminAPI', CoreAdminAPIFactory);
+
+    CoreAdminAPIFactory.$inject = ['sitesManagerApiHelper'];
+
+    function CoreAdminAPIFactory(sitesManagerApiHelper) {
+
+        var api = sitesManagerApiHelper;
+
+        return {
+            isPluginActivated: isPluginActivated()
+        };
+
+        function isPluginActivated() {
+            return api.fetchApi('CoreAdminHome.isPluginActivated', api.valueAdaptor);
+        }
+    }
+})();
diff --git a/plugins/SitesManager/angularjs/sites-manager/api-helper.service.js b/plugins/SitesManager/angularjs/sites-manager/api-helper.service.js
new file mode 100644
index 0000000000000000000000000000000000000000..65cc0e236879b49a29a97debe3b5281aef86bc19
--- /dev/null
+++ b/plugins/SitesManager/angularjs/sites-manager/api-helper.service.js
@@ -0,0 +1,74 @@
+/*!
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+(function () {
+    // can probably be renamed and shared
+    angular.module('piwikApp').factory('sitesManagerApiHelper', SitesManagerAPIHelperFactory);
+
+    SitesManagerAPIHelperFactory.$inject = ['piwikApi'];
+
+    function SitesManagerAPIHelperFactory(piwikApi) {
+
+        return {
+            fetch: fetch,
+            commaDelimitedFieldToArray: commaDelimitedFieldToArray,
+            fetchApi: fetchApi,
+            fetchAction: fetchAction,
+            singleObjectAdaptor: singleObjectAdaptor,
+            valueAdaptor: valueAdaptor,
+            noop: noop
+        };
+
+        function fetch (endpoint, jsonResponseAdaptor, params) {
+
+            return function (clientHandover, additionalParams) {
+
+                params = angular.extend(params || {}, additionalParams || {});
+
+                var requestDefinition = angular.extend(endpoint, params);
+
+                var responseHandler = function (response) {
+
+                    response = jsonResponseAdaptor(response);
+
+                    clientHandover(response);
+                };
+
+                piwikApi.fetch(requestDefinition).then(responseHandler);
+            }
+        }
+
+        function commaDelimitedFieldToArray (value) {
+
+            if(value == null || value == '')
+                return [];
+
+            return value.split(',');
+        }
+
+        function fetchApi(apiMethod, jsonResponseAdaptor, params) {
+
+            return fetch({method: apiMethod}, jsonResponseAdaptor, params);
+        }
+
+        function fetchAction(module, action, jsonResponseAdaptor, params) {
+
+            return fetch({module: module, action: action}, jsonResponseAdaptor, params);
+        }
+
+        function singleObjectAdaptor(response) {
+            return response[0];
+        }
+
+        function valueAdaptor(response) {
+            return response.value;
+        }
+
+        function noop(response) {
+            return response;
+        }
+    }
+})();
diff --git a/plugins/SitesManager/angularjs/sites-manager/api-site.service.js b/plugins/SitesManager/angularjs/sites-manager/api-site.service.js
new file mode 100644
index 0000000000000000000000000000000000000000..6a19081d473ded6812b28b99b3916d7bb1ededee
--- /dev/null
+++ b/plugins/SitesManager/angularjs/sites-manager/api-site.service.js
@@ -0,0 +1,45 @@
+/*!
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+(function () {
+    angular.module('piwikApp').factory('sitesManagerAPI', SitesManagerAPIFactory);
+
+    SitesManagerAPIFactory.$inject = ['sitesManagerApiHelper'];
+
+    function SitesManagerAPIFactory(sitesManagerApiHelper) {
+
+        var api = sitesManagerApiHelper;
+
+        return {
+            getCurrencyList: getCurrencyList(),
+            getSitesWithAdminAccess: getSitesWithAdminAccess(),
+            getTimezonesList: getTimezonesList(),
+            isTimezoneSupportEnabled: isTimezoneSupportEnabled(),
+            getGlobalSettings: getGlobalSettings()
+        };
+
+        function getCurrencyList () {
+            return api.fetchApi('SitesManager.getCurrencyList', api.noop);
+        }
+
+        function getSitesWithAdminAccess () {
+            return api.fetchApi('SitesManager.getSitesWithAdminAccess', api.noop, {fetchAliasUrls: true});
+        }
+
+        function getTimezonesList () {
+            return api.fetchApi('SitesManager.getTimezonesList', api.noop);
+        }
+
+        function isTimezoneSupportEnabled () {
+            return api.fetchApi('SitesManager.isTimezoneSupportEnabled', api.valueAdaptor);
+        }
+
+        function getGlobalSettings () {
+            return api.fetchAction('SitesManager', 'getGlobalSettings', api.noop);
+        }
+    }
+
+})();
diff --git a/plugins/SitesManager/angularjs/sites-manager/edit-trigger.directive.js b/plugins/SitesManager/angularjs/sites-manager/edit-trigger.directive.js
new file mode 100644
index 0000000000000000000000000000000000000000..d4cf807579a4ae4997fb2ee66394d883615c0fe5
--- /dev/null
+++ b/plugins/SitesManager/angularjs/sites-manager/edit-trigger.directive.js
@@ -0,0 +1,30 @@
+/*!
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+(function () {
+    angular.module('piwikApp').directive('sitesManagerEditTrigger', sitesManagerEditTrigger);
+
+    function sitesManagerEditTrigger() {
+
+        return {
+            restrict: 'A',
+            link: function (scope, element) {
+
+                element.bind('click', function(){
+
+                    if(!scope.site.editMode)
+                        scope.$apply(scope.editSite());
+                });
+
+                scope.$watch('site.editMode', function() {
+
+                    element.toggleClass('editable-site-field', !scope.site.editMode);
+                });
+            }
+        };
+    }
+
+})();
diff --git a/plugins/SitesManager/templates/directives/multiline-field.html b/plugins/SitesManager/angularjs/sites-manager/multiline-field.directive.html
similarity index 100%
rename from plugins/SitesManager/templates/directives/multiline-field.html
rename to plugins/SitesManager/angularjs/sites-manager/multiline-field.directive.html
diff --git a/plugins/SitesManager/angularjs/sites-manager/multiline-field.directive.js b/plugins/SitesManager/angularjs/sites-manager/multiline-field.directive.js
new file mode 100644
index 0000000000000000000000000000000000000000..5517495bdda7546170482de51e7fbb7e75a9b39e
--- /dev/null
+++ b/plugins/SitesManager/angularjs/sites-manager/multiline-field.directive.js
@@ -0,0 +1,49 @@
+/*!
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+(function () {
+    angular.module('piwikApp').directive('sitesManagerMultilineField', sitesManagerMultilineField);
+
+    function sitesManagerMultilineField() {
+
+        return {
+            restrict: 'A',
+            replace: true,
+            scope: {
+                managedValue: '=field',
+                rows: '@?',
+                cols: '@?'
+            },
+            templateUrl: 'plugins/SitesManager/angularjs/sites-manager/multiline-field.directive.html?cb=' + piwik.cacheBuster,
+            link: function (scope) {
+
+                var separator = '\n';
+
+                var init = function () {
+
+                    scope.field = {};
+                    scope.onChange = updateManagedScopeValue;
+
+                    scope.$watch('managedValue', updateInputValue);
+                };
+
+                var updateManagedScopeValue = function () {
+                    scope.managedValue = scope.field.value.trim().split(separator);
+                };
+
+                var updateInputValue = function () {
+
+                    if(angular.isUndefined(scope.managedValue))
+                        return;
+
+                    scope.field.value = scope.managedValue.join(separator);
+                };
+
+                init();
+            }
+        };
+    }
+})();
diff --git a/plugins/SitesManager/angularjs/sites-manager/scroll.directive.js b/plugins/SitesManager/angularjs/sites-manager/scroll.directive.js
new file mode 100644
index 0000000000000000000000000000000000000000..630f5b7ee71c1efb13518773e4bb89ed9ab3b798
--- /dev/null
+++ b/plugins/SitesManager/angularjs/sites-manager/scroll.directive.js
@@ -0,0 +1,32 @@
+/*!
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+(function () {
+    angular.module('piwikApp').directive('sitesManagerScroll', sitesManagerScroll);
+
+    sitesManagerScroll.$inject = ['piwik'];
+
+    function sitesManagerScroll (piwik) {
+
+        return {
+            restrict: 'A',
+            link: function (scope, element) {
+
+                function scrollToSite () {
+                    piwik.helper.lazyScrollTo(element[0], 500, true);
+                };
+
+                scope.$watch('site.editMode', function() {
+
+                    if(scope.site.editMode)
+                        scrollToSite();
+                });
+
+            }
+        };
+    }
+
+})();
diff --git a/plugins/SitesManager/angularjs/sites-manager/sites-manager-site.controller.js b/plugins/SitesManager/angularjs/sites-manager/sites-manager-site.controller.js
new file mode 100644
index 0000000000000000000000000000000000000000..def55a8b394f6bdf1a258dffc63996a27ca7c8d0
--- /dev/null
+++ b/plugins/SitesManager/angularjs/sites-manager/sites-manager-site.controller.js
@@ -0,0 +1,169 @@
+/*!
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+(function () {
+    angular.module('piwikApp').controller('SitesManagerSiteController', SitesManagerSiteController);
+
+    SitesManagerSiteController.$inject = ['$scope', '$filter', 'sitesManagerApiHelper'];
+
+    function SitesManagerSiteController($scope, $filter, sitesManagerApiHelper) {
+
+        var translate = $filter('translate');
+
+        var init = function () {
+
+            initModel();
+            initActions();
+        };
+
+        var initActions = function () {
+
+            $scope.editSite = editSite;
+            $scope.saveSite = saveSite;
+            $scope.openDeleteDialog = openDeleteDialog;
+            $scope.site.delete = deleteSite;
+        };
+
+        var initModel = function() {
+
+            if(siteIsNew())
+                initNewSite();
+            else
+                initExistingSite();
+
+            $scope.site.editDialog = {};
+            $scope.site.removeDialog = {};
+        };
+
+        var editSite = function () {
+
+            if ($scope.siteIsBeingEdited) {
+
+                $scope.site.editDialog.show = true;
+                $scope.site.editDialog.title = translate('SitesManager_OnlyOneSiteAtTime', '"' + $scope.lookupCurrentEditSite().name + '"');
+
+            } else {
+
+                $scope.site.editMode = true;
+                $scope.informSiteIsBeingEdited();
+            }
+        };
+
+        var saveSite = function() {
+
+            var sendSiteSearchKeywordParams = $scope.site.sitesearch == '1' && !$scope.site.useDefaultSiteSearchParams;
+            var sendSearchCategoryParameters = sendSiteSearchKeywordParams && $scope.customVariablesActivated;
+
+            var ajaxHandler = new ajaxHelper();
+            ajaxHandler.addParams({
+                module: 'API',
+                format: 'json'
+            }, 'GET');
+
+            if(siteIsNew()) {
+
+                ajaxHandler.addParams({
+                    method: 'SitesManager.addSite'
+                }, 'GET');
+
+            } else {
+
+                ajaxHandler.addParams({
+                    idSite: $scope.site.idsite,
+                    method: 'SitesManager.updateSite'
+                }, 'GET');
+            }
+
+            ajaxHandler.addParams({
+                siteName: $scope.site.name,
+                timezone: $scope.site.timezone,
+                currency: $scope.site.currency,
+                ecommerce: $scope.site.ecommerce,
+                excludedIps: $scope.site.excluded_ips.join(','),
+                excludedQueryParameters: $scope.site.excluded_parameters.join(','),
+                excludedUserAgents: $scope.site.excluded_user_agents.join(','),
+                keepURLFragments: $scope.site.keep_url_fragment,
+                siteSearch: $scope.site.sitesearch,
+                searchKeywordParameters: sendSiteSearchKeywordParams ? $scope.site.sitesearch_keyword_parameters.join(',') : null,
+                searchCategoryParameters: sendSearchCategoryParameters ? $scope.site.sitesearch_category_parameters.join(',') : null,
+                urls: $scope.site.alias_urls
+            }, 'POST');
+
+            ajaxHandler.redirectOnSuccess($scope.redirectParams);
+            ajaxHandler.setLoadingElement();
+            ajaxHandler.send(true);
+        };
+
+        var siteIsNew = function() {
+            return angular.isUndefined($scope.site.idsite);
+        };
+
+        var initNewSite = function() {
+
+            $scope.informSiteIsBeingEdited();
+
+            $scope.site.editMode = true;
+            $scope.site.name = "Name";
+            $scope.site.alias_urls = [
+                "http://siteUrl.com/",
+                "http://siteUrl2.com/"
+            ];
+            $scope.site.keep_url_fragment = "0";
+            $scope.site.excluded_ips = [];
+            $scope.site.excluded_parameters = [];
+            $scope.site.excluded_user_agents = [];
+            $scope.site.sitesearch_keyword_parameters = [];
+            $scope.site.sitesearch_category_parameters = [];
+            $scope.site.sitesearch = $scope.globalSettings.searchKeywordParametersGlobal.length ? "1" : "0";
+            $scope.site.timezone = $scope.globalSettings.defaultTimezone;
+            $scope.site.currency = $scope.globalSettings.defaultCurrency;
+            $scope.site.ecommerce = "0";
+
+            updateSiteWithSiteSearchConfig();
+        };
+
+        var initExistingSite = function() {
+
+            $scope.site.excluded_ips = sitesManagerApiHelper.commaDelimitedFieldToArray($scope.site.excluded_ips);
+            $scope.site.excluded_parameters = sitesManagerApiHelper.commaDelimitedFieldToArray($scope.site.excluded_parameters);
+            $scope.site.excluded_user_agents = sitesManagerApiHelper.commaDelimitedFieldToArray($scope.site.excluded_user_agents);
+            $scope.site.sitesearch_keyword_parameters = sitesManagerApiHelper.commaDelimitedFieldToArray($scope.site.sitesearch_keyword_parameters);
+            $scope.site.sitesearch_category_parameters = sitesManagerApiHelper.commaDelimitedFieldToArray($scope.site.sitesearch_category_parameters);
+
+            updateSiteWithSiteSearchConfig();
+        };
+
+        var updateSiteWithSiteSearchConfig = function() {
+
+            $scope.site.useDefaultSiteSearchParams =
+                $scope.globalSettings.searchKeywordParametersGlobal.length && !$scope.site.sitesearch_keyword_parameters.length;
+        };
+
+        var openDeleteDialog = function() {
+
+            $scope.site.removeDialog.title = translate('SitesManager_DeleteConfirm', '"' + $scope.site.name + '" (idSite = ' + $scope.site.idsite + ')');
+            $scope.site.removeDialog.show = true;
+        };
+
+        var deleteSite = function() {
+
+            var ajaxHandler = new ajaxHelper();
+
+            ajaxHandler.addParams({
+                idSite: $scope.site.idsite,
+                module: 'API',
+                format: 'json',
+                method: 'SitesManager.deleteSite'
+            }, 'GET');
+
+            ajaxHandler.redirectOnSuccess($scope.redirectParams);
+            ajaxHandler.setLoadingElement();
+            ajaxHandler.send(true);
+        };
+
+        init();
+    }
+})();
\ No newline at end of file
diff --git a/plugins/SitesManager/angularjs/sites-manager/sites-manager.controller.js b/plugins/SitesManager/angularjs/sites-manager/sites-manager.controller.js
new file mode 100644
index 0000000000000000000000000000000000000000..752616be1755ce54932d0d9d92bcc926fb14010a
--- /dev/null
+++ b/plugins/SitesManager/angularjs/sites-manager/sites-manager.controller.js
@@ -0,0 +1,249 @@
+/*!
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+(function () {
+    angular.module('piwikApp').controller('SitesManagerController', SitesManagerController);
+
+    SitesManagerController.$inject = ['$scope', '$filter', 'coreAPI', 'coreAdminAPI', 'sitesManagerAPI', 'piwik', 'sitesManagerApiHelper'];
+
+    function SitesManagerController($scope, $filter, coreAPI, coreAdminAPI, sitesManagerAPI, piwik, sitesManagerApiHelper) {
+
+        var translate = $filter('translate');
+
+        var init = function () {
+
+            initModel();
+            initActions();
+        };
+
+        var initModel = function() {
+
+            $scope.sites = [];
+            $scope.hasSuperUserAccess = piwik.hasSuperUserAccess;
+            $scope.redirectParams = {showaddsite: false};
+
+            initSelectLists();
+            initUtcTime();
+            initUserIP();
+            initCustomVariablesActivated();
+            initIsTimezoneSupportEnabled();
+            initGlobalParams();
+        };
+
+        var initActions = function () {
+
+            $scope.cancelEditSite = cancelEditSite;
+            $scope.addSite = addSite;
+            $scope.saveGlobalSettings = saveGlobalSettings;
+
+            $scope.informSiteIsBeingEdited = informSiteIsBeingEdited;
+            $scope.lookupCurrentEditSite = lookupCurrentEditSite;
+        };
+
+        var informSiteIsBeingEdited = function() {
+
+            $scope.siteIsBeingEdited = true;
+        };
+
+        var initSelectLists = function() {
+
+            initSiteSearchSelectOptions();
+            initEcommerceSelectOptions();
+            initCurrencyList();
+            initTimezones();
+        };
+
+        var initGlobalParams = function() {
+
+            showLoading();
+
+            sitesManagerAPI.getGlobalSettings(function(globalSettings) {
+
+                $scope.globalSettings = globalSettings;
+
+                $scope.globalSettings.searchKeywordParametersGlobal = sitesManagerApiHelper.commaDelimitedFieldToArray($scope.globalSettings.searchKeywordParametersGlobal);
+                $scope.globalSettings.searchCategoryParametersGlobal = sitesManagerApiHelper.commaDelimitedFieldToArray($scope.globalSettings.searchCategoryParametersGlobal);
+                $scope.globalSettings.excludedIpsGlobal = sitesManagerApiHelper.commaDelimitedFieldToArray($scope.globalSettings.excludedIpsGlobal);
+                $scope.globalSettings.excludedQueryParametersGlobal = sitesManagerApiHelper.commaDelimitedFieldToArray($scope.globalSettings.excludedQueryParametersGlobal);
+                $scope.globalSettings.excludedUserAgentsGlobal = sitesManagerApiHelper.commaDelimitedFieldToArray($scope.globalSettings.excludedUserAgentsGlobal);
+
+                initKeepURLFragmentsList();
+
+                initSiteList();
+
+                triggerAddSiteIfRequested();
+            });
+        };
+
+        var triggerAddSiteIfRequested = function() {
+
+            if(piwik.helper.getArrayFromQueryString(String(window.location.search))['showaddsite'] == 1)
+                addSite();
+        };
+
+        var initEcommerceSelectOptions = function() {
+
+            $scope.eCommerceptions = [
+                {key: '0', value: translate('SitesManager_NotAnEcommerceSite')},
+                {key: '1', value: translate('SitesManager_EnableEcommerce')}
+            ];
+        };
+
+        var initUtcTime = function() {
+
+            var currentDate = new Date();
+
+            $scope.utcTime =  new Date(
+                currentDate.getUTCFullYear(),
+                currentDate.getUTCMonth(),
+                currentDate.getUTCDate(),
+                currentDate.getUTCHours(),
+                currentDate.getUTCMinutes(),
+                currentDate.getUTCSeconds()
+            );
+        };
+
+        var initIsTimezoneSupportEnabled = function() {
+
+            sitesManagerAPI.isTimezoneSupportEnabled(function (timezoneSupportEnabled) {
+                $scope.timezoneSupportEnabled = timezoneSupportEnabled;
+            });
+        };
+
+        var initTimezones = function() {
+
+            sitesManagerAPI.getTimezonesList(
+
+                function (timezones) {
+
+                    $scope.timezones = [];
+
+                    angular.forEach(timezones, function(groupTimezones, timezoneGroup) {
+
+                        angular.forEach(groupTimezones, function(label, code) {
+
+                            $scope.timezones.push({
+                                group: timezoneGroup,
+                                code: code,
+                                label: label
+                            });
+                        });
+                    });
+                }
+            );
+        };
+
+        var initCustomVariablesActivated = function() {
+
+            coreAdminAPI.isPluginActivated(
+
+                function (customVariablesActivated) {
+                    $scope.customVariablesActivated = customVariablesActivated;
+                },
+
+                {pluginName: 'CustomVariables'}
+            );
+        };
+
+        var initUserIP = function() {
+
+            coreAPI.getIpFromHeader(function(ip) {
+                $scope.currentIpAddress = ip;
+            });
+        };
+
+        var initSiteSearchSelectOptions = function() {
+
+            $scope.siteSearchOptions = [
+                {key: '1', value: translate('SitesManager_EnableSiteSearch')},
+                {key: '0', value: translate('SitesManager_DisableSiteSearch')}
+            ];
+        };
+
+        var initKeepURLFragmentsList = function() {
+
+            $scope.keepURLFragmentsOptions = {
+                0: ($scope.globalSettings.keepURLFragmentsGlobal ? translate('General_Yes') : translate('General_No')) + ' (' + translate('General_Default') + ')',
+                1: translate('General_Yes'),
+                2: translate('General_No')
+            };
+        };
+
+        var addSite = function() {
+            $scope.sites.push({});
+        };
+
+        var saveGlobalSettings = function() {
+
+            var ajaxHandler = new ajaxHelper();
+
+            ajaxHandler.addParams({
+                module: 'SitesManager',
+                format: 'json',
+                action: 'setGlobalSettings'
+            }, 'GET');
+
+            ajaxHandler.addParams({
+                timezone: $scope.globalSettings.defaultTimezone,
+                currency: $scope.globalSettings.defaultCurrency,
+                excludedIps: $scope.globalSettings.excludedIpsGlobal.join(','),
+                excludedQueryParameters: $scope.globalSettings.excludedQueryParametersGlobal.join(','),
+                excludedUserAgents: $scope.globalSettings.excludedUserAgentsGlobal.join(','),
+                keepURLFragments: $scope.globalSettings.keepURLFragmentsGlobal ? 1 : 0,
+                enableSiteUserAgentExclude: $scope.globalSettings.siteSpecificUserAgentExcludeEnabled ? 1 : 0,
+                searchKeywordParameters: $scope.globalSettings.searchKeywordParametersGlobal.join(','),
+                searchCategoryParameters: $scope.globalSettings.searchCategoryParametersGlobal.join(',')
+            }, 'POST');
+
+            ajaxHandler.redirectOnSuccess($scope.redirectParams);
+            ajaxHandler.setLoadingElement();
+            ajaxHandler.send(true);
+        };
+
+        var cancelEditSite = function ($event) {
+            $event.stopPropagation();
+            piwik.helper.redirect($scope.redirectParams);
+        };
+
+        var lookupCurrentEditSite = function () {
+
+            var sitesInEditMode = $scope.sites.filter(function(site) {
+                return site.editMode;
+            });
+
+            return sitesInEditMode[0];
+        };
+
+        var initSiteList = function () {
+
+            sitesManagerAPI.getSitesWithAdminAccess(function (sites) {
+
+                angular.forEach(sites, function(site) {
+                    $scope.sites.push(site);
+                });
+
+                hideLoading();
+            });
+        };
+
+        var initCurrencyList = function () {
+
+            sitesManagerAPI.getCurrencyList(function (currencies) {
+                $scope.currencies = currencies;
+            });
+        };
+
+        var showLoading = function() {
+            $scope.loading = true;
+        };
+
+        var hideLoading = function() {
+            $scope.loading = false;
+        };
+
+        init();
+    }
+})();
diff --git a/plugins/SitesManager/javascripts/sites-manager-controller.js b/plugins/SitesManager/javascripts/sites-manager-controller.js
deleted file mode 100644
index 6da2f511e4988342663f4f8da4077e57be316f2b..0000000000000000000000000000000000000000
--- a/plugins/SitesManager/javascripts/sites-manager-controller.js
+++ /dev/null
@@ -1,244 +0,0 @@
-/*!
- * Piwik - free/libre analytics platform
- *
- * @link http://piwik.org
- * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
- */
-
-angular.module('piwikApp').controller('SitesManagerController', function ($scope, $filter, coreAPI, coreAdminAPI, sitesManagerAPI, piwik, sitesManagerApiHelper) {
-
-    var translate = $filter('translate');
-
-    var init = function () {
-
-        initModel();
-        initActions();
-    };
-
-    var initModel = function() {
-
-        $scope.sites = [];
-        $scope.hasSuperUserAccess = piwik.hasSuperUserAccess;
-        $scope.redirectParams = {showaddsite: false};
-
-        initSelectLists();
-        initUtcTime();
-        initUserIP();
-        initCustomVariablesActivated();
-        initIsTimezoneSupportEnabled();
-        initGlobalParams();
-    };
-
-    var initActions = function () {
-
-        $scope.cancelEditSite = cancelEditSite;
-        $scope.addSite = addSite;
-        $scope.saveGlobalSettings = saveGlobalSettings;
-
-        $scope.informSiteIsBeingEdited = informSiteIsBeingEdited;
-        $scope.lookupCurrentEditSite = lookupCurrentEditSite;
-    };
-
-    var informSiteIsBeingEdited = function() {
-
-        $scope.siteIsBeingEdited = true;
-    };
-
-    var initSelectLists = function() {
-
-        initSiteSearchSelectOptions();
-        initEcommerceSelectOptions();
-        initCurrencyList();
-        initTimezones();
-    };
-
-    var initGlobalParams = function() {
-
-        showLoading();
-
-        sitesManagerAPI.getGlobalSettings(function(globalSettings) {
-
-            $scope.globalSettings = globalSettings;
-
-            $scope.globalSettings.searchKeywordParametersGlobal = sitesManagerApiHelper.commaDelimitedFieldToArray($scope.globalSettings.searchKeywordParametersGlobal);
-            $scope.globalSettings.searchCategoryParametersGlobal = sitesManagerApiHelper.commaDelimitedFieldToArray($scope.globalSettings.searchCategoryParametersGlobal);
-            $scope.globalSettings.excludedIpsGlobal = sitesManagerApiHelper.commaDelimitedFieldToArray($scope.globalSettings.excludedIpsGlobal);
-            $scope.globalSettings.excludedQueryParametersGlobal = sitesManagerApiHelper.commaDelimitedFieldToArray($scope.globalSettings.excludedQueryParametersGlobal);
-            $scope.globalSettings.excludedUserAgentsGlobal = sitesManagerApiHelper.commaDelimitedFieldToArray($scope.globalSettings.excludedUserAgentsGlobal);
-
-            initKeepURLFragmentsList();
-
-            initSiteList();
-
-            triggerAddSiteIfRequested();
-        });
-    };
-
-    var triggerAddSiteIfRequested = function() {
-
-        if(piwikHelper.getArrayFromQueryString(String(window.location.search))['showaddsite'] == 1)
-            addSite();
-    };
-
-    var initEcommerceSelectOptions = function() {
-
-        $scope.eCommerceptions = [
-            {key: '0', value: translate('SitesManager_NotAnEcommerceSite')},
-            {key: '1', value: translate('SitesManager_EnableEcommerce')}
-        ];
-    };
-
-    var initUtcTime = function() {
-
-        var currentDate = new Date();
-
-        $scope.utcTime =  new Date(
-            currentDate.getUTCFullYear(),
-            currentDate.getUTCMonth(),
-            currentDate.getUTCDate(),
-            currentDate.getUTCHours(),
-            currentDate.getUTCMinutes(),
-            currentDate.getUTCSeconds()
-        );
-    };
-
-    var initIsTimezoneSupportEnabled = function() {
-
-        sitesManagerAPI.isTimezoneSupportEnabled(function (timezoneSupportEnabled) {
-            $scope.timezoneSupportEnabled = timezoneSupportEnabled;
-        });
-    };
-
-    var initTimezones = function() {
-
-        sitesManagerAPI.getTimezonesList(
-
-            function (timezones) {
-
-                $scope.timezones = [];
-
-                angular.forEach(timezones, function(groupTimezones, timezoneGroup) {
-
-                    angular.forEach(groupTimezones, function(label, code) {
-
-                        $scope.timezones.push({
-                           group: timezoneGroup,
-                           code: code,
-                           label: label
-                        });
-                    });
-                });
-            }
-        );
-    };
-
-    var initCustomVariablesActivated = function() {
-
-        coreAdminAPI.isPluginActivated(
-
-            function (customVariablesActivated) {
-                $scope.customVariablesActivated = customVariablesActivated;
-            },
-
-            {pluginName: 'CustomVariables'}
-        );
-    };
-
-    var initUserIP = function() {
-
-        coreAPI.getIpFromHeader(function(ip) {
-            $scope.currentIpAddress = ip;
-        });
-    };
-
-    var initSiteSearchSelectOptions = function() {
-
-        $scope.siteSearchOptions = [
-            {key: '1', value: translate('SitesManager_EnableSiteSearch')},
-            {key: '0', value: translate('SitesManager_DisableSiteSearch')}
-        ];
-    };
-
-    var initKeepURLFragmentsList = function() {
-
-        $scope.keepURLFragmentsOptions = {
-            0: ($scope.globalSettings.keepURLFragmentsGlobal ? translate('General_Yes') : translate('General_No')) + ' (' + translate('General_Default') + ')',
-            1: translate('General_Yes'),
-            2: translate('General_No')
-        };
-    };
-
-    var addSite = function() {
-        $scope.sites.push({});
-    };
-
-    var saveGlobalSettings = function() {
-
-        var ajaxHandler = new ajaxHelper();
-
-        ajaxHandler.addParams({
-            module: 'SitesManager',
-            format: 'json',
-            action: 'setGlobalSettings'
-        }, 'GET');
-
-        ajaxHandler.addParams({
-            timezone: $scope.globalSettings.defaultTimezone,
-            currency: $scope.globalSettings.defaultCurrency,
-            excludedIps: $scope.globalSettings.excludedIpsGlobal.join(','),
-            excludedQueryParameters: $scope.globalSettings.excludedQueryParametersGlobal.join(','),
-            excludedUserAgents: $scope.globalSettings.excludedUserAgentsGlobal.join(','),
-            keepURLFragments: $scope.globalSettings.keepURLFragmentsGlobal ? 1 : 0,
-            enableSiteUserAgentExclude: $scope.globalSettings.siteSpecificUserAgentExcludeEnabled ? 1 : 0,
-            searchKeywordParameters: $scope.globalSettings.searchKeywordParametersGlobal.join(','),
-            searchCategoryParameters: $scope.globalSettings.searchCategoryParametersGlobal.join(',')
-        }, 'POST');
-
-        ajaxHandler.redirectOnSuccess($scope.redirectParams);
-        ajaxHandler.setLoadingElement();
-        ajaxHandler.send(true);
-    };
-
-    var cancelEditSite = function ($event) {
-        $event.stopPropagation();
-        piwikHelper.redirect($scope.redirectParams);
-    };
-
-    var lookupCurrentEditSite = function () {
-
-        var sitesInEditMode = $scope.sites.filter(function(site) {
-            return site.editMode;
-        });
-
-        return sitesInEditMode[0];
-    };
-
-    var initSiteList = function () {
-
-        sitesManagerAPI.getSitesWithAdminAccess(function (sites) {
-
-            angular.forEach(sites, function(site) {
-                $scope.sites.push(site);
-            });
-
-            hideLoading();
-        });
-    };
-
-    var initCurrencyList = function () {
-
-        sitesManagerAPI.getCurrencyList(function (currencies) {
-            $scope.currencies = currencies;
-        });
-    };
-
-    var showLoading = function() {
-        $scope.loading = true;
-    };
-
-    var hideLoading = function() {
-        $scope.loading = false;
-    };
-
-    init();
-});
diff --git a/plugins/SitesManager/javascripts/sites-manager-directives.js b/plugins/SitesManager/javascripts/sites-manager-directives.js
deleted file mode 100644
index 2357d3da607d231bcb011d37bf869b24fb5db2be..0000000000000000000000000000000000000000
--- a/plugins/SitesManager/javascripts/sites-manager-directives.js
+++ /dev/null
@@ -1,85 +0,0 @@
-/*!
- * Piwik - free/libre analytics platform
- *
- * @link http://piwik.org
- * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
- */
-
-angular.module('piwikApp').directive('sitesManagerMultilineField', function () {
-
-    return {
-        restrict: 'A',
-        replace: true,
-        scope: {
-            managedValue: '=field',
-            rows: '@?',
-            cols: '@?'
-        },
-        templateUrl: 'plugins/SitesManager/templates/directives/multiline-field.html?cb=' + piwik.cacheBuster,
-        link: function (scope) {
-
-            var separator = '\n';
-
-            var init = function () {
-
-                scope.field = {};
-                scope.onChange = updateManagedScopeValue;
-
-                scope.$watch('managedValue', updateInputValue);
-            };
-
-            var updateManagedScopeValue = function () {
-                scope.managedValue = scope.field.value.trim().split(separator);
-            };
-
-            var updateInputValue = function () {
-
-                if(angular.isUndefined(scope.managedValue))
-                    return;
-
-                scope.field.value = scope.managedValue.join(separator);
-            };
-
-            init();
-        }
-    };
-});
-
-angular.module('piwikApp').directive('sitesManagerEditTrigger', function () {
-
-    return {
-        restrict: 'A',
-        link: function (scope, element) {
-
-            element.bind('click', function(){
-
-                if(!scope.site.editMode)
-                    scope.$apply(scope.editSite());
-            });
-
-            scope.$watch('site.editMode', function() {
-
-                element.toggleClass('editable-site-field', !scope.site.editMode);
-            });
-        }
-    };
-});
-
-angular.module('piwikApp').directive('sitesManagerScroll', function () {
-
-    return {
-        restrict: 'A',
-        link: function (scope, element) {
-
-            scope.$watch('site.editMode', function() {
-
-                if(scope.site.editMode)
-                    scrollToSite();
-            });
-
-            var scrollToSite = function() {
-                piwikHelper.lazyScrollTo(element[0], 500, true);
-            };
-        }
-    };
-});
diff --git a/plugins/SitesManager/javascripts/sites-manager-recipes.js b/plugins/SitesManager/javascripts/sites-manager-recipes.js
deleted file mode 100644
index 81bcaff97e7dbbb92218e5276ed371bd10bfdbc0..0000000000000000000000000000000000000000
--- a/plugins/SitesManager/javascripts/sites-manager-recipes.js
+++ /dev/null
@@ -1,95 +0,0 @@
-/*!
- * Piwik - free/libre analytics platform
- *
- * @link http://piwik.org
- * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
- */
-
-angular.module('piwikApp').factory('sitesManagerAPI', function SitesManagerAPIFactory(sitesManagerApiHelper) {
-
-    var api = sitesManagerApiHelper;
-
-    return {
-        getCurrencyList: api.fetchApi('SitesManager.getCurrencyList', api.noop),
-        getSitesWithAdminAccess: api.fetchApi('SitesManager.getSitesWithAdminAccess', api.noop, {fetchAliasUrls: true}),
-        getTimezonesList: api.fetchApi('SitesManager.getTimezonesList', api.noop),
-        isTimezoneSupportEnabled: api.fetchApi('SitesManager.isTimezoneSupportEnabled', api.valueAdaptor),
-        getGlobalSettings: api.fetchAction('SitesManager', 'getGlobalSettings', api.noop)
-    };
-});
-
-// can probably be shared
-angular.module('piwikApp').factory('coreAPI', function CoreAPIFactory(sitesManagerApiHelper) {
-
-    var api = sitesManagerApiHelper;
-
-    return {
-        getIpFromHeader: api.fetchApi('API.getIpFromHeader', api.valueAdaptor)
-    };
-});
-
-// can probably be shared
-angular.module('piwikApp').factory('coreAdminAPI', function CoreAdminAPIFactory(sitesManagerApiHelper) {
-
-    var api = sitesManagerApiHelper;
-
-    return {
-        isPluginActivated: api.fetchApi('CoreAdminHome.isPluginActivated', api.valueAdaptor)
-    };
-});
-
-// can probably be renamed and shared
-angular.module('piwikApp').factory('sitesManagerApiHelper', function SitesManagerAPIHelperFactory(piwikApi) {
-
-    return {
-
-        fetch: function (endpoint, jsonResponseAdaptor, params) {
-
-            return function (clientHandover, additionalParams) {
-
-                params = angular.extend(params || {}, additionalParams || {});
-
-                var requestDefinition = angular.extend(endpoint, params);
-
-                var responseHandler = function (response) {
-
-                    response = jsonResponseAdaptor(response);
-
-                    clientHandover(response);
-                };
-
-                piwikApi.fetch(requestDefinition).then(responseHandler);
-            }
-        },
-
-        commaDelimitedFieldToArray: function(value) {
-
-            if(value == null || value == '')
-                return [];
-
-            return value.split(',');
-        },
-
-        fetchApi: function (apiMethod, jsonResponseAdaptor, params) {
-
-           return this.fetch({method: apiMethod}, jsonResponseAdaptor, params);
-        },
-
-        fetchAction: function (module, action, jsonResponseAdaptor, params) {
-
-            return this.fetch({module: module, action: action}, jsonResponseAdaptor, params);
-        },
-
-        singleObjectAdaptor: function (response) {
-            return response[0];
-        },
-
-        valueAdaptor: function (response) {
-            return response.value;
-        },
-
-        noop: function (response) {
-            return response;
-        }
-    };
-});
diff --git a/plugins/SitesManager/javascripts/sites-manager-site-controller.js b/plugins/SitesManager/javascripts/sites-manager-site-controller.js
deleted file mode 100644
index c6ea22e45d865254d99eb24c8e162988900bf183..0000000000000000000000000000000000000000
--- a/plugins/SitesManager/javascripts/sites-manager-site-controller.js
+++ /dev/null
@@ -1,164 +0,0 @@
-/*!
- * Piwik - free/libre analytics platform
- *
- * @link http://piwik.org
- * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
- */
-
-angular.module('piwikApp').controller('SitesManagerSiteController', function ($scope, $filter, sitesManagerApiHelper) {
-
-    var translate = $filter('translate');
-
-    var init = function () {
-
-        initModel();
-        initActions();
-    };
-
-    var initActions = function () {
-
-        $scope.editSite = editSite;
-        $scope.saveSite = saveSite;
-        $scope.openDeleteDialog = openDeleteDialog;
-        $scope.site.delete = deleteSite;
-    };
-
-    var initModel = function() {
-
-        if(siteIsNew())
-            initNewSite();
-        else
-            initExistingSite();
-
-        $scope.site.editDialog = {};
-        $scope.site.removeDialog = {};
-    };
-
-    var editSite = function () {
-
-        if ($scope.siteIsBeingEdited) {
-
-            $scope.site.editDialog.show = true;
-            $scope.site.editDialog.title = translate('SitesManager_OnlyOneSiteAtTime', '"' + $scope.lookupCurrentEditSite().name + '"');
-
-        } else {
-
-            $scope.site.editMode = true;
-            $scope.informSiteIsBeingEdited();
-        }
-    };
-
-    var saveSite = function() {
-
-        var sendSiteSearchKeywordParams = $scope.site.sitesearch == '1' && !$scope.site.useDefaultSiteSearchParams;
-        var sendSearchCategoryParameters = sendSiteSearchKeywordParams && $scope.customVariablesActivated;
-
-        var ajaxHandler = new ajaxHelper();
-        ajaxHandler.addParams({
-            module: 'API',
-            format: 'json'
-        }, 'GET');
-
-        if(siteIsNew()) {
-
-            ajaxHandler.addParams({
-                method: 'SitesManager.addSite'
-            }, 'GET');
-
-        } else {
-
-            ajaxHandler.addParams({
-                idSite: $scope.site.idsite,
-                method: 'SitesManager.updateSite'
-            }, 'GET');
-        }
-
-        ajaxHandler.addParams({
-            siteName: $scope.site.name,
-            timezone: $scope.site.timezone,
-            currency: $scope.site.currency,
-            ecommerce: $scope.site.ecommerce,
-            excludedIps: $scope.site.excluded_ips.join(','),
-            excludedQueryParameters: $scope.site.excluded_parameters.join(','),
-            excludedUserAgents: $scope.site.excluded_user_agents.join(','),
-            keepURLFragments: $scope.site.keep_url_fragment,
-            siteSearch: $scope.site.sitesearch,
-            searchKeywordParameters: sendSiteSearchKeywordParams ? $scope.site.sitesearch_keyword_parameters.join(',') : null,
-            searchCategoryParameters: sendSearchCategoryParameters ? $scope.site.sitesearch_category_parameters.join(',') : null,
-            urls: $scope.site.alias_urls
-        }, 'POST');
-
-        ajaxHandler.redirectOnSuccess($scope.redirectParams);
-        ajaxHandler.setLoadingElement();
-        ajaxHandler.send(true);
-    };
-
-    var siteIsNew = function() {
-        return angular.isUndefined($scope.site.idsite);
-    };
-
-    var initNewSite = function() {
-
-        $scope.informSiteIsBeingEdited();
-
-        $scope.site.editMode = true;
-        $scope.site.name = "Name";
-        $scope.site.alias_urls = [
-            "http://siteUrl.com/",
-            "http://siteUrl2.com/"
-        ];
-        $scope.site.keep_url_fragment = "0";
-        $scope.site.excluded_ips = [];
-        $scope.site.excluded_parameters = [];
-        $scope.site.excluded_user_agents = [];
-        $scope.site.sitesearch_keyword_parameters = [];
-        $scope.site.sitesearch_category_parameters = [];
-        $scope.site.sitesearch = $scope.globalSettings.searchKeywordParametersGlobal.length ? "1" : "0";
-        $scope.site.timezone = $scope.globalSettings.defaultTimezone;
-        $scope.site.currency = $scope.globalSettings.defaultCurrency;
-        $scope.site.ecommerce = "0";
-
-        updateSiteWithSiteSearchConfig();
-    };
-
-    var initExistingSite = function() {
-
-        $scope.site.excluded_ips = sitesManagerApiHelper.commaDelimitedFieldToArray($scope.site.excluded_ips);
-        $scope.site.excluded_parameters = sitesManagerApiHelper.commaDelimitedFieldToArray($scope.site.excluded_parameters);
-        $scope.site.excluded_user_agents = sitesManagerApiHelper.commaDelimitedFieldToArray($scope.site.excluded_user_agents);
-        $scope.site.sitesearch_keyword_parameters = sitesManagerApiHelper.commaDelimitedFieldToArray($scope.site.sitesearch_keyword_parameters);
-        $scope.site.sitesearch_category_parameters = sitesManagerApiHelper.commaDelimitedFieldToArray($scope.site.sitesearch_category_parameters);
-
-        updateSiteWithSiteSearchConfig();
-    };
-
-    var updateSiteWithSiteSearchConfig = function() {
-
-        $scope.site.useDefaultSiteSearchParams =
-            $scope.globalSettings.searchKeywordParametersGlobal.length && !$scope.site.sitesearch_keyword_parameters.length;
-    };
-
-    var openDeleteDialog = function() {
-
-        $scope.site.removeDialog.title = translate('SitesManager_DeleteConfirm', '"' + $scope.site.name + '" (idSite = ' + $scope.site.idsite + ')');
-        $scope.site.removeDialog.show = true;
-    };
-
-    var deleteSite = function() {
-
-        var ajaxHandler = new ajaxHelper();
-
-        ajaxHandler.addParams({
-            idSite: $scope.site.idsite,
-            module: 'API',
-            format: 'json',
-            method: 'SitesManager.deleteSite'
-        }, 'GET');
-
-        ajaxHandler.redirectOnSuccess($scope.redirectParams);
-        ajaxHandler.setLoadingElement();
-        ajaxHandler.send(true);
-    };
-
-    init();
-});
diff --git a/plugins/ZenMode/ZenMode.php b/plugins/ZenMode/ZenMode.php
index 8f9d1407952c57924bfcc9ae448e5b3fc727de9f..d22ad4af9d06af9083b0a2fefda5596bb0ef8af8 100644
--- a/plugins/ZenMode/ZenMode.php
+++ b/plugins/ZenMode/ZenMode.php
@@ -39,13 +39,13 @@ class ZenMode extends \Piwik\Plugin
     public function getJsFiles(&$jsFiles)
     {
         $jsFiles[] = "plugins/ZenMode/javascripts/zen-mode.js";
-        $jsFiles[] = "plugins/ZenMode/angularjs/quick-access/quick-access-directive.js";
-        $jsFiles[] = "plugins/ZenMode/angularjs/zen-mode/zen-mode-switcher-directive.js";
+        $jsFiles[] = "plugins/ZenMode/angularjs/quick-access/quick-access.directive.js";
+        $jsFiles[] = "plugins/ZenMode/angularjs/zen-mode/zen-mode-switcher.directive.js";
     }
 
     public function getStylesheetFiles(&$stylesheets)
     {
-        $stylesheets[] = "plugins/ZenMode/angularjs/quick-access/quick-access.less";
+        $stylesheets[] = "plugins/ZenMode/angularjs/quick-access/quick-access.directive.less";
         $stylesheets[] = "plugins/ZenMode/angularjs/zen-mode/zen-mode.less";
     }
 }
diff --git a/plugins/ZenMode/angularjs/quick-access/quick-access-directive.js b/plugins/ZenMode/angularjs/quick-access/quick-access-directive.js
deleted file mode 100644
index fe1b196602965579d4a7b556241336137b439e71..0000000000000000000000000000000000000000
--- a/plugins/ZenMode/angularjs/quick-access/quick-access-directive.js
+++ /dev/null
@@ -1,134 +0,0 @@
-/*!
- * Piwik - free/libre analytics platform
- *
- * @link http://piwik.org
- * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
- */
-
-/**
- * Usage:
- * <div piwik-dialog="showDialog">...</div>
- * Will show dialog once showDialog evaluates to true.
- *
- * Will execute the "executeMyFunction" function in the current scope once the yes button is pressed.
- */
-angular.module('piwikApp').directive('piwikQuickAccess', function($rootElement, $timeout, $filter, siteSelectorModel, piwik) {
-
-    return {
-        restrict: 'A',
-        replace: true,
-        scope: {},
-        templateUrl: 'plugins/ZenMode/angularjs/quick-access/quick-access.html?cb=' + piwik.cacheBuster,
-        link: function (scope, element, attrs) {
-
-            var menuIndex = -1;
-            var menuItems = [];
-            var reportEntries = [];
-
-            scope.reportEntries = [];
-            scope.menuItems  = [];
-            scope.sitesModel = siteSelectorModel;
-
-            function getMenuItems()
-            {
-                if (menuItems && menuItems.length) {
-                    return menuItems;
-                }
-
-                $rootElement.find('#topRightBar .topBarElem a').each(function (index, element) {
-                    menuItems.push({name: $(element).text(), index: ++menuIndex, category: 'menuCategory'});
-                    $(element).attr('quick_access', menuIndex);
-                });
-
-                return menuItems;
-            }
-
-            function getReportEntries()
-            {
-                if (reportEntries && reportEntries.length) {
-                    return reportEntries;
-                }
-
-                $rootElement.find('.Menu-tabList a').each(function (index, element) {
-                    reportEntries.push({name: $(element).text(), category: 'reportCategory', index: ++menuIndex});
-                    $(element).attr('quick_access', menuIndex);
-                });
-
-                return reportEntries;
-            }
-
-            function highlightPreviousItem()
-            {
-                if (0 >= (scope.search.index - 1)) {
-                    scope.search.index = 0;
-                } else {
-                    scope.search.index--;
-                }
-            }
-
-            function highlightNextItem()
-            {
-                var numTotal = element.find('li.result').length;
-
-                if (numTotal <= (scope.search.index + 1)) {
-                    scope.search.index = numTotal - 1;
-                } else {
-                    scope.search.index++;
-                }
-            }
-
-            function executeMenuItem()
-            {
-                var results = element.find('li.result');
-                if (results && results.length && results[scope.search.index]) {
-                    var selectedMenuElement = $(results[scope.search.index]);
-                    $timeout(function () {
-                        selectedMenuElement.click();
-                    }, 20);
-                }
-            }
-
-            scope.onKeypress = function (event) {
-
-                if (38 == event.which) {
-                    highlightPreviousItem();
-                    event.preventDefault();
-                } else if (40 == event.which) {
-                    highlightNextItem();
-                    event.preventDefault();
-                } else if (13 == event.which) {
-                    executeMenuItem();
-                }
-            };
-
-            scope.search = function (searchTerm) {
-                this.search.index  = 0;
-
-                this.menuItems     = $filter('filter')(getMenuItems(), searchTerm);
-                this.reportEntries = $filter('filter')(getReportEntries(), searchTerm);
-                this.sitesModel.searchSite(searchTerm);
-            };
-
-            scope.selectSite = function (idsite) {
-                this.sitesModel.loadSite(idsite);
-            };
-
-            scope.selectMenuItem = function (index) {
-                var target = $rootElement.find('[quick_access=' + index + ']');
-
-                if (target && target.length && target[0]) {
-                    var actualTarget = target[0];
-
-                    var href = $(actualTarget).attr('href');
-
-                    if (href && href.length > 10) {
-                        actualTarget.click();
-                    } else {
-                        $(actualTarget).click();
-                    }
-                }
-            };
-
-        }
-    };
-});
\ No newline at end of file
diff --git a/plugins/ZenMode/angularjs/quick-access/quick-access.html b/plugins/ZenMode/angularjs/quick-access/quick-access.directive.html
similarity index 100%
rename from plugins/ZenMode/angularjs/quick-access/quick-access.html
rename to plugins/ZenMode/angularjs/quick-access/quick-access.directive.html
diff --git a/plugins/ZenMode/angularjs/quick-access/quick-access.directive.js b/plugins/ZenMode/angularjs/quick-access/quick-access.directive.js
new file mode 100644
index 0000000000000000000000000000000000000000..c05fb5fbbce5448b770941037d83c225a50a54f6
--- /dev/null
+++ b/plugins/ZenMode/angularjs/quick-access/quick-access.directive.js
@@ -0,0 +1,140 @@
+/*!
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+/**
+ * Usage:
+ * <div piwik-dialog="showDialog">...</div>
+ * Will show dialog once showDialog evaluates to true.
+ *
+ * Will execute the "executeMyFunction" function in the current scope once the yes button is pressed.
+ */
+(function () {
+    angular.module('piwikApp').directive('piwikQuickAccess', QuickAccessDirective);
+
+    QuickAccessDirective.$inject = ['$rootElement', '$timeout', '$filter', 'siteSelectorModel', 'piwik'];
+
+    function QuickAccessDirective ($rootElement, $timeout, $filter, siteSelectorModel, piwik) {
+
+        return {
+            restrict: 'A',
+            replace: true,
+            scope: {},
+            templateUrl: 'plugins/ZenMode/angularjs/quick-access/quick-access.directive.html?cb=' + piwik.cacheBuster,
+            link: function (scope, element, attrs) {
+
+                var menuIndex = -1;
+                var menuItems = [];
+                var reportEntries = [];
+
+                scope.reportEntries = [];
+                scope.menuItems  = [];
+                scope.sitesModel = siteSelectorModel;
+
+                function getMenuItems()
+                {
+                    if (menuItems && menuItems.length) {
+                        return menuItems;
+                    }
+
+                    $rootElement.find('#topRightBar .topBarElem a').each(function (index, element) {
+                        menuItems.push({name: $(element).text(), index: ++menuIndex, category: 'menuCategory'});
+                        $(element).attr('quick_access', menuIndex);
+                    });
+
+                    return menuItems;
+                }
+
+                function getReportEntries()
+                {
+                    if (reportEntries && reportEntries.length) {
+                        return reportEntries;
+                    }
+
+                    $rootElement.find('.Menu-tabList a').each(function (index, element) {
+                        reportEntries.push({name: $(element).text(), category: 'reportCategory', index: ++menuIndex});
+                        $(element).attr('quick_access', menuIndex);
+                    });
+
+                    return reportEntries;
+                }
+
+                function highlightPreviousItem()
+                {
+                    if (0 >= (scope.search.index - 1)) {
+                        scope.search.index = 0;
+                    } else {
+                        scope.search.index--;
+                    }
+                }
+
+                function highlightNextItem()
+                {
+                    var numTotal = element.find('li.result').length;
+
+                    if (numTotal <= (scope.search.index + 1)) {
+                        scope.search.index = numTotal - 1;
+                    } else {
+                        scope.search.index++;
+                    }
+                }
+
+                function executeMenuItem()
+                {
+                    var results = element.find('li.result');
+                    if (results && results.length && results[scope.search.index]) {
+                        var selectedMenuElement = $(results[scope.search.index]);
+                        $timeout(function () {
+                            selectedMenuElement.click();
+                        }, 20);
+                    }
+                }
+
+                scope.onKeypress = function (event) {
+
+                    if (38 == event.which) {
+                        highlightPreviousItem();
+                        event.preventDefault();
+                    } else if (40 == event.which) {
+                        highlightNextItem();
+                        event.preventDefault();
+                    } else if (13 == event.which) {
+                        executeMenuItem();
+                    }
+                };
+
+                scope.search = function (searchTerm) {
+                    this.search.index  = 0;
+
+                    this.menuItems     = $filter('filter')(getMenuItems(), searchTerm);
+                    this.reportEntries = $filter('filter')(getReportEntries(), searchTerm);
+                    this.sitesModel.searchSite(searchTerm);
+                };
+
+                scope.selectSite = function (idsite) {
+                    this.sitesModel.loadSite(idsite);
+                };
+
+                scope.selectMenuItem = function (index) {
+                    var target = $rootElement.find('[quick_access=' + index + ']');
+
+                    if (target && target.length && target[0]) {
+                        var actualTarget = target[0];
+
+                        var href = $(actualTarget).attr('href');
+
+                        if (href && href.length > 10) {
+                            actualTarget.click();
+                        } else {
+                            $(actualTarget).click();
+                        }
+                    }
+                };
+
+            }
+        };
+    }
+})();
\ No newline at end of file
diff --git a/plugins/ZenMode/angularjs/quick-access/quick-access.less b/plugins/ZenMode/angularjs/quick-access/quick-access.directive.less
similarity index 100%
rename from plugins/ZenMode/angularjs/quick-access/quick-access.less
rename to plugins/ZenMode/angularjs/quick-access/quick-access.directive.less
diff --git a/plugins/ZenMode/angularjs/zen-mode/zen-mode-switcher-directive.js b/plugins/ZenMode/angularjs/zen-mode/zen-mode-switcher-directive.js
deleted file mode 100644
index 6a54ef446e79bf20edcff0288df36d454e312fbd..0000000000000000000000000000000000000000
--- a/plugins/ZenMode/angularjs/zen-mode/zen-mode-switcher-directive.js
+++ /dev/null
@@ -1,48 +0,0 @@
-/*!
- * Piwik - free/libre analytics platform
- *
- * @link http://piwik.org
- * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
- */
-
-/**
- * Usage:
- * <div piwik-zen-mode-switcher>...</div>
- * Will toggle the zen mode on click on this element.
- */
-angular.module('piwikApp').directive('piwikZenModeSwitcher', function($rootElement, $filter) {
-
-    function showZenModeIsActivatedNotification() {
-        var howToSearch = $filter('translate')('ZenMode_HowToSearch');
-        var howToToggle = $filter('translate')('ZenMode_HowToToggleZenMode');
-        var activated   = $filter('translate')('ZenMode_Activated');
-
-        var message = '<ul><li>' + howToSearch + '</li><li>' + howToToggle + '</li></ul>';
-
-        var UI = require('piwik/UI');
-        var notification = new UI.Notification();
-        notification.show(message, {
-            title: activated,
-            context: 'info',
-            id: 'ZenMode_EnabledInfo'
-        });
-    }
-
-    return {
-        restrict: 'A',
-        compile: function (element, attrs) {
-
-            element.on('click', function() {
-                $rootElement.trigger('zen-mode-toggle', {});
-
-                if ($rootElement.hasClass('zenMode')) {
-                    showZenModeIsActivatedNotification();
-                }
-            });
-
-            return function () {
-            };
-        }
-    };
-
-});
\ No newline at end of file
diff --git a/plugins/ZenMode/angularjs/zen-mode/zen-mode-switcher.directive.js b/plugins/ZenMode/angularjs/zen-mode/zen-mode-switcher.directive.js
new file mode 100644
index 0000000000000000000000000000000000000000..36ac43a39a596df73366e6082377bd28d3a7e5e8
--- /dev/null
+++ b/plugins/ZenMode/angularjs/zen-mode/zen-mode-switcher.directive.js
@@ -0,0 +1,54 @@
+/*!
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+/**
+ * Usage:
+ * <div piwik-zen-mode-switcher>...</div>
+ * Will toggle the zen mode on click on this element.
+ */
+(function () {
+    angular.module('piwikApp').directive('piwikZenModeSwitcher', piwikZenModeSwitcher);
+
+    piwikZenModeSwitcher.$inject = ['$rootElement', '$filter'];
+
+    function piwikZenModeSwitcher($rootElement, $filter) {
+
+        function showZenModeIsActivatedNotification() {
+            var howToSearch = $filter('translate')('ZenMode_HowToSearch');
+            var howToToggle = $filter('translate')('ZenMode_HowToToggleZenMode');
+            var activated   = $filter('translate')('ZenMode_Activated');
+
+            var message = '<ul><li>' + howToSearch + '</li><li>' + howToToggle + '</li></ul>';
+
+            var UI = require('piwik/UI');
+            var notification = new UI.Notification();
+            notification.show(message, {
+                title: activated,
+                context: 'info',
+                id: 'ZenMode_EnabledInfo'
+            });
+        }
+
+        return {
+            restrict: 'A',
+            compile: function (element, attrs) {
+
+                element.on('click', function() {
+                    $rootElement.trigger('zen-mode-toggle', {});
+
+                    if ($rootElement.hasClass('zenMode')) {
+                        showZenModeIsActivatedNotification();
+                    }
+                });
+
+                return function () {
+                };
+            }
+        };
+
+    }
+})();
\ No newline at end of file
diff --git a/tests/angularjs/README.md b/tests/angularjs/README.md
index 53c2b611e5692671ea653c2addc799d5f1c76aff..a50b688e1bf3124ec6ca921720351a90de72e9f4 100644
--- a/tests/angularjs/README.md
+++ b/tests/angularjs/README.md
@@ -17,11 +17,11 @@ On Ubuntu you might be able to use the `scripts/install-ubuntu.sh` script. Have
 
 ## File structure
 
-We do not have a general `tests` folder containing all test files. Instead we create a file having the same name appended by `_spec.js` in the same directory.
+We do not have a general `tests` folder containing all test files. Instead we create a file having the same name appended by `.spec.js` in the same directory.
 
-For instance you want to test a file named `startfrom.js` then we create a file named `startfrom_spec.js`:
+For instance you want to test a file named `startfrom.js` then we create a file named `startfrom.spec.js`:
 `plugins/CoreHome/angularjs/common/filters/startfrom.js` =>
-`plugins/CoreHome/angularjs/common/filters/startfrom_spec.js`
+`plugins/CoreHome/angularjs/common/filters/startfrom.spec.js`
 
 ## Execution
 
@@ -44,7 +44,7 @@ Before executing a test it'll always run [JSHint](http://www.jshint.com/) to det
 Just in case you want to write a test for your jQuery code you can do this the same way. You might be interested in the [Chai jQuery](http://chaijs.com/plugins/chai-jquery) plugin.
 
 ## Examples
-* [Testing a filter](../../plugins/CoreHome/angularjs/common/filters/startfrom_spec.js)
-* [Testing a directive](../../plugins/CoreHome/angularjs/common/directives/autocomplete-matched_spec.js)
-* [Testing a service/provider/factory/model](../../plugins/CoreHome/angularjs/common/services/piwik_spec.js)
+* [Testing a filter](../../plugins/CoreHome/angularjs/common/filters/startfrom.spec.js)
+* [Testing a directive](../../plugins/CoreHome/angularjs/common/directives/autocomplete-matched.spec.js)
+* [Testing a service/provider/factory/model](../../plugins/CoreHome/angularjs/common/services/piwik.spec.js)
 * See more examples in [AngularJS guide](http://docs.angularjs.org/guide/unit-testing)
diff --git a/tests/angularjs/karma.conf.js b/tests/angularjs/karma.conf.js
index 641da9895b1cd744e500353be7d1bf0cf21a71b6..28889ffe942224b18d61f9fc3c122cffee97fcc6 100755
--- a/tests/angularjs/karma.conf.js
+++ b/tests/angularjs/karma.conf.js
@@ -25,12 +25,12 @@ module.exports = function(config) {
         "plugins/Morpheus/javascripts/piwikHelper.js",
         "plugins/Morpheus/javascripts/ajaxHelper.js",
         "plugins/CoreHome/javascripts/broadcast.js",
-        'plugins/CoreHome/angularjs/common/services/service.js',
-        'plugins/CoreHome/angularjs/common/filters/filter.js',
-        'plugins/CoreHome/angularjs/common/directives/directive.js',
+        'plugins/CoreHome/angularjs/common/services/service.module.js',
+        'plugins/CoreHome/angularjs/common/filters/filter.module.js',
+        'plugins/CoreHome/angularjs/common/directives/directive.module.js',
         'plugins/CoreHome/angularjs/piwikApp.js',
         'plugins/*/angularjs/**/*.js',
-        'plugins/*/angularjs/**/*_spec.js'
+        'plugins/*/angularjs/**/*.spec.js'
     ],
 
     // list of files to exclude