From bb99f42e0831b9f576885cd2d261e24e91c4cdd1 Mon Sep 17 00:00:00 2001
From: Thomas Steur <thomas.steur@googlemail.com>
Date: Sun, 16 Feb 2014 02:55:58 +0100
Subject: [PATCH] some more angular best practices

---
 .../templates/trackingCodeGenerator.twig      | 12 ++--
 plugins/CoreHome/CoreHome.php                 | 23 +++++--
 .../directives/autocompleteMatched.js         | 31 +++++++++
 .../javascripts/directives/directives.js      | 60 +-----------------
 .../directives/focusAnywhereButHere.js        | 33 ++++++++++
 .../javascripts/directives/ignoreClick.js     | 14 +++++
 .../CoreHome/javascripts/filters/filters.js   |  5 +-
 .../javascripts/filters/htmldecode.js         | 12 ++++
 .../CoreHome/javascripts/filters/translate.js | 12 ++++
 plugins/CoreHome/javascripts/piwikApp.js      |  7 ++-
 .../{providers => services}/PiwikApi.js       | 19 +++++-
 .../{providers => services}/piwik.js          |  3 +-
 .../CoreHome/javascripts/services/services.js |  8 +++
 .../javascripts/siteselector/directives.js    | 63 -------------------
 ...ontroller.js => siteSelectorController.js} |  1 -
 .../siteselector/siteSelectorDirectives.js    | 63 +++++++++++++++++++
 .../{providers.js => siteSelectorModel.js}    | 41 ++++++------
 ...{partial.html => siteSelectorPartial.html} | 20 ++----
 plugins/UsersManager/templates/index.twig     |  6 +-
 .../UsersManager/templates/userSettings.twig  |  4 +-
 .../Zeitgeist/stylesheets/ui/_siteSelect.less | 12 ++++
 21 files changed, 270 insertions(+), 179 deletions(-)
 create mode 100644 plugins/CoreHome/javascripts/directives/autocompleteMatched.js
 create mode 100644 plugins/CoreHome/javascripts/directives/focusAnywhereButHere.js
 create mode 100644 plugins/CoreHome/javascripts/directives/ignoreClick.js
 create mode 100644 plugins/CoreHome/javascripts/filters/htmldecode.js
 create mode 100644 plugins/CoreHome/javascripts/filters/translate.js
 rename plugins/CoreHome/javascripts/{providers => services}/PiwikApi.js (90%)
 rename plugins/CoreHome/javascripts/{providers => services}/piwik.js (66%)
 create mode 100644 plugins/CoreHome/javascripts/services/services.js
 delete mode 100644 plugins/CoreHome/javascripts/siteselector/directives.js
 rename plugins/CoreHome/javascripts/siteselector/{controller.js => siteSelectorController.js} (97%)
 create mode 100644 plugins/CoreHome/javascripts/siteselector/siteSelectorDirectives.js
 rename plugins/CoreHome/javascripts/siteselector/{providers.js => siteSelectorModel.js} (74%)
 rename plugins/CoreHome/javascripts/siteselector/{partial.html => siteSelectorPartial.html} (71%)

diff --git a/plugins/CoreAdminHome/templates/trackingCodeGenerator.twig b/plugins/CoreAdminHome/templates/trackingCodeGenerator.twig
index a70f9c40e7..1aadf4a0d2 100644
--- a/plugins/CoreAdminHome/templates/trackingCodeGenerator.twig
+++ b/plugins/CoreAdminHome/templates/trackingCodeGenerator.twig
@@ -30,10 +30,10 @@
         <div piwik-site-selector
              siteid="{{ idSite }}"
              sitename="{{ defaultReportSiteName }}"
-             showallsitesitem="false"
-             switchsiteonselect="false"
+             show-all-sites-item="false"
+             switch-site-on-select="false"
              id="js-tracker-website"
-             showselectedsite="true"></div>
+             show-selected-site="true"></div>
 
         <br/><br/><br/>
     </div>
