From a555bec5f6b157db385d423cad9cd1ef910e6730 Mon Sep 17 00:00:00 2001
From: diosmosis <benaka@piwik.pro>
Date: Wed, 1 Oct 2014 19:05:48 -0700
Subject: [PATCH] Create angularjs directive for Piwik notifications and use in
 old Notificaction class.

---
 plugins/CoreHome/CoreHome.php                 |   4 +
 .../notification/notification.controller.js   |  34 +++++
 .../notification/notification.directive.html  |   8 ++
 .../notification/notification.directive.js    |  95 ++++++++++++
 .../notification/notification.directive.less  |  84 +++++++++++
 plugins/CoreHome/javascripts/notification.js  | 135 ++++--------------
 .../CoreHome/stylesheets/notification.less    |  87 +----------
 .../ExampleUI/templates/notifications.twig    |   7 +-
 8 files changed, 256 insertions(+), 198 deletions(-)
 create mode 100644 plugins/CoreHome/angularjs/notification/notification.controller.js
 create mode 100644 plugins/CoreHome/angularjs/notification/notification.directive.html
 create mode 100644 plugins/CoreHome/angularjs/notification/notification.directive.js
 create mode 100644 plugins/CoreHome/angularjs/notification/notification.directive.less

diff --git a/plugins/CoreHome/CoreHome.php b/plugins/CoreHome/CoreHome.php
index 187f356c5e..f833b1e5cc 100644
--- a/plugins/CoreHome/CoreHome.php
+++ b/plugins/CoreHome/CoreHome.php
@@ -69,6 +69,7 @@ class CoreHome extends \Piwik\Plugin
         $stylesheets[] = "plugins/CoreHome/angularjs/enrichedheadline/enrichedheadline.directive.less";
         $stylesheets[] = "plugins/CoreHome/angularjs/menudropdown/menudropdown.directive.less";
         $stylesheets[] = "plugins/CoreHome/angularjs/dialogtoggler/ngdialog.less";
+        $stylesheets[] = "plugins/CoreHome/angularjs/notification/notification.directive.less";
     }
 
     public function getJsFiles(&$jsFiles)
@@ -149,6 +150,9 @@ class CoreHome extends \Piwik\Plugin
         $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/notification/notification.controller.js";
+        $jsFiles[] = "plugins/CoreHome/angularjs/notification/notification.directive.js";
     }
 
     public function getClientSideTranslationKeys(&$translationKeys)
