From 7e21b616285d198e39eadb1c1be3c4498d0f6319 Mon Sep 17 00:00:00 2001
From: Thomas Steur <thomas.steur@googlemail.com>
Date: Thu, 13 Feb 2014 07:50:26 +0100
Subject: [PATCH] started to integrate angularjs by rewriting the websites
 selector, it is still in progress and it is to defined how we want to deliver
 our templates and where we want to place our files, still a lot of work todo

---
 plugins/CoreHome/CoreHome.php                 |   8 +
 .../javascripts/dependencies/PiwikApi.js      | 202 ++++++++++++++++++
 .../javascripts/directives/directives.js      |  57 +++++
 .../CoreHome/javascripts/filters/filters.js   |  21 ++
 plugins/CoreHome/javascripts/piwikApp.js      |   2 +
 .../siteselector/siteSelector.tpl.html        |  49 +++++
 .../siteselector/siteSelectorController.js    |  91 ++++++++
 .../javascripts/siteselector/tests.js         |  17 ++
 plugins/CoreHome/templates/_siteSelect.twig   |  68 +-----
 plugins/Morpheus/stylesheets/forms.less       |   2 +-
 .../Zeitgeist/stylesheets/ui/_siteSelect.less |   7 +-
 plugins/Zeitgeist/templates/dashboard.twig    |   6 +-
 12 files changed, 455 insertions(+), 75 deletions(-)
 create mode 100644 plugins/CoreHome/javascripts/dependencies/PiwikApi.js
 create mode 100644 plugins/CoreHome/javascripts/directives/directives.js
 create mode 100644 plugins/CoreHome/javascripts/filters/filters.js
 create mode 100644 plugins/CoreHome/javascripts/piwikApp.js
 create mode 100644 plugins/CoreHome/javascripts/siteselector/siteSelector.tpl.html
 create mode 100644 plugins/CoreHome/javascripts/siteselector/siteSelectorController.js
 create mode 100644 plugins/CoreHome/javascripts/siteselector/tests.js

diff --git a/plugins/CoreHome/CoreHome.php b/plugins/CoreHome/CoreHome.php
index 5d9a523a59..5e24324539 100644
--- a/plugins/CoreHome/CoreHome.php
+++ b/plugins/CoreHome/CoreHome.php
@@ -68,6 +68,7 @@ class CoreHome extends \Piwik\Plugin
         $jsFiles[] = "libs/jquery/jquery.mousewheel.js";
         $jsFiles[] = "libs/jquery/mwheelIntent.js";
         $jsFiles[] = "libs/javascript/sprintf.js";
+        $jsFiles[] = "libs/angularjs/angular.js";
         $jsFiles[] = "plugins/Zeitgeist/javascripts/piwikHelper.js";
         $jsFiles[] = "plugins/Zeitgeist/javascripts/ajaxHelper.js";
         $jsFiles[] = "plugins/CoreHome/javascripts/require.js";
@@ -89,6 +90,11 @@ 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/filters/filters.js";
+        $jsFiles[] = "plugins/CoreHome/javascripts/dependencies/PiwikApi.js";
+        $jsFiles[] = "plugins/CoreHome/javascripts/directives/directives.js";
+        $jsFiles[] = "plugins/CoreHome/javascripts/siteselector/siteSelectorController.js";
     }
 
     public function getClientSideTranslationKeys(&$translationKeys)
@@ -98,6 +104,7 @@ class CoreHome extends \Piwik\Plugin
         $translationKeys[] = 'General_Show';
         $translationKeys[] = 'General_Hide';
         $translationKeys[] = 'General_YearShort';
+        $translationKeys[] = 'General_MultiSitesSummary';
         $translationKeys[] = 'CoreHome_YouAreUsingTheLatestVersion';
         $translationKeys[] = 'CoreHome_IncludeRowsWithLowPopulation';
         $translationKeys[] = 'CoreHome_ExcludeRowsWithLowPopulation';
@@ -107,6 +114,7 @@ class CoreHome extends \Piwik\Plugin
         $translationKeys[] = 'CoreHome_PageOf';
         $translationKeys[] = 'CoreHome_FlattenDataTable';
         $translationKeys[] = 'CoreHome_UnFlattenDataTable';
