Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I use Angular to hook into clicking a link, and add the href before the click completes?

The context:
It is necessary for me to fetch the URL from the server when the user clicks the link. It is not available beforehand. The click on the link should work as normal, but with that fetched URL.

The procedure is as follows:

  • The link contains href="#" before it is clicked
  • The link is clicked
  • I hook into the click with ng-mousedown or ng-click, and fetch the URL from the server using a $http request.
  • The href of the link is updated with the URL.
  • The original click should complete (with that new url, not the # placeholder).

The problem comes on the last step. Since the $http request is asynchronous, I suspect there is a timing issue. My suspicion is: If the server request is fast enough, it will go through, and the URL is changed before the original click goes through. If not, then the original click goes through and tries to go to the # placeholder URL. So nothing will happen when clicking the link.

I want to make the original click wait for the result of the $http request to have returned with the URL. The reason the original click is important is that it could either be a left or a middle mouse button click (cmd+click), and I don't know which one, so it'd be hard to call it myself from the javascript.

So, any clever ideas on how can make the original click go through with the fetched URL?

like image 457
Magne Avatar asked Apr 11 '14 22:04

Magne


2 Answers

If you really can't resolve the link before a click is detected, then you would be better off using an ngClick directive to call a function which might show a loading screen while waiting for your $http promise to resolve and then use $location.path() to actually initiate the route change. For example:

The HTML

<button ng-click="changeRoute($event)">Click me</button>

And the code

angular.module('myApp', [])
  .controller('pageCtrl', function($scope, $http, $location, $window) {
    $scope.changeRoute = function(event) {
      console.log('Fetching URL');

      // The popup must be created before the callback, because the callback 
      // is not considered user-initiated by the browser. Non-user-initiated
      // events always open in a new browser window (not a tab).
      if (event.which === 2 || (event.which ===1 && (event.metaKey || event.ctrlKey))) {
        // MIDDLE CLICK or CMD+LEFTCLICK - new tab
        var popup = $window.open("about:blank", "_blank"); 
        // the about:blank is to please Chrome, and _blank to please Firefox
      }

      $http.get('/some/path')
        .success(function(data){
          //check that it's a good URL, then route to it
          //$location.path(data);
          if (event.which === 1 && !event.metaKey && !event.ctrlKey) {
            // LEFTCLICK - same window
            $window.location.href = data;
          } else if (event.which === 2 || (event.which ===1 && (event.metaKey || event.ctrlKey))) {
            // MIDDLE CLICK or CMD+LEFTCLICK - new tab
            popup.location = data;
          }
        })
        .error(function(data, status, headers, config) {
          console.error('Failed to get URL: ' + status);
        });
      console.log('Fetch request sent, awaiting response...');
    };
  });

Edit

Updated to support external links and middle click will now open in a new window

Updated to support:

  • CMD+click and CTRL+click to function as a middle mouse click.
  • Putting in a new tab, instead of a new window.
like image 180
morloch Avatar answered Nov 18 '22 10:11

morloch


You should do event.preventDefault() first to make time for $http to complete its process. To do that in Angular, you should pass $event to the ng-click handler.

Example HTML:

<body ng-controller="appController">
    <a href="#" ng-click="navigate($event)">LINK</a>
</body>

App:

var app = angular.module('app', []);
app.controller('appController',
    function ($scope, $http, $window, $log) {
        // generic function for window navigation
        function nav(url, newTab) {
            if (newTab) {
                $window.open(url, '_blank');
            } else {
                $window.location.href = url;
            }
        }
        // handler for ng-click directive
        $scope.navigate = function (event) {
            // This is the key -> preventing default navigation
            event.preventDefault();
            // determine whether to open a new page or not
            var newTab = ((event.which === 1 && (event.metaKey || event.ctrlKey)) || event.which === 2);
            // get the URL from a file
            $http.get('links.json')
                .success(function (data, status, headers, config) {
                    // navigate when URL is fetched
                    nav(data.someLink, newTab);
                })
                .error(function (data, status, headers, config) {
                    $log.debug('Error:', data, status);
                });

        };
    });

See an example of the exposed $event object here.

like image 23
Onur Yıldırım Avatar answered Nov 18 '22 08:11

Onur Yıldırım