diff --git a/plugins/CoreHome/angularjs/notification/notification.controller.js b/plugins/CoreHome/angularjs/notification/notification.controller.js
new file mode 100644
index 0000000000..645cc13e59
--- /dev/null
+++ b/plugins/CoreHome/angularjs/notification/notification.controller.js
@@ -0,0 +1,34 @@
+/*!
+ * 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('NotificationController', NotificationController);
+
+    NotificationController.$inject = ['piwikApi'];
+
+    function NotificationController(piwikApi) {
+        /**
+         * Marks a persistent notification as read so it will not reappear on the next page
+         * load.
+         */
+        this.markNotificationAsRead = function () {
+            var notificationId = this.notificationId;
+            if (!notificationId) {
+                return;
+            }
+
+            piwikApi.post(
+                { // GET params
+                    module: 'CoreHome',
+                    action: 'markNotificationAsRead'
+                },
+                { // POST params
+                    notificationId: notificationId
+                }
+            );
+        };
+    }
+})();
\ No newline at end of file
diff --git a/plugins/CoreHome/angularjs/notification/notification.directive.html b/plugins/CoreHome/angularjs/notification/notification.directive.html
new file mode 100644
index 0000000000..a643be8a3b
--- /dev/null
+++ b/plugins/CoreHome/angularjs/notification/notification.directive.html
@@ -0,0 +1,8 @@
+<div class="notification system">
+    <button type="button" class="close" data-dismiss="alert" ng-if="!noclear" ng-click="notification.markNotificationAsRead()">&times;</button>
+    <strong ng-if="title">{{ title }}</strong>
+
+    <!-- ng-transclude causes directive child elements to be added here -->
+    <div ng-transclude style="display:inline-block"></div>
+
+</div>
\ No newline at end of file
diff --git a/plugins/CoreHome/angularjs/notification/notification.directive.js b/plugins/CoreHome/angularjs/notification/notification.directive.js
new file mode 100644
index 0000000000..167c61575b
--- /dev/null
+++ b/plugins/CoreHome/angularjs/notification/notification.directive.js
@@ -0,0 +1,95 @@
+/*!
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+/**
+ * Directive to show a notification.
+ *
+ * Note: using this directive is preferred over the Notification class (which uses jquery
+ * exclusively).
+ *
+ * Supports the following attributes:
+ *
+ * * **context**: Either 'success', 'error', 'info', 'warning'
+ * * **type**: Either 'toast', 'persistent', 'transient'
+ * * **noclear**: If truthy, no clear button is displayed. For persistent notifications, has no effect.
+ *
+ * Usage:
+ *
+ *     <div piwik-notification context="success" type="persistent" noclear="true">
+ *         <strong>Info: </strong>My notification message.
+ *     </div>
+ */
+(function () {
+    angular.module('piwikApp').directive('piwikNotification', piwikNotification);
+
+    piwikNotification.$inject = ['piwik', '$timeout'];
+
+    function piwikNotification(piwik, $timeout) {
+        return {
+            restrict: 'A',
+            scope: {
+                notificationId: '@?',
+                title: '@?notificationTitle', // TODO: shouldn't need this since the title can be specified within
+                                              //       HTML of the node that uses the directive.
+                context: '@?',
+                type: '@?',
+                noclear: '=?'
+            },
+            transclude: true,
+            templateUrl: 'plugins/CoreHome/angularjs/notification/notification.directive.html?cb=' + piwik.cacheBuster,
+            controller: 'NotificationController',
+            controllerAs: 'notification',
+            link: function (scope, element, attrs) {
+                if (scope.context) {
+                    element.children('.notification').addClass('notification-' + scope.context);
+                }
+
+                if (scope.type == 'persistent') {
+                    // otherwise it is never possible to dismiss the notification
+                    scope.noclear = false;
+                }
+
+                if (scope.notificationId) {
+                    closeExistingNotificationHavingSameIdIfNeeded();
+                }
+
+                if ('toast' == scope.type) {
+                    addToastEvent();
+                }
+
+                if (!scope.noclear) {
+                    addCloseEvent();
+                }
+
+                function closeExistingNotificationHavingSameIdIfNeeded() {
+                    // TODO: instead of doing a global query for notification, there should be a notification-container
+                    //       directive that manages notifications.
+                    var existingNode = angular.element('.system.notification[notification-id=' + attrs.id + ']');
+                    if (existingNode && existingNode.length) {
+                        existingNode.remove();
+                    }
+                }
+
+                function addToastEvent() {
+                    $timeout(function () {
+                        element.fadeOut('slow', function() {
+                            element.remove();
+                        });
+                    }, 12 * 1000);
+                }
+
+                function addCloseEvent() {
+                    element.on('click', '.close', function (event) {
+                        if (event && event.delegateTarget) {
+                            angular.element(event.delegateTarget).remove();
+                        }
+                    });
+                }
+            }
+        };
+    }
+})();
\ No newline at end of file
diff --git a/plugins/CoreHome/angularjs/notification/notification.directive.less b/plugins/CoreHome/angularjs/notification/notification.directive.less
new file mode 100644
index 0000000000..bc3c5e3f5f
--- /dev/null
+++ b/plugins/CoreHome/angularjs/notification/notification.directive.less
@@ -0,0 +1,84 @@
+.system.notification {
+  color: #9b7a44;
+  float: none;
+
+  padding: 15px 35px 15px 15px;
+  text-shadow: 0 1px 0 rgba(255,255,255,.5);
+  background-color: #ffffe0;
+  border: 1px solid #e6db55;
+  border-radius: 3px;
+  font-size: 14px;
+  margin: 0px;
+  margin-bottom: 16px;
+
+  a {
+    color: #9b7a44;
+    text-decoration: underline;
+  }
+
+  .close {
+    position: relative;
+    top: -5px;
+    right: -28px;
+    line-height: 20px;
+  }
+
+  button.close {
+    padding: 0;
+    cursor: pointer;
+    background: transparent;
+    border: 0;
+    -webkit-appearance: none;
+  }
+  .close {
+    float: right;
+    font-size: 20px;
+    font-weight: bold;
+    line-height: 20px;
+    color: #000000;
+    text-shadow: 0 1px 0 #ffffff;
+    opacity: 0.2;
+    filter: alpha(opacity=20);
+  }
+
+  &.notification-success {
+    background-color: #dff0d8;
+    border-color: #c3d6b7;
+    color: #468847;
+
+    a {
+      color: #468847
+    }
+  }
+  &.notification-danger,
+  &.notification-error {
+    background-color: #f2dede;
+    border-color: #d5bfc4;
+    color: #b94a48;
+
+    a {
+      color: #b94a48
+    }
+  }
+  &.notification-info {
+    background-color: #d9edf7;
+    border-color: #a7d3e3;
+    color: #3a87ad;
+
+    a {
+      color: #3a87ad
+    }
+  }
+
+  &.notification-block {
+    padding-top: 14px;
+    padding-bottom: 14px;
+  }
+  &.notification-block > p,
+  &.notification-block > ul {
+    margin-bottom: 0;
+  }
+  &.notification-block p + p {
+    margin-top: 5px;
+  }
+}
\ No newline at end of file
diff --git a/plugins/CoreHome/javascripts/notification.js b/plugins/CoreHome/javascripts/notification.js
index 7bd92042a5..266477446e 100644
--- a/plugins/CoreHome/javascripts/notification.js
+++ b/plugins/CoreHome/javascripts/notification.js
@@ -50,26 +50,8 @@
             options = {};
         }
 