+        $translationKeys[] = 'SitesManager_NotFound';
         $translationKeys[] = 'Annotations_ViewAndAddAnnotations';
         $translationKeys[] = 'General_RowEvolutionRowActionTooltipTitle';
         $translationKeys[] = 'General_RowEvolutionRowActionTooltip';
diff --git a/plugins/CoreHome/javascripts/dependencies/PiwikApi.js b/plugins/CoreHome/javascripts/dependencies/PiwikApi.js
new file mode 100644
index 0000000000..87328e6239
--- /dev/null
+++ b/plugins/CoreHome/javascripts/dependencies/PiwikApi.js
@@ -0,0 +1,202 @@
+/*!
+ * Piwik - Web Analytics
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+piwikApp.factory('piwikApi', function ($http, $q, $rootScope) {
+
+    var url = 'index.php';
+    var format = 'json';
+    var getParams  = {};
+    var postParams = {};
+
+    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
+     * @param {string}  type  type of given parameters (POST or GET)
+     * @return {void}
+     */
+    piwikApi.addParams = function (params, type) {
+        if (typeof params == 'string') {
+            params = broadcast.getValuesFromUrl(params);
+        }
+
+        for (var key in params) {
+            if(type.toLowerCase() == 'get') {
+                getParams[key] = params[key];
+            } else if(type.toLowerCase() == 'post') {
+                postParams[key] = params[key];
+            }
+        }
+    };
+
+    /**
+     * Sets the base URL to use in the AJAX request.
+     *
+     * @param {string} url
+     */
+    piwikApi.setUrl = function (url) {
+        this.addParams(broadcast.getValuesFromUrl(url), 'GET');
+    };
+
+    /**
+     * Gets this helper instance ready to send a bulk request. Each argument to this
+     * function is a single request to use.
+     */
+    piwikApi.setBulkRequests = function () {
+        var urls = [];
+        for (var i = 0; i != arguments.length; ++i) {
+            urls.push($.param(arguments[i]));
+        }
+
+        this.addParams({
+            module: 'API',
+            method: 'API.getBulkRequest',
+            urls: urls,
+            format: 'json'
+        }, 'post');
+    };
+
+    /**
+     * Sets the response format for the request
+     *
+     * @param {string} theFormat  response format (e.g. json, html, ...)
+     * @return {void}
+     */
+    piwikApi.setFormat = function (theFormat) {
+        format = theFormat;
+    };
+
+    /**
+     * Send the request
+     * @param {Boolean} [sync]  indicates if the request should be synchronous (defaults to false)
+     * @return {void}
+     */
+    piwikApi.send = function () {
+
+        var deferred = $q.defer();
+
+        var onError = function (message) {
+            deferred.reject(message);
+        };
+
+        var onSuccess  = function (response) {
+            if (response && response.result == 'error') {
+
+                if (response.message) {
+                    onError(response.message);
+
+                    var UI = require('piwik/UI');
+                    var notification = new UI.Notification();
+                    notification.show(response.message, {
+                        context: 'error',
+                        type: 'toast',
+                        id: 'ajaxHelper'
+                    });
+                    notification.scrollToNotification();
+                } else {
+                    onError(null);
+                }
+
+            } else {
+                deferred.resolve(response);
+            }
+        };
+
+        var ajaxCall = {
+            method: 'POST',
+            url: url,
+            responseType: format,
+            params: _mixinDefaultGetParams(getParams),
+            data: $.param(_mixinDefaultPostParams(postParams)),
+            timeout: deferred.promise,
+            headers: {'Content-Type': 'application/x-www-form-urlencoded'}
+        };
+
+        $http(ajaxCall).success(onSuccess).error(onError);
+
+        return deferred.promise;
+    };
+
+    /**
+     * Mixin the default parameters to send as POST
+     *
+     * @param {object}   params   parameter object
+     * @return {object}
+     * @private
+     */
+     function _mixinDefaultPostParams (params) {
+
+        var defaultParams = {
+            token_auth: piwik.token_auth
+        };
+
+        for (var index in defaultParams) {
+            if (!params[index]) {
+                params[index] = defaultParams[index];
+            }
+        }
+
+        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 || broadcast.getValueFromUrl('idSite'),
+            period:  piwik.period || broadcast.getValueFromUrl('period'),
+            segment: 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;
+        }
+
+        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 || broadcast.getValueFromUrl('date');
+            if (getParamsToMixin.period == 'range' && piwik.currentDateString) {
+                getParamsToMixin.date = piwik.startDateString + ',' + getParamsToMixin.date;
+            }
+        }
+
+        return getParamsToMixin;
+    };
+
+    /**
+     * Convenient method for making an API request
+     * @param getParams
+     */
+    piwikApi.fetch = function (getParams) {
+
+        getParams.module = 'API';
+        getParams.format = 'JSON';
+
+        this.addParams(getParams, 'GET');
+
+        return this.send();
+    };
+
+    return piwikApi;
+});
diff --git a/plugins/CoreHome/javascripts/directives/directives.js b/plugins/CoreHome/javascripts/directives/directives.js
new file mode 100644
index 0000000000..028a54e8f6
--- /dev/null
+++ b/plugins/CoreHome/javascripts/directives/directives.js
@@ -0,0 +1,57 @@
+/*!
+ * Piwik - Web Analytics
+ *
+ * @link http://piwik.org
+ * @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('piwikAutocompleteMatched', function() {
+    return function(scope, element, attrs) {
+        var searchTerm;
+
+        scope.$watch(attrs.autocompleteMatched, function(value) {
+            searchTerm = value;
+            updateText();
+        });
+
+        function updateText () {
+            if (!searchTerm) {
+                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/filters/filters.js b/plugins/CoreHome/javascripts/filters/filters.js
new file mode 100644
index 0000000000..8e8002bf78
--- /dev/null
+++ b/plugins/CoreHome/javascripts/filters/filters.js
@@ -0,0 +1,21 @@
+/*!
+ * Piwik - Web Analytics
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+piwikApp.filter('translate', function() {
+    return function(key) {
+        return _pk_translate(key);
+    }
+});
+
+piwikApp.filter('default', function() {
+    return function(value, defaultValue) {
+        if (!value) {
+            return defaultValue;
+        }
+        return value;
+    }
+});
diff --git a/plugins/CoreHome/javascripts/piwikApp.js b/plugins/CoreHome/javascripts/piwikApp.js
new file mode 100644
index 0000000000..36dc2c55c4
--- /dev/null
+++ b/plugins/CoreHome/javascripts/piwikApp.js
@@ -0,0 +1,2 @@
+var piwikApp  = angular.module('piwikApp', []);
+var customApp = angular.module('app', []);
diff --git a/plugins/CoreHome/javascripts/siteselector/siteSelector.tpl.html b/plugins/CoreHome/javascripts/siteselector/siteSelector.tpl.html
new file mode 100644
index 0000000000..2a01bf5f13
--- /dev/null
+++ b/plugins/CoreHome/javascripts/siteselector/siteSelector.tpl.html
@@ -0,0 +1,49 @@
+<div ng-class="{'sites_autocomplete--dropdown': (hasMultipleWebsites || showAllSitesItem)}"
+     id="{{ siteSelectorId }}">
+    <div piwik-focus-anywhere-but-here="showSitesList=false" class="custom_select">
+
+        <input ng-if="inputName" ng-model="selectedSiteId" type="hidden" name="{{ inputName }}" value=""/>
+
+        <a ng-click="showSitesList=!showSitesList; showSitesList && loadInitialSites()" href="javascript:void(0)"
+           class="custom_select_main_link" ng-class="{'loading': isLoading}"
+           data-siteid="{{ selectedSiteId }}">
+            <span>{{ siteName }}</span>
+        </a>
+
+        <div ng-show="showSitesList" class="custom_select_block">
+
+            <div ng-click="siteName='Ddd'" ng-show="allWebsitesLinkLocation=='top' && showAllSitesItem" class="custom_select_all" style="clear: both;">
+                <a href="#">
+                    {{ allSitesItemText | default:'General_MultiSitesSummary'|translate }}
+                </a>
+            </div>
+
+            <div class="custom_select_container">
+                <ul class="custom_select_ul_list">
+                    <li ng-focus="searchTerm=site.name" ng-click="$parent.showSitesList=false; switchSite(site)" ng-repeat="site in sites" ng-hide="!showSelectedSite && piwik.idSite==site.idsite">
+                        <a href="javascript:void(0)" piwik-autocomplete-matched="searchTerm">{{ site.name }}</a>
+                    </li>
+                </ul>
+                <ul ng-show="!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) + ' ' + searchTerm }}</a>
+                    </li>
+                </ul>
+            </div>
+
+            <div ng-click="siteName='Ddd'" ng-show="allWebsitesLinkLocation=='bottom' && showAllSitesItem" class="custom_select_all" style="clear: both;">
+                <a href="#">
+                    {{ allSitesItemText | default:'General_MultiSitesSummary'|translate }}
+                </a>
+            </div>
+
+            <div class="custom_select_search" ng-show="show_autocompleter">
+                <input type="text" ng-focus="showSitesList" ng-click="searchTerm=''" ng-model="searchTerm" ng-change="searchSite(searchTerm)" length="15" class="websiteSearch inp"/>
+                <input type="submit" value="Search" class="but"/>
+                <img title="Clear" ng-show="searchTerm" ng-click="searchTerm=''; loadInitialSites()" class="reset" style="position: relative; top: 4px; left: -44px; cursor: pointer;"
+                     src="plugins/CoreHome/images/reset_search.png"/>
+            </div>
+        </div>
+    </div>
+
+</div>
diff --git a/plugins/CoreHome/javascripts/siteselector/siteSelectorController.js b/plugins/CoreHome/javascripts/siteselector/siteSelectorController.js
new file mode 100644
index 0000000000..b099cdede4
--- /dev/null
+++ b/plugins/CoreHome/javascripts/siteselector/siteSelectorController.js
@@ -0,0 +1,91 @@
+/*!
+ * Piwik - Web Analytics
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+piwikApp.controller('SiteSelectorCtrl', ['$scope', 'piwikApi', function($scope, piwikApi){
+    var filterLimit = 10;
+
+    $scope.templateUrl = 'plugins/CoreHome/javascripts/siteselector//siteSelector.tpl.html';
+    $scope.allWebsitesLinkLocation = 'bottom';
+    $scope.sites = [];
+    $scope.showSelectedSite = false;
+    $scope.show_autocompleter = true;
+    $scope.siteSelectorId = '';
+    $scope.switchSiteOnSelect = false;
+    $scope.hasMultipleWebsites = false;
+    $scope.isLoading = false;
+    $scope.showAllSitesItem = true;
+    $scope.selectedSiteId = 0;
+    $scope.searchTerm = '';
+    $scope.max_sitename_width = 130; // can be removed?
+
+    $scope.switchSite = function (site) {
+        if (!$scope.switchSiteOnSelect || piwik.idSite == site.idsite) {
+            $scope.selectedSiteId = site.idsite;
+            $scope.siteName = site.name;
+            return;
+        }
+
+        if (site.idsite == 'all' && !$scope.showAllSitesItem) {
+            broadcast.propagateNewPage('module=MultiSites&action=index');
+        } else {
+            broadcast.propagateNewPage($scope.getUrlForWebsiteId(site.idsite), false);
+        }
+    };
+
+    $scope.getUrlForWebsiteId = function (idSite) {
+        var idSiteParam   = 'idSite=' + idSite;
+        var newParameters = 'segment=&' + idSiteParam;
+        var hash = broadcast.isHashExists() ? broadcast.getHashFromUrl() : "";
+        return piwikHelper.getCurrentQueryStringWithParametersModified(newParameters)
+            + '#' + piwikHelper.getQueryStringWithParametersModified(hash.substring(1), newParameters);
+    };
+
+    $scope.updateWebsitesList = function (websites) {
+        angular.forEach(websites, function (website) {
+            website.name = piwikHelper.htmlDecode(website.name);
+        });
+
+        $scope.sites = websites;
+
+        if (!$scope.siteName) {
+            $scope.siteName = websites[0].name;
+        }
+
+        $scope.hasMultipleWebsites = websites.length > 1;
+    };
+
+    $scope.searchSite = function (term) {
+        if (!term) {
+            $scope.loadInitialSites();
+            return;
+        }
+        $scope.isLoading = true;
+        piwikApi.fetch({
+            method: 'SitesManager.getPatternMatchSites',
+            filter_limit: filterLimit,
+            pattern: term
+        }).then(function (response) {
+            $scope.updateWebsitesList(response);
+        }).finally(function () {
+            $scope.isLoading = false;
+        });
+    };
+
+    $scope.loadInitialSites = function () {
+        $scope.isLoading = true;
+        piwikApi.fetch({
+            method: 'SitesManager.getSitesWithAtLeastViewAccess',
+            filter_limit: filterLimit,
+            showColumns: 'name,idsite'
+        }).then(function (response) {
+            $scope.updateWebsitesList(response);
+        }).finally(function () {
+            $scope.isLoading = false;
+        });
+    }
+
+}]);
\ No newline at end of file
diff --git a/plugins/CoreHome/javascripts/siteselector/tests.js b/plugins/CoreHome/javascripts/siteselector/tests.js
new file mode 100644
index 0000000000..92b3089007
--- /dev/null
+++ b/plugins/CoreHome/javascripts/siteselector/tests.js
@@ -0,0 +1,17 @@
+/*!
+ * Piwik - Web Analytics
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+describe('SiteSelector Tests', function() {
+    beforeEach(module(function() {
+        return function(inject1, inject2) {
+        }
+    }));
+
+    it('should fetch all websites', function() {
+        expect(1).toBe(1);
+    });
+});
\ No newline at end of file
diff --git a/plugins/CoreHome/templates/_siteSelect.twig b/plugins/CoreHome/templates/_siteSelect.twig
index 071673c1e0..84092ac04e 100644
--- a/plugins/CoreHome/templates/_siteSelect.twig
+++ b/plugins/CoreHome/templates/_siteSelect.twig
@@ -1,67 +1 @@
-{# The following parameters can be used to customize this widget when 'include'-ing:
- * 
- * - showAllSitesItem true if the 'All Websites' option should be shown, false if otherwise. Default = true.
- * - allSitesItemText The text to use for the 'All Websites' option. Default = 'All Websites'.
- * - allWebsitesLinkLocation The location of the 'All Websites' option. Can be 'top' or 'bottom'. Default = 'bottom'.
- * - showSelectedSite false if the currently selected site should be excluded from the list of sites. Default = false.
- * - sites The list of sites to use. By default, the first N sites are used. They are retrieved in View.
- * - show_autocompleter Whether to show the autocompleter or not. Default true.
- * - switchSiteOnSelect Whether to change the page w/ a new idSite value when a site is selected, or not.
- *                      Default = true.
- * - inputName If set, a hidden <input> w/ name == $inputName is created which will hold the selected site's ID. For
- *             use with <form> elements.
- * - siteName The currently selected site name. Defaults to the first name in $sites set by View.
- * - idSite The currently selected idSite. Defaults to the first id in $sites set by View.
- #}
-{% set sitesSelector_allWebsitesLink %}
-    <div class="custom_select_all" style="clear: both;">
-        <a href="#" {% if showAllSitesItem is defined and showAllSitesItem == false %}style="display:none;"{% endif %}>
-            {% if allSitesItemText is defined %}
-                {{ allSitesItemText }}
-            {% else %}
-                {{ 'General_MultiSitesSummary'|translate }}
-            {% endif %}
-        </a>
-    </div>
-{% endset %}
-<div class="sites_autocomplete {% if sites|length > 1 %}sites_autocomplete--dropdown{% endif %}"
-        {% if siteSelectorId is defined %}id="{{ siteSelectorId }}"{% endif %}
-        {% if switchSiteOnSelect is not defined or switchSiteOnSelect %}data-switch-site-on-select="1"{% endif %}>
-    <div class="custom_select">
-
-        <a href="#" onclick="return false" class="custom_select_main_link" data-loading="0" data-siteid="{% if idSite is defined %}{{ idSite }}{% else %}{{ sites[0].idsite }}{% endif %}">
-            <span>{% if siteName is defined %}{{ siteName|raw }}{% else %}{{ sites[0].name|raw }}{% endif %}</span>
-        </a>
-
-        <div class="custom_select_block">
-            {% if allWebsitesLinkLocation is defined and allWebsitesLinkLocation == 'top' %}
-                {{ sitesSelector_allWebsitesLink }}
-            {% endif %}
-            <div class="custom_select_container">
-                <ul class="custom_select_ul_list">
-                    {% for info in sites %}
-                        <li {% if (showSelectedSite is not defined or showSelectedSite == false) and idSite == info.idsite %} style="display: none;"{% endif %}>
-                            <a href="#" data-siteid="{{ info.idsite }}">{{ info.name|raw }}</a>
-                        </li>
-                    {% endfor %}
-                </ul>
-            </div>
-            {% if allWebsitesLinkLocation is not defined or allWebsitesLinkLocation == 'bottom' %}
-                {{ sitesSelector_allWebsitesLink }}
-            {% endif %}
-            <div class="custom_select_search" {% if show_autocompleter == false %}style="display:none;"{% endif %}>
-                <input type="text" length="15" class="websiteSearch inp"/>
-                <input type="hidden" class="max_sitename_width" value="130"/>
-                <input type="submit" value="Search" class="but"/>
-                <img title="Clear" class="reset" style="position: relative; top: 4px; left: -44px; cursor: pointer; display: none;"
-                     src="plugins/CoreHome/images/reset_search.png"/>
-            </div>
-        </div>
-    </div>
-    {% if inputName is defined %}
-        <input type="hidden" name="{{ inputName }}" value="{% if idSite is defined %}{{ idSite }}{% else %}{{ sites[0].idsite }}{% endif %}"/>
-    {% endif %}
-</div>
-<script type="text/javascript">
-    $(document).ready(function () { piwik.initSiteSelectors(); });
-</script>
+<div ng-include src="templateUrl" ng-init="siteName='test';inputName='test2'" ng-controller="SiteSelectorCtrl" class="sites_autocomplete"></div>
\ No newline at end of file
diff --git a/plugins/Morpheus/stylesheets/forms.less b/plugins/Morpheus/stylesheets/forms.less
index 95f79d2e39..842df5164d 100644
--- a/plugins/Morpheus/stylesheets/forms.less
+++ b/plugins/Morpheus/stylesheets/forms.less
@@ -79,7 +79,7 @@ button[type="button"],
 
 
 .sites_autocomplete--dropdown {
-    .custom_select_main_link[data-loading="0"]:before {
+    .custom_select_main_link:not(.loading):before {
         color: @brand-red;
         font-size: 0.7em;
         top: 2px;
diff --git a/plugins/Zeitgeist/stylesheets/ui/_siteSelect.less b/plugins/Zeitgeist/stylesheets/ui/_siteSelect.less
index 80dcf97d1c..cd6dd28ff5 100644
--- a/plugins/Zeitgeist/stylesheets/ui/_siteSelect.less
+++ b/plugins/Zeitgeist/stylesheets/ui/_siteSelect.less
@@ -64,7 +64,7 @@
   padding: 0 20px 0 4px;
 }
 
-.sites_autocomplete--dropdown .custom_select_main_link[data-loading="0"]:before {
+.sites_autocomplete--dropdown .custom_select_main_link:not(.loading):before {
   content: " \25BC";
   position: absolute;
   right: 0;
@@ -78,7 +78,7 @@
   position: relative;
 }
 
-.sites_autocomplete .custom_select_main_link[data-loading="1"] {
+.sites_autocomplete .custom_select_main_link.loading {
   background: url(plugins/Zeitgeist/images/loading-blue.gif) no-repeat right 3px;
 }
 
@@ -144,9 +144,8 @@
 }
 
 .custom_select_block {
-  height: 0;
   overflow: hidden;
-  max-width:140px;
+  max-width: inherit
 }
 
 .custom_select_block_show {
diff --git a/plugins/Zeitgeist/templates/dashboard.twig b/plugins/Zeitgeist/templates/dashboard.twig
index 282f51f6c8..d2778327a4 100644
--- a/plugins/Zeitgeist/templates/dashboard.twig
+++ b/plugins/Zeitgeist/templates/dashboard.twig
@@ -1,8 +1,8 @@
 <!DOCTYPE html>
 <!--[if lt IE 9 ]>
-<html class="old-ie"> <![endif]-->
+<html class="old-ie" id="ng-app" ng-app="piwikApp"> <![endif]-->
 <!--[if (gte IE 9)|!(IE)]><!-->
-<html><!--<![endif]-->
+<html id="ng-app" ng-app="piwikApp"><!--<![endif]-->
     <head>
         {% block head %}            
             <meta charset="utf-8">
@@ -23,7 +23,7 @@
             <![endif]-->
         {% endblock %}
     </head>
-    <body>
+    <body ng-app="app">
     {% include "_iframeBuster.twig" %}
     {% include "@CoreHome/_javaScriptDisabled.twig" %}
 
-- 
GitLab