From 0dd316efea209e5e5de3e456b4e6562f011a1294 Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Sun, 12 Oct 2014 17:52:44 +0100 Subject: [PATCH] feat(jqLite): add private jqDocumentComplete function This helper function can be used to execute a callback only after the document has completed its loading, i.e. after the `load` event fires or immediately if the page has already loaded and `document.readyState === 'complete'`. --- src/.jshintrc | 1 + src/jqLite.js | 14 ++++++++++++++ test/.jshintrc | 1 + test/jqLiteSpec.js | 45 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 61 insertions(+) diff --git a/src/.jshintrc b/src/.jshintrc index d11760dc..784f77f4 100644 --- a/src/.jshintrc +++ b/src/.jshintrc @@ -140,6 +140,7 @@ "addEventListenerFn": false, "removeEventListenerFn": false, "jqLiteIsTextNode": false, + "jqLiteDocumentLoaded": false, /* apis.js */ "hashKey": false, diff --git a/src/jqLite.js b/src/jqLite.js index f4bd6df0..9b527088 100644 --- a/src/jqLite.js +++ b/src/jqLite.js @@ -453,6 +453,20 @@ function jqLiteRemove(element, keepData) { if (parent) parent.removeChild(element); } + +function jqLiteDocumentLoaded(action, win) { + win = win || window; + if (win.document.readyState === 'complete') { + // Force the action to be run async for consistent behaviour + // from the action's point of view + // i.e. it will definitely not be in a $apply + win.setTimeout(action); + } else { + // No need to unbind this handler as load is only ever called once + jqLite(win).on('load', action); + } +} + ////////////////////////////////////////// // Functions which are declared directly. ////////////////////////////////////////// diff --git a/test/.jshintrc b/test/.jshintrc index 9ce1b57a..811d0775 100644 --- a/test/.jshintrc +++ b/test/.jshintrc @@ -119,6 +119,7 @@ "JQLitePrototype": false, "addEventListenerFn": false, "removeEventListenerFn": false, + "jqLiteDocumentLoaded": false, /* apis.js */ "hashKey": false, diff --git a/test/jqLiteSpec.js b/test/jqLiteSpec.js index a6ae18d3..8f76d478 100644 --- a/test/jqLiteSpec.js +++ b/test/jqLiteSpec.js @@ -1993,4 +1993,49 @@ describe('jqLite', function() { }); }); + + describe('jqLiteDocumentLoaded', function() { + + function createMockWindow(readyState) { + return { + document: {readyState: readyState || 'loading'}, + setTimeout: jasmine.createSpy('window.setTimeout'), + addEventListener: jasmine.createSpy('window.addEventListener'), + removeEventListener: jasmine.createSpy('window.removeEventListener') + }; + } + + it('should execute the callback via a timeout if the document has already completed loading', function() { + function onLoadCallback() { } + + var mockWindow = createMockWindow('complete'); + + jqLiteDocumentLoaded(onLoadCallback, mockWindow); + + expect(mockWindow.addEventListener).not.toHaveBeenCalled(); + expect(mockWindow.setTimeout.mostRecentCall.args[0]).toBe(onLoadCallback); + }); + + + it('should register a listener for the `load` event', function() { + var onLoadCallback = jasmine.createSpy('onLoadCallback'); + var mockWindow = createMockWindow(); + + jqLiteDocumentLoaded(onLoadCallback, mockWindow); + + expect(mockWindow.addEventListener).toHaveBeenCalledOnce(); + }); + + + it('should execute the callback only once the document completes loading', function() { + var onLoadCallback = jasmine.createSpy('onLoadCallback'); + var mockWindow = createMockWindow(); + + jqLiteDocumentLoaded(onLoadCallback, mockWindow); + expect(onLoadCallback).not.toHaveBeenCalled(); + + jqLite(mockWindow).triggerHandler('load'); + expect(onLoadCallback).toHaveBeenCalledOnce(); + }); + }); });