From 23b955c6b4dc5452b5f906587fb2e463b36d8aba Mon Sep 17 00:00:00 2001
From: Ian Gilman <ian@iangilman.com>
Date: Wed, 10 Aug 2016 10:35:08 -0700
Subject: [PATCH] Updated with improved API

---
 src/tiledimage.js          | 46 +++++++++++++++++++++++++++++---------
 test/modules/tiledimage.js | 40 +++++++++++++++++++++++++++++++++
 2 files changed, 75 insertions(+), 11 deletions(-)

diff --git a/src/tiledimage.js b/src/tiledimage.js
index c273223b..6acfc08c 100644
--- a/src/tiledimage.js
+++ b/src/tiledimage.js
@@ -163,6 +163,8 @@ $.TiledImage = function( options ) {
 
     }, options );
 
+    this._fullyLoaded = false;
+
     this._xSpring = new $.Spring({
         initial: x,
         springStiffness: this.springStiffness,
@@ -218,6 +220,37 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
         return this._needsDraw;
     },
 
+    /**
+     * @returns {Boolean} Whether all tiles necessary for this TiledImage to draw at the current view have been loaded.
+     */
+    getFullyLoaded: function() {
+        return this._fullyLoaded;
+    },
+
+    // private
+    _setFullyLoaded: function(flag) {
+        if (flag === this._fullyLoaded) {
+            return;
+        }
+
+        this._fullyLoaded = flag;
+
+        /**
+         * Fired when the TiledImage's "fully loaded" flag (whether all tiles necessary for this TiledImage
+         * to draw at the current view have been loaded) changes.
+         *
+         * @event fully-loaded-change
+         * @memberof OpenSeadragon.TiledImage
+         * @type {object}
+         * @property {Boolean} fullyLoaded - The new "fully loaded" value.
+         * @property {OpenSeadragon.TiledImage} eventSource - A reference to the TiledImage which raised the event.
+         * @property {?Object} userData - Arbitrary subscriber-defined object.
+         */
+        this.raiseEvent('fully-loaded-change', {
+            fullyLoaded: this._fullyLoaded
+        });
+    },
+
     /**
      * Clears all tiles and triggers an update on the next call to
      * {@link OpenSeadragon.TiledImage#update}.
@@ -914,18 +947,9 @@ function updateViewport( tiledImage ) {
     // Load the new 'best' tile
     if (best && !best.context2D) {
         loadTile( tiledImage, best, currentTime );
+        tiledImage._setFullyLoaded(false);
     } else {
-        /**
-         * Fired every time the TiledImage is drawn when all the necessary tiles for the
-         * current view have been fully loaded.
-         *
-         * @event fully-loaded
-         * @memberof OpenSeadragon.TiledImage
-         * @type {object}
-         * @property {OpenSeadragon.TiledImage} eventSource - A reference to the TiledImage which raised the event.
-         * @property {?Object} userData - Arbitrary subscriber-defined object.
-         */
-        tiledImage.raiseEvent('fully-loaded');
+        tiledImage._setFullyLoaded(true);
     }
 }
 
diff --git a/test/modules/tiledimage.js b/test/modules/tiledimage.js
index d7a46aef..3dc0eb33 100644
--- a/test/modules/tiledimage.js
+++ b/test/modules/tiledimage.js
@@ -222,6 +222,7 @@
         });
     });
 
+    // ----------
     asyncTest('getClipBounds', function() {
         var clip = new OpenSeadragon.Rect(100, 200, 800, 500);
 
@@ -292,6 +293,7 @@
         });
     });
 
+    // ----------
     asyncTest('fitBounds', function() {
 
         function assertRectEquals(actual, expected, message) {
@@ -338,6 +340,7 @@
         ]);
     });
 
+    // ----------
     asyncTest('fitBounds in constructor', function() {
 
         function assertRectEquals(actual, expected, message) {
@@ -383,6 +386,7 @@
             }]);
     });
 
+    // ----------
     asyncTest('fitBounds with clipping', function() {
 
         function assertRectEquals(actual, expected, message) {
@@ -426,4 +430,40 @@
                 fitBoundsPlacement: OpenSeadragon.Placement.TOP_LEFT
             }]);
     });
+
+    // ----------
+    asyncTest('fullyLoaded', function() {
+        viewer.addHandler('open', function openHandler() {
+            viewer.removeHandler('open', openHandler);
+
+            var image = viewer.world.getItemAt(0);
+            equal(image.getFullyLoaded(), false, 'not fully loaded at first');
+
+            var count = 0;
+
+            var fullyLoadedChangeHandler = function(event) {
+                if (count === 0) {
+                    equal(event.fullyLoaded, true, 'event includes true fullyLoaded property');
+                    equal(image.getFullyLoaded(), true, 'image is fully loaded after event');
+                    viewer.viewport.zoomBy(5, null, true);
+                } else if (count === 1) {
+                    equal(event.fullyLoaded, false, 'event includes false fullyLoaded property');
+                    equal(image.getFullyLoaded(), false, 'image is not fully loaded after zoom');
+                } else {
+                    image.removeHandler('fully-loaded-change', fullyLoadedChangeHandler);
+                    equal(image.getFullyLoaded(), true, 'image is once again fully loaded');
+                    start();
+                }
+
+                count++;
+            };
+
+            image.addHandler('fully-loaded-change', fullyLoadedChangeHandler);
+        });
+
+        viewer.open([{
+            tileSource: '/test/data/tall.dzi',
+        }]);
+    });
+
 })();