-        if ('persistent' == options.type) {
-            // otherwise it is never possible to dismiss the notification
-            options.noclear = false;
-        }
-
-        closeExistingNotificationHavingSameIdIfNeeded(options);
-
-        var template          = generateNotificationHtmlMarkup(options, message);
-        var $notificationNode = placeNotification(template, options);
-        this.$node = $notificationNode;
-
-        if ('persistent' == options.type) {
-            addPersistentEvent($notificationNode);
-        } else if ('toast' == options.type) {
-            addToastEvent($notificationNode);
-        }
-
-        if (!options.noclear) {
-            addCloseEvent($notificationNode);
-        }
+        var template = generateNotificationHtmlMarkup(options, message);
+        this.$node   = placeNotification(template, options);
     };
 
     Notification.prototype.scrollToNotification = function () {
@@ -80,69 +62,37 @@
 
     exports.Notification = Notification;
 
-    function closeExistingNotificationHavingSameIdIfNeeded(options)
-    {
-        if (!options.id) {
-            return;
-        }
-
-        var $existingNode = $('.system.notification[data-id=' + options.id + ']');
-        if ($existingNode && $existingNode.length) {
-            $existingNode.remove();
-        }
-    }
-
     function generateNotificationHtmlMarkup(options, message) {
-        var template = buildNotificationStart(options);
-
-        if (!options.noclear) {
-            template += buildClearButton();
-        }
-
-        if (options.title) {
-            template += buildTitle(options);
-        }
-
-        template += message;
-        template += buildNotificationEnd();
-
-        return template;
-    }
-
-    function buildNotificationStart(options) {
-        var template = '<div class="notification system';
-
-        if (options.context) {
-            template += ' notification-' + options.context;
-        }
-
-        template += '"';
-
-        if (options.id) {
-            template += ' data-id="' + options.id + '"';
+        var attributeMapping = {
+                id: 'notification-id',
+                title: 'notification-title',
+                context: 'context',
+                type: 'type',
+                noclear: 'noclear'
+            },
+            html = '<div piwik-notification';
+
+        for (var key in attributeMapping) {
+            if (attributeMapping.hasOwnProperty(key)
+                && options[key]
+            ) {
+                html += ' ' + attributeMapping[key] + '="' + options[key].toString().replace(/"/g, "&quot;") + '"';
+            }
         }
 
-        template += '>';
-
-        return template;
-    }
-
-    function buildNotificationEnd() {
-        return '</div>';
-    }
-
-    function buildClearButton() {
-        return '<button type="button" class="close" data-dismiss="alert">&times;</button>';
-    }
+        html += '>' + message + '</div>';
 
-    function buildTitle(options) {
-        return '<strong>' + options.title + '</strong> ';
+        return html;
     }
 
     function placeNotification(template, options) {
-
         var $notificationNode = $(template);
 
+        // compile the template in angular
+        angular.element(document).injector().invoke(function ($compile, $rootScope) {
+            $compile($notificationNode)($rootScope.$new(true));
+        });
+
         if (options.style) {
             $notificationNode.css(options.style);
         }
@@ -158,41 +108,4 @@
 
         return $notificationNode;
     }
-
-    function addToastEvent($notificationNode)
-    {
-        setTimeout(function () {
-            $notificationNode.fadeOut( 'slow', function() {
-                $notificationNode.remove();
-                $notificationNode = null;
-            });
-        }, 12 * 1000);
-    }
-
-    function addCloseEvent($notificationNode) {
-        $notificationNode.on('click', '.close', function (event) {
-            if (event && event.delegateTarget) {
-                $(event.delegateTarget).remove();
-            }
-        });
-    };
-
-    function addPersistentEvent($notificationNode) {
-        var notificationId = $notificationNode.data('id');
-
-        if (!notificationId) {
-            return;
-        }
-
-        $notificationNode.on('click', '.close', function (event) {
-            var ajaxHandler = new ajaxHelper();
-            ajaxHandler.addParams({
-                module: 'CoreHome',
-                action: 'markNotificationAsRead'
-            }, 'GET');
-            ajaxHandler.addParams({notificationId: notificationId}, 'POST');
-            ajaxHandler.send(true);
-        });
-    };
-
 })(jQuery, require);
\ No newline at end of file
diff --git a/plugins/CoreHome/stylesheets/notification.less b/plugins/CoreHome/stylesheets/notification.less
index 23b29e1fe4..36f7085f6f 100644
--- a/plugins/CoreHome/stylesheets/notification.less
+++ b/plugins/CoreHome/stylesheets/notification.less
@@ -5,89 +5,4 @@
   .notification {
     margin: 10px;
   }
-}
-
-.system.notification {
-  color: #9b7a44;
-  float: none;
-
-  padding: 15px 35px 15px 15px;
-  text-shadow: 0 1px 0 rgba(255,255,255,.5);
-  background-color: #ffffe0;
-  border: 1px solid #e6db55;
-  border-radius: 3px;
-  font-size: 14px;
-  margin: 0px;
-  margin-bottom: 16px;
-
-  a {
-    color: #9b7a44;
-    text-decoration: underline;
-  }
-
-  .close {
-    position: relative;
-    top: -5px;
-    right: -28px;
-    line-height: 20px;
-  }
-
-  button.close {
-    padding: 0;
-    cursor: pointer;
-    background: transparent;
-    border: 0;
-    -webkit-appearance: none;
-  }
-  .close {
-    float: right;
-    font-size: 20px;
-    font-weight: bold;
-    line-height: 20px;
-    color: #000000;
-    text-shadow: 0 1px 0 #ffffff;
-    opacity: 0.2;
-    filter: alpha(opacity=20);
-  }
-
-  &.notification-success {
-    background-color: #dff0d8;
-    border-color: #c3d6b7;
-    color: #468847;
-
-    a {
-      color: #468847
-    }
-  }
-  &.notification-danger,
-  &.notification-error {
-    background-color: #f2dede;
-    border-color: #d5bfc4;
-    color: #b94a48;
-
-    a {
-      color: #b94a48
-    }
-  }
-  &.notification-info {
-    background-color: #d9edf7;
-    border-color: #a7d3e3;
-    color: #3a87ad;
-
-    a {
-      color: #3a87ad
-    }
-  }
-
-  &.notification-block {
-    padding-top: 14px;
-    padding-bottom: 14px;
-  }
-  &.notification-block > p,
-  &.notification-block > ul {
-    margin-bottom: 0;
-  }
-  &.notification-block p + p {
-    margin-top: 5px;
-  }
-}
+}
\ No newline at end of file
diff --git a/plugins/ExampleUI/templates/notifications.twig b/plugins/ExampleUI/templates/notifications.twig
index 2212ead3d9..1058a50a61 100644
--- a/plugins/ExampleUI/templates/notifications.twig
+++ b/plugins/ExampleUI/templates/notifications.twig
@@ -4,6 +4,11 @@
     <h2>Inline notification example:</h2>
 
     <div style="display:inline-block;margin-top:10px;" id="exampleUI_notifications">
-        {{ 'This is an example for an inline notification. Have you noticed the success message disappeared after a few seconds?'|notification({'placeAt': '#exampleUI_notifications', 'title': 'Info: ', 'noclear': true, 'context': 'info'}) }}
+        <div piwik-notification
+             notification-title="Info:"
+             noclear="true"
+             context="info">
+            This is an example for an inline notification. Have you noticed the success message disappeared after a few seconds?
+        </div>
     </div>
 {% endblock %}
\ No newline at end of file
-- 
GitLab