Random musings

    Querystring helpers in .NET

    2015-03-24 20:00:00 +0000

    Often we have pages that have multiple optional querystrings. Here is a simple function that allows developers to write really clear code to get parameters from the querystring
    read more

    Tracking scroll depth of elements in javascript

    2015-03-23 20:00:00 +0000

    I wanted to make a plug that looked at what elements the user had scrolled into view for tracking purposes. Firstly we need to be able to capture when a user is scrolling on the page.

    (function() {
        var x =0;
        $(window).on("scroll", function() {
            x++;
            console.log("Scroll has been called " + x + " times");
        });
    }());

    Run this in a console window on any page with jQuery and the event fires thousands of times (every frame where jquery realises the user is scrolling)

    If we add a throttle function to the page, like Throttle / Debounce from Ben Alman then we can fire the event every so often so the javascript isn’t overrun.

    (function() {
        var x = 0;
        $(window).on("scroll", $.throttle( 1000, function() { 
            x++;
            console.log("Scroll has been called " + x + " times");
        }));
    }());

    Now we can test every second how far the user has scrolled - but we only trigger it if the user has continued scrolling. Now we should determine how far down the page the user has scrolled.

    (function() {
        var $window = $(window),
            docHeight = $(document).height(),
            winHeight = window.innerHeight || $window.height();
        $window.on("scroll", $.throttle( 1000, function() { 
            var scrollDistance = $window.scrollTop() + winHeight;
            x++;
            console.log(" scroll is at  " + scrollDistance);
        }));
    }());

    But we needn’t log every time we scroll - only when we get further down the page.

    (function() {
        var maxScrollDistnace = 0,
            $window = $(window),
            docHeight = $(document).height(),
            winHeight = window.innerHeight || $window.height();
        $window.on("scroll", $.throttle( 1000, function() { 
            var scrollDistance = $window.scrollTop() + winHeight;
            if(scrollDistance > maxScrollDistnace) {
                maxScrollDistnace = scrollDistance;
                console.log("scroll got as far down as... " + maxScrollDistnace);
            }
        }));
    }());

    Now we want to check against a list of elements to see if they have become visible in the page. It’s probably best at this moment to make this a jquery plugin, as we need to pass in a selector element.

    (function ( $ ) {
        $.fn.scrollLog = function(options) {
            var $window = $(window),
                docHeight = $(document).height(),
                winHeight = window.innerHeight || $window.height(),
                maxScrollDistnace = 0,
                selector = (options && options.selector) || "article",
                onLog = $.isFunction(options.onLog) ? options.onLog : function(e) {
                    console.log("Scrolled to " + $(e).attr("class"));
                },
                elements = $(selector);
            $window.on("scroll", $.throttle( 1000, function() { 
                var scrollDistance = $window.scrollTop() + winHeight;
                if(scrollDistance > maxScrollDistnace) {
                    maxScrollDistnace = scrollDistance;
                    $.each(elements, function(index, e) {
                        if(!$(e).data("hasBeenSeen")) {
                            if ( scrollDistance >= $(e).offset().top ) {
                                $(e).data("hasBeenSeen", true);
                                onLog(e);
                            }
                        }
                    });
    
    
                }
            }));
        };
    }( jQuery ));

    Now apply this to any page using the following line:

    $.fn.scrollLog();

    And it will tell you in console.log every time you scroll one of your “article” html elements into the view.

    Other examples would be, if you wanted fire an ajax call to record the id every time you got scrolled a list item of type SearchResult into view, you can use this…

    $.fn.scrollLog({
        selector:"li.searchResult",
        onLog:function(e) {
            var id = $(e).id;
            $.ajax("/user/viewed/" + id);
        }
    });

    This script could probably do with some enhancements - ie - what if the user resizes the window? If so it’d probably be best to reset the docHeight, winHeight & maxScrollDistnace parameters to make sure we reset where the user has got to for responsive layouts.

    I have done a little bit of research and it seems there are some interesting examples of similar work online, like Scrolldepth which is intended for Google Analytics tracking.

    How to simulate ajax in qunit tests

    2015-03-22 20:00:00 +0000

    When writing qUnit, it often gets hard trying to simulate an ajax operation.

    Often you find yourself writing passing tests, but then breaking them when you actually have to provide the data service, or worse tightly coupling them to the actual ajax calls, or hack apart your javascript just to write tests. This doesn’t have to happen.

    I have written a short helper to allow us to test async.

    function resolveAfter (time, args) {
        var dfd = new $.Deferred();
        window.setTimeout(function () {
            dfd.resolve(args);
        }, time || 1000);
        return dfd.promise();
    }
    function failAfter (time, args) {
        var dfd = new $.Deferred();
        window.setTimeout(function () {
            dfd.reject(args);
        }, time || 1000);
        return dfd.promise();
    }
     

    How does it work?

    Imagine you have a very simple bit of code that shows results from the data set

    $.fn.examplePlugin = function() {
     
        return this.each(function() {
            
            var $this = $(this);
            $this.on("click", function() {
                Data.getData().
                    then(function(d) {
                        $this.data("pass", true);
                        $this.html(d.message);
                        $this.trigger("update");
                    }).
                    fail(function(args) {
                        $this.data("pass", false);
                        $this.text("Could not get any data");
                        $this.trigger("update");
                    });
            });
            
        });
     
    };

    Now we should be writing qUnit tests to assert that it works in all scenarios.

    Here are two such tests:

    //Create a mock object ready for faking the getData call
    var Data = {getData: function() {}};
    
    test("Control updates after a passed getData call", function () {
        stop();
        
        //Setup the UI.
        var $div = $("<div></div>").appendTo("body");
        $div.examplePlugin();
        
        //Setup the data service
        Data.getData = function() {
            return resolveAfter(10, {message: "example data"});
        };
        
        //Test the functionality
        $div.trigger("click");
        $div.on("update", function() {
            start();
            ok($div.data("pass"), "Sucessful service sets pass to true");
            equal($div.html(), "example data", "Data returned updates the UI");
        });
    });
    
    test("Control updates after a failed getData call", function () {
        stop();
        
        //Setup the UI.
        var $div = $("<div></div>").appendTo("body");
        $div.examplePlugin();
        
        //Setup the data service
        Data.getData = function() {
            return failAfter(10, {message: "example data"});
        };
        
        $div.trigger("click");
        $div.on("update", function() {
            start();
            ok(!$div.data("pass"), "Failed data set pass to false");
            equal($div.text(), "Could not get any data", "Failed data message");
        });
    });

    As you can see, we can mock out data request really easily with the style of data service. You simulate an existing Data service and get reliable output so you can test every scenario. This means there is no excuse to write code for if your data services fail to return with a success message - it’s a very simple task to write code to test for the failing data service scenarios.

    How to test async functions in qUnit

    2015-03-21 20:00:00 +0000

    Writing a qUnit test is fairly straight forward. The syntax is as follows:

    test("Here is a test", function() {
        ok(true, "A passing test.");
    });

    It doesn’t work when you have anything that replies on async behaviour (ajax, settimeout, animation ect…).

    test("This won't work", function() {
        window.setTimeout(function () {
            ok(true, "This won't get associated with the test.");
        }, 1000);
    });

    The output is as follows:

    Expected at least one assertion, but none were run - call expect(0) to accept zero assertions.

    And then finds an assertion outside the test flow.

    Uncaught Error: ok() assertion outside test context

    If you want to write an asynchronous test, you will have to stop the test runner and tell it when to continue testing. This is the sytax to do so…

    test("Here is an async", function() {
        stop();
        window.setTimeout(function () {
            start();
            ok(true, "A passing test.");
        }, 1000);
    });

    If you don’t like the concept of having to stop the test runner, there’s a shorthand way to write this test.

    asyncTest("Another async test", function() {
        window.setTimeout(function () {
            start();
            ok(true, "Didn't have to say 'stop()'.");
        }, 1000);
    });

    This has been a primer for writing async tests in qUnit. In a later post, I will show a helper to help simulate ajax calls.

    The command pattern - using it on deferred objects in javascript

    2015-03-20 20:00:00 +0000

    In the previous article we created some commands an ran them through the system.

    In this article I will show how easy it is to make these events wait for each other to complete.
    read more