@@ -202,10 +202,10 @@
         <div piwik-site-selector
              siteid="{{ idSite }}"
              sitename="{{ defaultReportSiteName }}"
-             showallsitesitem="false"
-             switchsiteonselect="false"
              id="image-tracker-website"
-             showselectedsite="true"></div>
+             show-all-sites-item="false"
+             switch-site-on-select="false"
+             show-selected-site="true"></div>
 
         <br/><br/><br/>
     </div>
diff --git a/plugins/CoreHome/CoreHome.php b/plugins/CoreHome/CoreHome.php
index 6784cf7a7a..f751a43e37 100644
--- a/plugins/CoreHome/CoreHome.php
+++ b/plugins/CoreHome/CoreHome.php
@@ -90,14 +90,25 @@ class CoreHome extends \Piwik\Plugin
         $jsFiles[] = "plugins/CoreHome/javascripts/color_manager.js";
         $jsFiles[] = "plugins/CoreHome/javascripts/notification.js";
         $jsFiles[] = "plugins/CoreHome/javascripts/notification_parser.js";
-        $jsFiles[] = "plugins/CoreHome/javascripts/piwikApp.js";
+
+        $jsFiles[] = "plugins/CoreHome/javascripts/services/services.js";
+        $jsFiles[] = "plugins/CoreHome/javascripts/services/piwik.js";
+        $jsFiles[] = "plugins/CoreHome/javascripts/services/piwikApi.js";
+
         $jsFiles[] = "plugins/CoreHome/javascripts/filters/filters.js";
-        $jsFiles[] = "plugins/CoreHome/javascripts/providers/PiwikApi.js";
-        $jsFiles[] = "plugins/CoreHome/javascripts/providers/piwik.js";
+        $jsFiles[] = "plugins/CoreHome/javascripts/filters/htmldecode.js";
+        $jsFiles[] = "plugins/CoreHome/javascripts/filters/translate.js";
+
         $jsFiles[] = "plugins/CoreHome/javascripts/directives/directives.js";
-        $jsFiles[] = "plugins/CoreHome/javascripts/siteselector/providers.js";
-        $jsFiles[] = "plugins/CoreHome/javascripts/siteselector/controller.js";
-        $jsFiles[] = "plugins/CoreHome/javascripts/siteselector/directives.js";
+        $jsFiles[] = "plugins/CoreHome/javascripts/directives/autocompleteMatched.js";
+        $jsFiles[] = "plugins/CoreHome/javascripts/directives/focusAnywhereButHere.js";
+        $jsFiles[] = "plugins/CoreHome/javascripts/directives/ignoreClick.js";
+
+        $jsFiles[] = "plugins/CoreHome/javascripts/piwikApp.js";
+        
+        $jsFiles[] = "plugins/CoreHome/javascripts/siteselector/siteSelectorModel.js";
+        $jsFiles[] = "plugins/CoreHome/javascripts/siteselector/siteSelectorController.js";
+        $jsFiles[] = "plugins/CoreHome/javascripts/siteselector/siteSelectorDirectives.js";
     }
 
     public function getClientSideTranslationKeys(&$translationKeys)
diff --git a/plugins/CoreHome/javascripts/directives/autocompleteMatched.js b/plugins/CoreHome/javascripts/directives/autocompleteMatched.js
new file mode 100644
index 0000000000..3e17c897f9
--- /dev/null
+++ b/plugins/CoreHome/javascripts/directives/autocompleteMatched.js
@@ -0,0 +1,31 @@
+/*!
+ * Piwik - Web Analytics
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+piwikAppDirectives.directive('piwikAutocompleteMatched', function() {
+    return function(scope, element, attrs) {
+        var searchTerm;
+
+        scope.$watch(attrs.piwikAutocompleteMatched, function(value) {
+            searchTerm = value;
+            updateText();
+        });
+
+        function updateText () {
+            if (!searchTerm || !element) {
+                return;
+            }
+
+            var content   = element.text();
+            var startTerm = content.toLowerCase().indexOf(searchTerm);
+            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
diff --git a/plugins/CoreHome/javascripts/directives/directives.js b/plugins/CoreHome/javascripts/directives/directives.js
index 0c6201f753..0107162d9e 100644
--- a/plugins/CoreHome/javascripts/directives/directives.js
+++ b/plugins/CoreHome/javascripts/directives/directives.js
@@ -5,62 +5,4 @@
  * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
  */
 
