Monday, November 17, 2014

Postback not happening on ASP:LinkButton with Adblock Plus

I recently had a problem while working on an ASP.NET website where I had a page doing a postback on an ASP LinkButton, except in FireFox with AdBlock enabled, the postback just wasn't happening. Clicking on the button resulted in nothing. When I examined the markup on the page it looked like this:

<a id="ctl00_ctl00_MainContent_Content_btnReserveTickets" class="btn" href="javascript:__doPostBack('/* postback param*/','')">Buy</a>

It looked like the Javascript initiating the postback should have been firing. I put a breakpoint i that __doPostBack function, but it didn't get hit. There were no console errors either. I racked my mind and googled around with no luck. The only potential solution I could find - to add a CausesValidation="false" attribute to the LinkButton - didn't work.

Finally I posed my question on StackOverflow and walther over there gave me a hint that led to the solution. He suggested that I look for AdBlock's blacklisted keywords that may have been attached to the name or attribute or something of the LinkButton, such as "ad" or "facebook" or so on and so forth.

In the end, I discovered that there was a function adding a click handler to the LinkButton that was sending a Google Analytics event, as follows:

$(anchorEl).click(function () { 
                        ga('send', 'event', 'outbound', 'click', anchorEl.href, {
                            'hitCallback':
                              function () {
                                  document.location = anchorEl.href;
                              }
                        });
                        return false;
                    }); 

It turns out that once this click event gets attached, and because it's Google Analytics, all javascript on the LinkButton gets stripped off!

In the end this blogpost guided me to a solution: I wrapped the click event binding above in the following if statement:

if (ga.hasOwnProperty('loaded') && ga.loaded === true) {
    // bind event
}
This means that if AdBlock is ever blocking Google Analytics from loading, the anchorEl's click event is never bound to that Google Analytics event (binding it successfully would be useless anyway, since Google Analytics isn't loaded), and AdBlock doesn't strip the Javascript from my LinkButton.

Sunday, November 16, 2014

Learning Angular | Using a factory to make an API call

I was recently building an Angular app in which I had a search page and a search results page. Obviously, the API call would be made after a user hit the search button on the search page, but the question was whether I should make the call from the search page or the search results page.

Search page on the left, search results on the right
My initial solution was extremely complex - I tried recording the selected parameters, and then I created a super-complicated route that allowed me to pass them into the the search results page. However, that was way too complicated, and I quickly realized that there had to be a better way.

After asking around a bit on some Angular help channels, I figured out that Angular services and factories are singletons! This means that I can make the call in the search controller, store the results in my API factory, and then bootstrap my search results controller user the cached results. This ended up being a much faster, simpler, neater implementation!

Here's how my final implementation worked:

Search function - searchController.js

$scope.search = function() {
        var searchParameters = {
            // create search object
        }
        
        API.search(searchParameters).success(function (data, status, headers, config) {
            $location.url('/results');
        });
    };


Controller - searchResultsController.js

// gets the cached results from the API
var data = API.getSearchResults();

    

API factory - API.js

angular.module("donorsApp").factory("API", ["$http", function ($http) {
    
    var constructSearchUrl = function (searchParameters) {
        //returns URL;
    }
    
    // cached results
    var results = null;
    
    return {

        // gets called from the searchController             
        search : function (searchParameters) {
            var request = $http.jsonp(constructSearchUrl(searchParameters));
            
            request.success(function (data, status, headers, config) {
                // caches the returned results
                results = data;
            });
            
            return request;
        },
        
        // gets called from the searchResultsController
        getSearchResults: function() {
            return results;
        }
        
    }
    

}]);

Lesson learned: don't make API calls from the controller, make them from a factory or a service, and then cache the results as needed.