Second Life of a Hungarian SharePoint Geek

November 30, 2016

Creating an AngularJS Directive for Mouse Hold

Filed under: AngularJS — Tags: — Peter Holpar @ 21:12

Most of the time we use AngularJS to create our single-page application (SPA) in SharePoint and Project Server.

Recently we had a requirement that AngularJS does not provide an out-of-the-box solution for: there is a container on the page that displays a fix number of items and two buttons (one is located above the container, the other is below the container), that should scroll the items in the container back and forth. When the user clicks the upper button, new items should appear at the bottom of the container and the top items should disappear. When the user clicks the button below, the items formerly scrolled out at the top should re-appear and the items below should disappear. The user must be able to scroll the items one-by-one. However, there is a large number of items, so clicking the buttons 20 times just to scroll 20 items down would be rather inconvenient for the users.

So we need something similar, that we already have in the scroll bar in our traditional Windows applications:

  • If the user clicks on the arrow button at the bottom of the scroll bar, and holds the mouse button in the position (there is a single mouse down event but no mouse up event), the application will scroll the content in small steps down.
  • Similarly, if the user clicks on the arrow button at the top of the scroll bar, and holds the mouse button in the position (there is a single mouse down event but no mouse up event), the application will scroll the content in small steps up.
  • If the user clicks on either of the arrow buttons, holds the mouse button in this position, but moves the mouse pointer out of the area of the arrow button (the mouse down event is raised in the area of the button, but a mouse out event is raised before the mouse up event) the content will be scrolled only while the mouse pointer is over the arrow button.
  • If the user clicks on the screen as the mouse pointer is out of the arrow button area, holds the mouse button in this position, and moves the pointer over the arrow button only later (the mouse down event is raised outside the area of the button) the content will be not scrolled.

To sum up the above rules for our case: the single mouse down event should happen while the mouse pointer is over the button, and the action performed by the application (in our case it was scrolling up / down) is repeated until either a mouse up event or a mouse out event occurs.

We decided to implement the requirements as a reusable component in AngularJS, namely a directive, that encapsulates the functionality and enables to apply it to various HTML elements declaratively.

We also wanted to provide the following parameters to our component:

  • Which action the component should repeat, similar to the other, built-in AngularJS events, like ng-click. Its value should be the name of the JavaScript function available in the scope.
  • The time interval to configure the frequency of the repetition the action. Its value should be a numeric value of the delay in milliseconds.
  • The time interval to configure the delay for starting the repetition (for example, the user has to hold the mouse button down for 1 sec. to start the repetition, but once it is started, the action is performed in every 0.2 sec.) was in our first scope of work, but it was selected as victim of  feature cutting.

We found several similar solutions on web blogs and forums, but none of them fulfilled our demands completely, or they simply just didn’t work.

Our implementation was created as an attribute-level AngularJS directive: the mandatory ‘on-mouse-hold’ attribute contains the name of the function that would be invoked as action for the mouse hold event. The optionally ‘mouse-hold-repeat’ attribute contains the delay for the repetition (in milliseconds), the default value is 0.5 sec.

Note: In this post I illustrate the usage of the directive in a non-SharePoint-specific application for those of you who are not interested in SharePoint, and to separate this piece of functionality from the other (IMHO not less interesting) SharePoint-related stuff. I plan to write a further post about using the directive in a SharePoint-specific application, namely how to load items dynamically in case of scrolling using the JavaScript client object model.

The following HTML snippet illustrates using the directive in simple case. There are two buttons having different actions and delays. In this case we simply count a numeric value up and down.

  1. <div ng-app="myApp" ng-controller="counterCtrl">
  2.     <button type="button" on-mouse-hold="countUp">Count up</button>
  3.     <div>{{counter}}</div>
  4.     <button type="button" on-mouse-hold="countDown" mouse-hold-repeat="50">Count down</button>
  5. </div>

The functionality of the AngularJS directive is implemented in the JavaScript code below:

  1. 'use strict';
  2.  
  3. var myApp = angular.module('myApp', []);
  4.  
  5. myApp.controller('counterCtrl', function ($scope) {
  6.  
  7.     $scope.counter = 0;
  8.  
  9.     $scope.countDown = function () {
  10.         $scope.counter–;
  11.     }
  12.  
  13.     $scope.countUp = function () {
  14.         $scope.counter++;
  15.     }
  16.  
  17. }).directive('onMouseHold', function ($parse, $interval) {
  18.     var stop;
  19.  
  20.     var dirDefObj = {
  21.         restrict: 'A',
  22.         scope: { method: '&onMouseHold' },
  23.         link: function (scope, element, attrs) {
  24.             var expressionHandler = scope.method();
  25.             var actionInterval = (attrs.mouseHoldRepeat) ? attrs.mouseHoldRepeat : 500;
  26.  
  27.             var startAction = function () {
  28.                 expressionHandler();
  29.                 stop = $interval(function () {
  30.                     expressionHandler();
  31.                 }, actionInterval);
  32.             };
  33.  
  34.             var stopAction = function () {
  35.                 if (stop) {
  36.                     $interval.cancel(stop);
  37.                     stop = undefined;
  38.                 }
  39.             };
  40.  
  41.             element.bind('mousedown', startAction);
  42.             element.bind('mouseup', stopAction);
  43.             element.bind('mouseout', stopAction);
  44.         }
  45.     };
  46.  
  47.     return dirDefObj;
  48. });

If you want to test the functionality online, visit this page on jsfiddle.

Leave a Comment »

No comments yet.

RSS feed for comments on this post. TrackBack URI

Leave a comment

Blog at WordPress.com.