Token deletion with simple undo
authorChristian Weiske <cweiske@cweiske.de>
Mon, 13 Oct 2014 18:41:30 +0000 (20:41 +0200)
committerChristian Weiske <cweiske@cweiske.de>
Mon, 13 Oct 2014 18:41:30 +0000 (20:41 +0200)
Following the lines of
- http://alistapart.com/article/neveruseawarning
- https://github.com/owncloud/core/issues/9268
- https://github.com/owncloud/contacts/issues/107

I've added simple undo functionality to the "delete token" action.
The user has 5 seconds to click on the "restore" button to undo the
token deletion.

js/grauphel.js [new file with mode: 0644]
templates/tokens.php

diff --git a/js/grauphel.js b/js/grauphel.js
new file mode 100644 (file)
index 0000000..d28e6c3
--- /dev/null
@@ -0,0 +1,126 @@
+/**
+ * Undo
+ *
+ * Single action:
+ * 1. User clicks "delete"
+ * 2. Notification appears for 5 seconds
+ * 3. Token row is faded out
+ * 4a User clicks on notification:
+ *    - Action is cancelled
+ *    - Token row is faded in
+ * 4b User does not click on notification:
+ *    - Action gets executed 5 seconds after the delete click
+ *    - Token row gets removed
+ *
+ *
+ * Multiple actions:
+ * 1. User clicks "delete"
+ * 2. Notification appears for 5 seconds
+ * 3. User clicks "delete"
+ * 4. Notification appears for 5 seconds
+ * 5a User clicks on notification: All pending actions are cancelled
+ * 5b User does not click on notification
+ *    - Action 1 gets executed 5 seconds after the first click
+ *    - Action 2 gets executed 5 seconds after the second click
+ */
+OC.grauphel = {
+    simpleUndo: function(undoTask) {
+        var notifier = $('#notification');
+        var timeout = 5;
+        notifier.off('click');
+        notifier.text('Token has been deleted. Click to undo.');
+        notifier.fadeIn();
+
+        $('#' + undoTask.elementId).fadeOut();
+
+        OC.grauphel.startGuiTimer(timeout, notifier);
+        var timer = setTimeout(
+            function() {
+                var dataid = timer.toString();
+                OC.grauphel.executeTask(notifier.data(dataid), true);
+                notifier.removeData(dataid);
+            },
+            timeout * 1000
+        );
+        var dataid = timer.toString();
+        notifier.data(dataid, undoTask);
+
+        notifier.on('click', function() {
+            for (var id in notifier.data()) {
+                clearTimeout(parseInt(id));
+                notifier.off('click');
+                OC.grauphel.restore(notifier.data(id));
+                notifier.removeData(id);
+            }
+        });
+    },
+
+    executeTask: function(task, async) {
+        //console.log("execute task: ", task);
+        jQuery.ajax({
+            url:   task.url,
+            type:  task.method,
+            async: async
+        });
+    },
+
+    restore: function(undoTask) {
+        $('#' + undoTask.elementId).fadeIn();
+
+        var notifier = $('#notification');
+        var timeout = 5;
+        notifier.off('click');
+        notifier.text('Token has been restored.');
+
+        OC.grauphel.startGuiTimer(timeout, notifier);
+        notifier.on('click', function() {
+            clearTimeout(OC.grauphel.guiTimer);
+            notifier.fadeOut();
+        });
+    },
+
+    executeAllTasks: function() {
+        var notifier = $('#notification');
+        for (var id in notifier.data()) {
+            clearTimeout(parseInt(id));
+            OC.grauphel.executeTask(notifier.data(id), false);
+            notifier.removeData(id);
+        }
+    },
+
+    guiTimer: null,
+
+    startGuiTimer: function(timeout, notifier) {
+        if (OC.grauphel.guiTimer !== null) {
+            clearTimeout(OC.grauphel.guiTimer);
+        }
+        OC.grauphel.guiTimer = setTimeout(
+            function() {
+                notifier.fadeOut();
+                notifier.off('click');
+            },
+            timeout * 1000
+        );
+    }
+};
+
+$(document).ready(function() {
+    $('#grauphel-tokens .delete').click(
+        function (event) {
+            event.preventDefault();
+
+            var undoTask = {
+                'method': 'DELETE',
+                'url': $(this).parent('form').attr('action'),
+                'elementId': $(this).data('token')
+            };
+            OC.grauphel.simpleUndo(undoTask);
+            return false;
+        }
+    );
+
+    //in case a user deletes tokens and leaves the page within the 5 seconds
+    window.onbeforeunload = function(e) {
+        OC.grauphel.executeAllTasks();
+    };
+});
index 9c1da6d..3aaabb7 100644 (file)
@@ -3,6 +3,8 @@
 <?php /** @var $l OC_L10N */ ?>
 <?php $_['appNavigation']->printPage(); ?>
 
+<script type="text/javascript" src="<?php p(OCP\Util::linkTo('grauphel','js/grauphel.js')); ?>"></script>
+
 <div id="app-content" class="list">
   <h1>Manage access tokens</h1>
   <p>
@@ -10,7 +12,7 @@
     You can permanently revoke access by clicking the "delete" icon on the right
     side of each token row.
   </p>
-  <table class="table">
+  <table class="table" id="grauphel-tokens">
    <thead>
     <tr>
      <th>Token</th>
    </thead>
    <tbody>
     <?php foreach ($_['tokens'] as $token) { ?>
-      <tr>
+      <tr id="token-<?php p($token->tokenKey); ?>">
        <td><?php p($token->tokenKey); ?></td>
        <td title="<?php p($token->client); ?>"><?php p($_['client']->getNiceName($token->client)); ?></td>
        <td>
         <?php p(\OCP\Util::formatDate($token->lastuse)); ?>
         <form method="POST" action="<?php p(OCP\Util::linkToRoute('grauphel.token.delete', array('username' => $_['username'], 'tokenKey' => $token->tokenKey))); ?>">
            <input type="hidden" name="delete" value="1" />
-           <button type="submit" class="icon-delete delete action" original-title="Delete"/>
+           <button type="submit" class="icon-delete delete action"
+                   original-title="Delete"
+                   data-token="token-<?php p($token->tokenKey); ?>"
+           />
         </form>
        </td>
       </tr>