-piwikApp.directive('piwikFocusAnywhereButHere', function($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);
-                }
-            }
-
-            $document.on('keyup', onEscapeHandler);
-            $document.on('mouseup', onClickOutsideElement);
-            scope.$on('$destroy', function() {
-                $document.off('mouseup', onClickOutsideElement);
-                $document.off('keyup', onEscapeHandler);
-            });
-        }
-    }
-});
-
-piwikApp.directive('piwikIgnoreClick', function() {
-    return function(scope, element, attrs) {
-        $(element).click(function(event) {
-            event.preventDefault();
-        });
-    }
-})
-
-piwikApp.directive('piwikAutocompleteMatched', function() {
-    return function(scope, element, attrs) {
-        var searchTerm;
-
-        scope.$watch(attrs.piwikAutocompleteMatched, function(value) {
-            searchTerm = value;
-            updateText();
-        });
-
-        function updateText () {
-            if (!searchTerm || !element) {
-                return;
-            }
-
-            var content   = element.text();
-            var startTerm = content.toLowerCase().indexOf(searchTerm);
-            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
+var piwikAppDirectives = angular.module('piwikApp.directive', []);
diff --git a/plugins/CoreHome/javascripts/directives/focusAnywhereButHere.js b/plugins/CoreHome/javascripts/directives/focusAnywhereButHere.js
new file mode 100644
index 0000000000..34dac376d0
--- /dev/null
+++ b/plugins/CoreHome/javascripts/directives/focusAnywhereButHere.js
@@ -0,0 +1,33 @@
+/*!
+ * Piwik - Web Analytics
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+piwikAppDirectives.directive('piwikFocusAnywhereButHere', function($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);
+                }
+            }
+
+            $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/javascripts/directives/ignoreClick.js b/plugins/CoreHome/javascripts/directives/ignoreClick.js
new file mode 100644
index 0000000000..1f2148c13c
--- /dev/null
+++ b/plugins/CoreHome/javascripts/directives/ignoreClick.js
@@ -0,0 +1,14 @@
+/*!
+ * Piwik - Web Analytics
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+piwikAppDirectives.directive('piwikIgnoreClick', function() {
+    return function(scope, element, attrs) {
+        $(element).click(function(event) {
+            event.preventDefault();
+        });
+    }
+});
\ No newline at end of file
diff --git a/plugins/CoreHome/javascripts/filters/filters.js b/plugins/CoreHome/javascripts/filters/filters.js
index f4ebc26774..2a2e1359c5 100644
--- a/plugins/CoreHome/javascripts/filters/filters.js
+++ b/plugins/CoreHome/javascripts/filters/filters.js
@@ -4,14 +4,15 @@
  * @link http://piwik.org
  * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
  */
+var piwikAppFilters = angular.module('piwikApp.filter', []);
 
-piwikApp.filter('translate', function() {
+piwikAppFilters.filter('translate', function() {
     return function(key) {
         return _pk_translate(key);
     }
 });
 
-piwikApp.filter('htmldecode', function() {
+piwikAppFilters.filter('htmldecode', function() {
     return function(theString) {
         return piwikHelper.htmlDecode(theString);
     }
diff --git a/plugins/CoreHome/javascripts/filters/htmldecode.js b/plugins/CoreHome/javascripts/filters/htmldecode.js
new file mode 100644
index 0000000000..0bd1215064
--- /dev/null
+++ b/plugins/CoreHome/javascripts/filters/htmldecode.js
@@ -0,0 +1,12 @@
+/*!
+ * Piwik - Web Analytics
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+piwikAppFilters.filter('htmldecode', function() {
+    return function(theString) {
+        return piwikHelper.htmlDecode(theString);
+    }
+});
diff --git a/plugins/CoreHome/javascripts/filters/translate.js b/plugins/CoreHome/javascripts/filters/translate.js
new file mode 100644
index 0000000000..fc3ba3551f
--- /dev/null
+++ b/plugins/CoreHome/javascripts/filters/translate.js
@@ -0,0 +1,12 @@
+/*!
+ * Piwik - Web Analytics
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+piwikAppFilters.filter('translate', function() {
+    return function(key) {
+        return _pk_translate(key);
+    }
+});
\ No newline at end of file
diff --git a/plugins/CoreHome/javascripts/piwikApp.js b/plugins/CoreHome/javascripts/piwikApp.js
index 57376fb346..42d8cc8981 100644
--- a/plugins/CoreHome/javascripts/piwikApp.js
+++ b/plugins/CoreHome/javascripts/piwikApp.js
@@ -1,2 +1,7 @@
-var piwikApp  = angular.module('piwikApp', ['ngSanitize']);
+var piwikApp = angular.module('piwikApp', [
+    'ngSanitize',
+    'piwikApp.service',
+    'piwikApp.directive',
+    'piwikApp.filter'
+]);
 var customApp = angular.module('app', []);
diff --git a/plugins/CoreHome/javascripts/providers/PiwikApi.js b/plugins/CoreHome/javascripts/services/PiwikApi.js
similarity index 90%
rename from plugins/CoreHome/javascripts/providers/PiwikApi.js
rename to plugins/CoreHome/javascripts/services/PiwikApi.js
index 77a6d4db50..de96f82b09 100644
--- a/plugins/CoreHome/javascripts/providers/PiwikApi.js
+++ b/plugins/CoreHome/javascripts/services/PiwikApi.js
@@ -5,12 +5,13 @@
  * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
  */
 
-piwikApp.factory('piwikApi', function ($http, $q, $rootScope, piwik, $window) {
+piwikAppServices.factory('piwikApi', function ($http, $q, $rootScope, piwik, $window) {
 
     var url = 'index.php';
     var format = 'json';
     var getParams  = {};
     var postParams = {};
+    var requestHandle = null;
 
     var piwikApi = {};
 
@@ -39,13 +40,14 @@ piwikApp.factory('piwikApi', function ($http, $q, $rootScope, piwik, $window) {
      */
     function send (cacheResult) {
 
-        var deferred = $q.defer();
+        var deferred = requestHandle = $q.defer();
 
         var onError = function (message) {
             deferred.reject(message);
+            requestHandle = null;
         };
 
-        var onSuccess  = function (response) {
+        var onSuccess = function (response) {
             if (response && response.result == 'error') {
 
                 if (response.message) {
@@ -66,6 +68,7 @@ piwikApp.factory('piwikApi', function ($http, $q, $rootScope, piwik, $window) {
             } else {
                 deferred.resolve(response);
             }
+            requestHandle = null;
         };
 
         var headers = {'Content-Type': 'application/x-www-form-urlencoded'};
@@ -142,6 +145,16 @@ piwikApp.factory('piwikApi', function ($http, $q, $rootScope, piwik, $window) {
         return getParamsToMixin;
     };
 
+    piwikApi.abort = function () {
+        getParams  = {};
+        postParams = {};
+
+        if (requestHandle) {
+            requestHandle.resolve();
+            requestHandle = null;
+        }
+    };
+
     /**
      * Make a reading API request. Response is cached for fast future requests
      * @param getParams
diff --git a/plugins/CoreHome/javascripts/providers/piwik.js b/plugins/CoreHome/javascripts/services/piwik.js
similarity index 66%
rename from plugins/CoreHome/javascripts/providers/piwik.js
rename to plugins/CoreHome/javascripts/services/piwik.js
index a1b84be81b..e3bed16ada 100644
--- a/plugins/CoreHome/javascripts/providers/piwik.js
+++ b/plugins/CoreHome/javascripts/services/piwik.js
@@ -5,8 +5,9 @@
  * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
  */
 
+var piwikAppServices = angular.module('piwikApp.service', []);
 
-piwikApp.service('piwik', function () {
+piwikAppServices.service('piwik', function () {
 
     piwik.helper    = piwikHelper;
     piwik.broadcast = broadcast;
diff --git a/plugins/CoreHome/javascripts/services/services.js b/plugins/CoreHome/javascripts/services/services.js
new file mode 100644
index 0000000000..f223aa3ddc
--- /dev/null
+++ b/plugins/CoreHome/javascripts/services/services.js
@@ -0,0 +1,8 @@
+/*!
+ * Piwik - Web Analytics
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+var piwikAppServices = angular.module('piwikApp.service', []);
diff --git a/plugins/CoreHome/javascripts/siteselector/directives.js b/plugins/CoreHome/javascripts/siteselector/directives.js
deleted file mode 100644
index da813538df..0000000000
--- a/plugins/CoreHome/javascripts/siteselector/directives.js
+++ /dev/null
@@ -1,63 +0,0 @@
-/*!
- * Piwik - Web Analytics
- *
- * @link http://piwik.org
- * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
- */
-
-piwikApp.directive('piwikSiteSelector', function($document, piwik, $filter){
-
-    return {
-        restrict: 'A',
-        // why not directly use camel case and for example site-name? If I remember correct this does not work
-        // in all of or required IE versions. Alternative can be to define a namespace attribute and prefix all
-        // attributes with piwik, eg. piwik-site-name. Again: Not sure if I remember correct, will have a look
-        // later
-        scope: {
-            showAutocomplete: '=showautocomplete',
-            showSelectedSite: '=showselectedsite',
-            showAllSitesItem: '=showallsitesitem',
-            switchSiteOnSelect: '=switchsiteonselect',
-            maxSitenameWidth: '=maxsitenamewidth',
-            inputName: '@inputname',
-            allSitesText: '@allsitestext',
-            allSitesLocation: '@allsiteslocation'
-        },
-        templateUrl: 'plugins/CoreHome/javascripts/siteselector/partial.html',
-        controller: 'SiteSelectorController',
-        compile: function (element, attrs) {
-            attrs.$addClass('sites_autocomplete');
-
-            // define default values
-            if (!attrs.allsiteslocation) attrs.allsiteslocation = 'bottom';
-            if (!attrs.allsitestext) attrs.allsitestext = $filter('translate')('General_MultiSitesSummary');
-            if (!attrs.siteid) attrs.siteid = '';
-            if (!attrs.sitename) attrs.sitename = '';
-            if (!attrs.inputname) attrs.inputname = '';
-            if (!attrs.showautocomplete) attrs.showautocomplete = 'true';
-            if (!attrs.showselectedsite) attrs.showselectedsite = 'false';
-            if (!attrs.switchsiteonselect) attrs.switchsiteonselect = 'true';
-            if (!attrs.showallsitesitem) attrs.showallsitesitem = 'true';
-            if (!attrs.maxSitenameWidth) attrs.maxsitenamewidth = '130'; // can be removed?
-
-            return function (scope, element, attrs) {
-
-                // selectedSite.id|.name is hard-coded but actually the directive should not know about this
-                scope.selectedSite.id   = attrs.siteid;
-                scope.selectedSite.name = attrs.sitename;
-
-                scope.$watch('selectedSite.id', function (newValue, oldValue, scope) {
-                    if (newValue != oldValue) {
-                        element.attr('siteid', newValue);
-                        element.trigger('change', scope.selectedSite);
-                    }
-                });
-
-                /** use observe to monitor attribute changes
-                attrs.$observe('maxsitenamewidth', function(val) {
-                    // for instance trigger a function or whatever
-                }) */
-            }
-        }
-    }
-});
\ No newline at end of file
diff --git a/plugins/CoreHome/javascripts/siteselector/controller.js b/plugins/CoreHome/javascripts/siteselector/siteSelectorController.js
similarity index 97%
rename from plugins/CoreHome/javascripts/siteselector/controller.js
rename to plugins/CoreHome/javascripts/siteselector/siteSelectorController.js
index a2efa8ec91..7e43787c1c 100644
--- a/plugins/CoreHome/javascripts/siteselector/controller.js
+++ b/plugins/CoreHome/javascripts/siteselector/siteSelectorController.js
@@ -8,7 +8,6 @@
 piwikApp.controller('SiteSelectorController', function($scope, siteSelectorModel, piwik){
 
     $scope.model = siteSelectorModel;
-    $scope.model.loadInitialSites();
 
     $scope.selectedSite = {id: '', name: ''};
     $scope.activeSiteId = piwik.idSite;
diff --git a/plugins/CoreHome/javascripts/siteselector/siteSelectorDirectives.js b/plugins/CoreHome/javascripts/siteselector/siteSelectorDirectives.js
new file mode 100644
index 0000000000..cf317d84b9
--- /dev/null
+++ b/plugins/CoreHome/javascripts/siteselector/siteSelectorDirectives.js
@@ -0,0 +1,63 @@
+/*!
+ * Piwik - Web Analytics
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+piwikApp.directive('piwikSiteSelector', function($document, piwik, $filter){
+    var defaults = {
+        'name': '',
+        'siteid': piwik.idSite,
+        'sitename': piwik.siteName,
+        'all-sites-location': 'bottom',
+        'all-sites-text': $filter('translate')('General_MultiSitesSummary'),
+        'show-selected-site': 'false',
+        'show-all-sites-item': 'true',
+        'switch-site-on-select': 'true',
+    };
+
+    return {
+        restrict: 'A',
+        scope: {
+            showSelectedSite: '=',
+            showAllSitesItem: '=',
+            switchSiteOnSelect: '=',
+            inputName: '@name',
+            allSitesText: '@',
+            allSitesLocation: '@'
+        },
+        templateUrl: 'plugins/CoreHome/javascripts/siteselector/siteSelectorPartial.html',
+        controller: 'SiteSelectorController',
+        compile: function (element, attrs) {
+            attrs.$addClass('sites_autocomplete');
+
+            for (var index in defaults) {
+               if (!attrs[index]) { attrs[index] = defaults[index]; }
+            }
+
+            return function (scope, element, attrs) {
+
+                // selectedSite.id|.name + model is hard-coded but actually the directive should not know about this
+                scope.selectedSite.id   = attrs.siteid;
+                scope.selectedSite.name = attrs.sitename;
+
+                if (!attrs.siteid || !attrs.sitename) {
+                    scope.model.loadInitialSites();
+                }
+
+                scope.$watch('selectedSite.id', function (newValue, oldValue, scope) {
+                    if (newValue != oldValue) {
+                        element.attr('siteid', newValue);
+                        element.trigger('change', scope.selectedSite);
+                    }
+                });
+
+                /** use observe to monitor attribute changes
+                attrs.$observe('maxsitenamewidth', function(val) {
+                    // for instance trigger a function or whatever
+                }) */
+            }
+        }
+    }
+});
\ No newline at end of file
diff --git a/plugins/CoreHome/javascripts/siteselector/providers.js b/plugins/CoreHome/javascripts/siteselector/siteSelectorModel.js
similarity index 74%
rename from plugins/CoreHome/javascripts/siteselector/providers.js
rename to plugins/CoreHome/javascripts/siteselector/siteSelectorModel.js
index f936112c1d..109b298bf7 100644
--- a/plugins/CoreHome/javascripts/siteselector/providers.js
+++ b/plugins/CoreHome/javascripts/siteselector/siteSelectorModel.js
@@ -1,6 +1,5 @@
 
 piwikApp.factory('siteSelectorModel', function (piwikApi, $filter) {
-    var filterLimit = 10;
 
     var model = {};
     model.sites = [];
@@ -8,6 +7,24 @@ piwikApp.factory('siteSelectorModel', function (piwikApi, $filter) {
     model.isLoading = false;
     model.firstSiteName = '';
 
+    function fetchAndUpdate(params)
+    {
+        if (model.isLoading) {
+            piwikApi.abort();
+        }
+
+        model.isLoading = true;
+
+        params.filter_limit = 10;
+        params.showColumns  = 'name,idsite';
+
+        piwikApi.fetch(params).then(function (response) {
+            model.updateWebsitesList(response);
+        }).finally(function () {
+            model.isLoading = false;
+        });
+    }
+
     model.updateWebsitesList = function (websites) {
 
         if (!websites || !websites.length) {
@@ -33,29 +50,17 @@ piwikApp.factory('siteSelectorModel', function (piwikApi, $filter) {
             model.loadInitialSites();
             return;
         }
-        model.isLoading = true;
-        piwikApi.fetch({
+
+        fetchAndUpdate({
             method: 'SitesManager.getPatternMatchSites',
-            filter_limit: filterLimit,
             pattern: term
-        }).then(function (response) {
-            model.updateWebsitesList(response);
-        }).finally(function () {
-            model.isLoading = false;
         });
     };
 
     model.loadInitialSites = function () {
-        model.isLoading = true;
-        piwikApi.fetch({
-            method: 'SitesManager.getSitesWithAtLeastViewAccess',
-            filter_limit: filterLimit,
-            showColumns: 'name,idsite'
-        }).then(function (response) {
-            model.updateWebsitesList(response);
-        }).finally(function () {
-            model.isLoading = false;
-        });
+        fetchAndUpdate({
+            method: 'SitesManager.getSitesWithAtLeastViewAccess'
+        })
     }
 
     return model;
diff --git a/plugins/CoreHome/javascripts/siteselector/partial.html b/plugins/CoreHome/javascripts/siteselector/siteSelectorPartial.html
similarity index 71%
rename from plugins/CoreHome/javascripts/siteselector/partial.html
rename to plugins/CoreHome/javascripts/siteselector/siteSelectorPartial.html
index 197cdb293c..a6e6bfc24a 100644
--- a/plugins/CoreHome/javascripts/siteselector/partial.html
+++ b/plugins/CoreHome/javascripts/siteselector/siteSelectorPartial.html
@@ -1,4 +1,4 @@
-<div ng-class="{'sites_autocomplete--dropdown': (model.hasMultipleWebsites || showAllSitesItem)}">
+<div ng-class="{'sites_autocomplete--dropdown': (model.hasMultipleWebsites || showAllSitesItem || !model.sites.length)}">
     <div piwik-focus-anywhere-but-here="view.showSitesList=false" class="custom_select">
 
         <script type="text/ng-template" id="siteselector_allsiteslink.html">
@@ -16,19 +16,13 @@
            href="javascript:void(0)"
            class="custom_select_main_link"
            ng-class="{'loading': model.isLoading}">
-            <span ng-bind-html="selectedSite.name || (initialSiteName || model.firstSiteName)"></span>
+            <span ng-bind-html="selectedSite.name || model.firstSiteName">?</span>
         </a>
 
         <div ng-show="view.showSitesList" class="custom_select_block">
             <div ng-if="allSitesLocation=='top' && showAllSitesItem"
                  ng-include="'siteselector_allsiteslink.html'"></div>
 
-            <div ng-click="switchSite({idsite: 'all', name: allSitesText})"
-                 ng-if="allSitesLocation=='top' && showAllSitesItem"
-                 class="custom_select_all">
-                <a href="{{ getUrlAllSites() }}" ng-bind-html="allSitesText"></a>
-            </div>
-
             <div class="custom_select_container">
                 <ul class="custom_select_ul_list">
                     <li ng-click="view.showSitesList=false; switchSite(site)"
@@ -37,10 +31,9 @@
                         <a piwik-ignore-click href="{{ getUrlForSiteId(site.idsite) }}" piwik-autocomplete-matched="view.searchTerm">{{ site.name }}</a>
                     </li>
                 </ul>
-                <ul ng-show="!model.sites.length" class="ui-autocomplete ui-front ui-menu ui-widget ui-widget-content ui-corner-all siteSelect">
-                    <li class="ui-menu-item"
-                        style="float:none;position:static">
-                        <a class="ui-corner-all" style="float:none;position:static" tabindex="-1">{{ ('SitesManager_NotFound' | translate) + ' ' + view.searchTerm }}</a>
+                <ul ng-show="!model.sites.length && view.searchTerm" class="ui-autocomplete ui-front ui-menu ui-widget ui-widget-content ui-corner-all siteSelect">
+                    <li class="ui-menu-item">
+                        <a class="ui-corner-all" tabindex="-1">{{ ('SitesManager_NotFound' | translate) + ' ' + view.searchTerm }}</a>
                     </li>
                 </ul>
             </div>
@@ -48,7 +41,7 @@
             <div ng-if="allSitesLocation=='bottom' && showAllSitesItem"
                  ng-include="'siteselector_allsiteslink.html'"></div>
 
-            <div class="custom_select_search" ng-show="showAutocomplete">
+            <div class="custom_select_search" ng-show="model.hasMultipleWebsites">
                 <input type="text"
                        ng-click="view.searchTerm=''"
                        ng-model="view.searchTerm"
@@ -60,7 +53,6 @@
                      ng-show="view.searchTerm"
                      ng-click="view.searchTerm=''; model.loadInitialSites()"
                      class="reset"
-                     style="position: relative; top: 4px; left: -44px; cursor: pointer;"
                      src="plugins/CoreHome/images/reset_search.png"/>
             </div>
         </div>
diff --git a/plugins/UsersManager/templates/index.twig b/plugins/UsersManager/templates/index.twig
index 3b19bae88c..e0ba163e63 100644
--- a/plugins/UsersManager/templates/index.twig
+++ b/plugins/UsersManager/templates/index.twig
@@ -16,10 +16,10 @@
         <div piwik-site-selector
              siteid="{{ idSiteSelected }}"
              sitename="{{ defaultReportSiteName }}"
-             allsitestext="{{ applyAllSitesText|raw }}"
-             allsiteslocation="top"
+             all-sites-text="{{ applyAllSitesText|raw }}"
+             all-sites-location="top"
              id="usersManagerSiteSelect"
-             switchsiteonselect="false"></div>
+             switch-site-on-select="false"></div>
     </section>
 </div>
 
diff --git a/plugins/UsersManager/templates/userSettings.twig b/plugins/UsersManager/templates/userSettings.twig
index 9f07412d91..0f086f5f32 100644
--- a/plugins/UsersManager/templates/userSettings.twig
+++ b/plugins/UsersManager/templates/userSettings.twig
@@ -47,8 +47,8 @@
                 <div piwik-site-selector
                      siteid="{{ defaultReportIdSite }}"
                      sitename="{{ defaultReportSiteName }}"
-                     switchsiteonselect="false"
-                     showallsitesitem="false"
+                     switch-site-on-select="false"
+                     show-all-sites-item="false"
                      showselectedsite="true"
                      id="defaultReportSiteSelector"></div>
             </fieldset>
diff --git a/plugins/Zeitgeist/stylesheets/ui/_siteSelect.less b/plugins/Zeitgeist/stylesheets/ui/_siteSelect.less
index cd6dd28ff5..806a57684e 100644
--- a/plugins/Zeitgeist/stylesheets/ui/_siteSelect.less
+++ b/plugins/Zeitgeist/stylesheets/ui/_siteSelect.less
@@ -126,6 +126,9 @@
   color: #454545;
 
 }
+.sites_autocomplete {
+  width: 165px;
+}
 
 .sites_autocomplete .custom_select_search .but {
   vertical-align: top;
@@ -143,6 +146,15 @@
   padding-left: 12px;
 }
 
+.custom_selector_container .ui-menu-item,
+.custom_selector_container .ui-menu-item a {
+  float:none;position:static
+}
+
+.custom_select_search .reset {
+  position: relative; top: 4px; left: -44px; cursor: pointer;
+}
+
 .custom_select_block {
   overflow: hidden;
   max-width: inherit
-- 
GitLab