commit ba4fa234ed3627598f61d269d1d33400c97d30d1
Author: Rob Bayliss <rob@lastcallmedia.com>
Date:   Fri Sep 30 12:49:33 2016 +0100

    Implement messaging on ajax and non-ajax flaggings

diff --git a/flag.libraries.yml b/flag.libraries.yml
index e4322a6..2a2fa86 100644
--- a/flag.libraries.yml
+++ b/flag.libraries.yml
@@ -13,3 +13,8 @@ flag.link:
   css:
     theme:
       css/flag-link.css: {}
+
+flag.ajax:
+  version: VERSION
+  js:
+    js/flag-ajax.js: {}
diff --git a/flag.module b/flag.module
index 934073c..d4c0563 100644
--- a/flag.module
+++ b/flag.module
@@ -21,6 +21,7 @@ use Drupal\user\UserInterface;
 use Drupal\flag\FlagInterface;
 use Drupal\Core\Url;
 use Drupal\Core\Plugin\DefaultSingleLazyPluginCollection;
+use Drupal\Core\Template\Attribute;
 
 /**
  * Implements hook_help().
@@ -278,12 +279,28 @@ function flag_theme() {
         'action' => 'flag',
         'flag' => NULL,
         'flaggable' => NULL,
+        'wrapper_attributes' => [],
+        'ajax_message' => NULL,
+        'ajax_message_attributes' => [],
       ],
     ],
   ];
 }
 
 /**
+ * Adds attribute arrays for wrapper and message.
+ *
+ * @param $variables
+ */
+function template_preprocess_flag(&$variables) {
+  $variables['wrapper_attributes'] = new Attribute($variables['wrapper_attributes']);
+  $variables['wrapper_attributes']['data-flag-link'] = $variables['flag']->id() . '-' . $variables['flaggable']->id();
+  $variables['ajax_message_attributes'] = new Attribute($variables['ajax_message_attributes']);
+  $variables['ajax_message_attributes']['data-flag-message-fadein'] = TRUE;
+  $variables['ajax_message_attributes']['data-flag-message-fadeout'] = TRUE;
+}
+
+/**
  * Implements hook_theme_suggestions_HOOK().
  */
 function flag_theme_suggestions_flag(array $variables) {
diff --git a/js/flag-ajax.js b/js/flag-ajax.js
new file mode 100644
index 0000000..d81ad69
--- /dev/null
+++ b/js/flag-ajax.js
@@ -0,0 +1,22 @@
+/**
+ * @file
+ * Defines Javascript behaviors for the flag module's ajax links.
+ */
+
+(function ($, Drupal, drupalSettings) {
+
+  'use strict';
+
+  Drupal.behaviors.flagAjax = {
+    attach: function (context) {
+      $('[data-flag-message-fadein]', context).hide().fadeIn();
+      $('[data-flag-message-fadeout]', context).each(function() {
+        var $message = $(this);
+        setTimeout(function() {
+          $message.fadeOut();
+        }, 3000);
+      });
+    }
+  };
+
+})(jQuery, Drupal, drupalSettings);
diff --git a/src/ActionLink/ActionLinkTypeBase.php b/src/ActionLink/ActionLinkTypeBase.php
index a8130e8..b10e104 100644
--- a/src/ActionLink/ActionLinkTypeBase.php
+++ b/src/ActionLink/ActionLinkTypeBase.php
@@ -104,7 +104,7 @@ abstract class ActionLinkTypeBase extends PluginBase implements ActionLinkTypePl
   /**
    * {@inheritdoc}
    */
-  public function buildLink($action, FlagInterface $flag, EntityInterface $entity) {
+  public function buildLink($action, FlagInterface $flag, EntityInterface $entity, $ajax_message = NULL) {
     // Get the Flag URL.
     $url = $this->getLinkURL($action, $flag, $entity);
 
@@ -114,6 +114,7 @@ abstract class ActionLinkTypeBase extends PluginBase implements ActionLinkTypePl
     $render['#flag'] = $flag;
     $render['#flaggable'] = $entity;
     $render['#theme'] = 'flag';
+    $render['#ajax_message'] = $ajax_message;
 
     // Build the URL. It is important that bubbleable metadata is explicitly
     // collected and applied to the render array, as it might be rendered on its
@@ -141,7 +142,7 @@ abstract class ActionLinkTypeBase extends PluginBase implements ActionLinkTypePl
   /**
    * {@inheritdoc}
    */
-  public function getLink(FlagInterface $flag, EntityInterface $entity) {
+  public function getLink(FlagInterface $flag, EntityInterface $entity, $message = NULL) {
     $action = $flag->isFlagged($entity) ? 'unflag' : 'flag';
 
     $access = $flag->actionAccess($action, $this->currentUser, $entity);
@@ -149,7 +150,7 @@ abstract class ActionLinkTypeBase extends PluginBase implements ActionLinkTypePl
       // The actual render array must be in a nested key, due to a bug in
       // lazy builder handling that does not properly render top-level #type
       // elements.build
-      $link = ['link' => $this->buildLink($action, $flag, $entity)];
+      $link = ['link' => $this->buildLink($action, $flag, $entity, $message)];
     } else {
       $link = [];
     }
diff --git a/src/ActionLink/ActionLinkTypePluginInterface.php b/src/ActionLink/ActionLinkTypePluginInterface.php
index ff5cd84..bed88aa 100644
--- a/src/ActionLink/ActionLinkTypePluginInterface.php
+++ b/src/ActionLink/ActionLinkTypePluginInterface.php
@@ -24,11 +24,13 @@ interface ActionLinkTypePluginInterface extends PluginFormInterface, Configurabl
    *   The flag entity.
    * @param EntityInterface $entity
    *   The entity for which to create a flag link.
+   * @param string}null $ajax_message
+*      The message to display after an ajax action.
    *
    * @return array
    *   A render array of the flag link.
    */
-  public function getLink(FlagInterface $flag, EntityInterface $entity);
+  public function getLink(FlagInterface $flag, EntityInterface $entity, $ajax_message = NULL);
 
   /**
    * Returns a Url object for the given flag action.
@@ -62,10 +64,12 @@ interface ActionLinkTypePluginInterface extends PluginFormInterface, Configurabl
    *   The flag entity.
    * @param EntityInterface $entity
    *   The entity for which to create a flag link.
+   * @param string|null $ajax_message
+   *   The message to display after an ajax action.
    *
    * @return array
    *   A render array of the flag link.
    */
-  public function buildLink($action, FlagInterface $flag, EntityInterface $entity);
+  public function buildLink($action, FlagInterface $flag, EntityInterface $entity, $ajax_message = null);
 
 }
diff --git a/src/Controller/ActionLinkController.php b/src/Controller/ActionLinkController.php
index 7279e7d..e12eaed 100644
--- a/src/Controller/ActionLinkController.php
+++ b/src/Controller/ActionLinkController.php
@@ -72,16 +72,18 @@ class ActionLinkController extends ControllerBase implements ContainerInjectionI
   public function flag(FlagInterface $flag, $entity_id, Request $request) {
     /* @var \Drupal\Core\Entity\EntityInterface $entity */
     $entity = $this->flagService->getFlaggableById($flag, $entity_id);
+    $message = NULL;
 
     try {
       $this->flagService->flag($flag, $entity);
+      $message = $flag->getFlagMessage();
     }
     catch (\LogicException $e) {
       // Fail silently so we return to the entity, which will show an updated
       // link for the existing state of the flag.
     }
 
-    return $this->generateResponse($flag, $entity, $request);
+    return $this->generateResponse($flag, $entity, $request, $message);
   }
 
   /**
@@ -102,16 +104,18 @@ class ActionLinkController extends ControllerBase implements ContainerInjectionI
   public function unflag(FlagInterface $flag, $entity_id, Request $request) {
     /* @var \Drupal\Core\Entity\EntityInterface $entity */
     $entity = $this->flagService->getFlaggableById($flag, $entity_id);
+    $message = NULL;
 
     try {
       $this->flagService->unflag($flag, $entity);
+      $message = $flag->getUnflagMessage();
     }
     catch (\LogicException $e) {
       // Fail silently so we return to the entity, which will show an updated
       // link for the existing state of the flag.
     }
 
-    return $this->generateResponse($flag, $entity, $request);
+    return $this->generateResponse($flag, $entity, $request, $message);
   }
 
   /**
@@ -126,11 +130,13 @@ class ActionLinkController extends ControllerBase implements ContainerInjectionI
    *   The entity object.
    * @param \Symfony\Component\HttpFoundation\Request $request
    *   The request.
+   * @param string|null $message
+   *   The message to display to the user to indicate what just happened.
    *
    * @return \Drupal\Core\Ajax\AjaxResponse|\Symfony\Component\HttpFoundation\RedirectResponse
    *   The response object.
    */
-  protected function generateResponse(FlagInterface $flag, EntityInterface $entity, Request $request) {
+  protected function generateResponse(FlagInterface $flag, EntityInterface $entity, Request $request, $message = null) {
     if ($request->get(MainContentViewSubscriber::WRAPPER_FORMAT) == 'drupal_ajax') {
       // Create a new AJAX response.
       $response = new AjaxResponse();
@@ -139,11 +145,10 @@ class ActionLinkController extends ControllerBase implements ContainerInjectionI
       $link_type = $flag->getLinkTypePlugin();
 
       // Generate the link render array.
-      $link = $link_type->getLink($flag, $entity);
+      $link = $link_type->getLink($flag, $entity, $message);
 
       // Generate a CSS selector to use in a JQuery Replace command.
-      $selector = '.flag-' . $flag->id() . '-' . $entity->id();
-
+      $selector = '[data-flag-link="' . $flag->id() . '-' . $entity->id() .'"]';
       // Create a new JQuery Replace command to update the link display.
       $replace = new ReplaceCommand($selector, $this->renderer->renderPlain($link));
       $response->addCommand($replace);
@@ -151,6 +156,7 @@ class ActionLinkController extends ControllerBase implements ContainerInjectionI
       return $response;
     }
     else {
+      drupal_set_message($message);
       // Redirect back to the entity. A passed in destination query parameter
       // will automatically override this.
       $url_info = $entity->toUrl();
diff --git a/src/Plugin/ActionLink/AJAXactionLink.php b/src/Plugin/ActionLink/AJAXactionLink.php
index f58f7ec..78c4bbd 100644
--- a/src/Plugin/ActionLink/AJAXactionLink.php
+++ b/src/Plugin/ActionLink/AJAXactionLink.php
@@ -23,9 +23,10 @@ class AJAXactionLink extends Reload {
   /**
    * {@inheritdoc}
    */
-  public function buildLink($action, FlagInterface $flag, EntityInterface $entity) {
-    $render = parent::buildLink($action, $flag, $entity);
+  public function buildLink($action, FlagInterface $flag, EntityInterface $entity, $ajax_message = NULL) {
+    $render = parent::buildLink($action, $flag, $entity, $ajax_message);
     $render['#attached']['library'][] = 'core/drupal.ajax';
+    $render['#attached']['library'][] = 'flag/flag.ajax';
     $render['#attributes']['class'][] = 'use-ajax';
     return $render;
   }
diff --git a/templates/flag.html.twig b/templates/flag.html.twig
index b7080a1..20e1df6 100644
--- a/templates/flag.html.twig
+++ b/templates/flag.html.twig
@@ -30,6 +30,12 @@
 action_class
 ] %}
 {% set attributes = attributes.addClass(classes) %}
-
-<a{{ attributes }}>{{ title }}</a>
+{% set wrapper_attributes = wrapper_attributes.addClass('flag-wrapper') %}
+{% set message_attributes = message_attributes.addClass('flag-message') %}
+<div{{ wrapper_attributes }}>
+  <a{{ attributes }}>{{ title }}</a>
+  {% if ajax_message %}
+    <span{{ ajax_message_attributes }}>{{ ajax_message }}</span>
+  {% endif %}
+</div>
 {% endspaceless %}
