LP#1646166 À la carte Hatch, on-call settings, strict access.
authorBill Erickson <berickxx@gmail.com>
Wed, 18 Jan 2017 19:36:11 +0000 (14:36 -0500)
committerKathy Lussier <klussier@masslnc.org>
Thu, 16 Feb 2017 20:21:35 +0000 (15:21 -0500)
1. Hatch now supports a al carte features instead of requiring all-or-none
functionality.  Supported features currently include printing, settings,
and offline.  (Note: offline handling pending merge of offline UI code).

2. Adds support for on-call setting keys.  On-Call keys are those that
can be set/get/remove'd from localStorage when Hatch is not avaialable,
even though Hatch is configured as the primary storage location for the
key in question.

The initital target use case for on-call keys are those that allow the
user to login and perform basic admin tasks (like disabling Hatch) even
when Hatch is down.  AKA Browser Staff Run Level 3.

3. egHatch no longer attempts requests at Hatch, falling through to
local requests when Hatch fails.  With the exception of on-call keys
(above), either Hatch is used or local requests are used, depending on
the configuration.  The goal is to prevent any unintended and
confusing blending of local and remote data.  In other words, if Hatch
is broken, it needs to be fixed or disabled for regular work flow to
continue.

4. Hatch now has a dedicated UI under workstation administration.

5. Workstation admin splash page rearranged to take advantage of more
horizontal space and avoid pushing so many options down the page.

Signed-off-by: Bill Erickson <berickxx@gmail.com>
Signed-off-by: Kathy Lussier <klussier@masslnc.org>
Open-ILS/src/templates/staff/admin/workstation/t_hatch.tt2 [new file with mode: 0644]
Open-ILS/src/templates/staff/admin/workstation/t_print_config.tt2
Open-ILS/src/templates/staff/admin/workstation/t_splash.tt2
Open-ILS/src/templates/staff/circ/checkin/t_checkin.tt2
Open-ILS/src/templates/staff/circ/patron/t_checkout.tt2
Open-ILS/web/js/ui/default/staff/admin/workstation/app.js
Open-ILS/web/js/ui/default/staff/circ/checkin/app.js
Open-ILS/web/js/ui/default/staff/circ/patron/checkout.js
Open-ILS/web/js/ui/default/staff/services/hatch.js
Open-ILS/web/js/ui/default/staff/services/print.js

diff --git a/Open-ILS/src/templates/staff/admin/workstation/t_hatch.tt2 b/Open-ILS/src/templates/staff/admin/workstation/t_hatch.tt2
new file mode 100644 (file)
index 0000000..d8d87ea
--- /dev/null
@@ -0,0 +1,52 @@
+<div class="container admin-splash-container">
+
+  <h2>[% l('Print / Storage Service ("Hatch")') %]</h2>
+
+  <div class="alert alert-success" ng-if="hatch_available">
+    [% l("Hatch is Available") %]
+    <span class="glyphicon glyphicon-thumbs-up"></span>
+  </div>
+
+  <div class="alert alert-danger" ng-if="!hatch_available">
+    [% l("Hatch is Not Installed In This Browser") %]
+  </div>
+
+  <div class="row">
+    <div class="col-md-6">
+      <div class="checkbox">
+        <label>
+          <input type="checkbox" 
+            ng-model="hatch_printing">
+            [% l('Use Hatch For Printing') %]
+        </label>
+      </div>
+    </div>
+  </div>
+
+  <div class="row new-entry">
+    <div class="col-md-6">
+      <div class="checkbox">
+        <label>
+          <input type="checkbox" 
+            ng-model="hatch_settings">
+            [% l('Store Local Settings in Hatch') %]
+        </label>
+      </div>
+    </div>
+  </div>
+
+  <div class="row new-entry">
+    <div class="col-md-6">
+      <div class="checkbox">
+        <label>
+          <input type="checkbox" 
+            ng-model="hatch_offline">
+            [% l('Store Offline Transaction Data in Hatch') %]
+        </label>
+      </div>
+    </div>
+  </div>
+
+</div>
+
+
index 8f4a34d..4c88af2 100644 (file)
 
   <div class="row"> 
     <div class="col-md-12">
-      <h2>[% l('Printer Settings for Remote Printing') %]</h2>
-      <div class="alert alert-warning" ng-show="hatchNotConnected()">
-        [% l("Hatch is not connected") %]
+      <h2>[% l('Remote Printer Settings') %]</h2>
+
+      <div class="alert alert-warning" ng-if="!hatchIsOpen()">
+[% l('Remote printing is not available on this browser.  The settings below will have no effect.') %]
+      </div>
+
+      <div class="alert alert-warning" 
+        ng-if="hatchIsOpen() && !useHatchPrinting()">
+        <p>
+[% l("Remote printing is not enabled on this browser.  The settings below will have no effect until remote printing is enabled.") %]
+          <a href="./admin/workstation/hatch" target="_self" 
+            title="[% l('Hatch Administration') %]">
+            [% l('Enable Remote Printing.') %]
+          </a>
+        </p>
       </div>
     </div>
   </div>
index bb80cc2..578ba73 100644 (file)
 <div class="container admin-splash-container">
 
   <div class="row">
-    <div class="col-md-6">
-      <div class="checkbox">
-        <label>
-          <input type="checkbox" ng-class="{disabled : !userHasAdminPerm}"
-            ng-model="hatchRequired" ng-change="updateHatchRequired()">
-[% l('This workstation uses a remote print / storage service ("Hatch")?') %]
-        </label>
+    <div class="col-md-6"><!-- left page column -->
+
+      <div class="row new-entry">
+        <div class="col-md-6">
+          <span class="glyphicon glyphicon-pushpin"></span>
+          <a target="_self" href="./admin/workstation/workstations">
+            [% l('Registered Workstations') %]
+          </a>
+        </div>
+      </div>
+
+      <div class="row new-entry">
+        <div class="col-md-6">
+          <span class="glyphicon glyphicon-print"></span>
+          <a target="_self" href="./admin/workstation/print/config">
+            [% l('Printer Settings') %]
+          </a>
+        </div>
+      </div>
+
+      <div class="row new-entry">
+        <div class="col-md-6">
+          <span class="glyphicon glyphicon-film"></span>
+          <a target="_self" href="./admin/workstation/print/templates">
+            [% l('Print Templates') %]
+          </a>
+        </div>
+      </div>
+
+      <div class="row new-entry">
+        <div class="col-md-6">
+          <span class="glyphicon glyphicon-info-sign"></span>
+          <a target="_self" href="./admin/workstation/stored_prefs">
+            [% l('Stored Preferences') %]
+          </a>
+        </div>
+      </div>
+
+      <div class="row new-entry">
+        <div class="col-md-6">
+          <span class="glyphicon glyphicon-retweet"></span>
+          <a target="_self" href="./admin/workstation/hatch">
+            [% l('Print/Storage Service ("Hatch")') %]
+          </a>
+        </div>
+      </div>
+
+      <div class="row new-entry">
+        <div class="col-md-4">
+          <div class="checkbox">
+            <label>
+              <input type="checkbox"
+                ng-model="disable_sound" 
+                  ng-change="apply_sound()">
+                [% l('Disable Sounds?') %]
+            </label>
+          </div>
+        </div>
+        <div class="col-md-8">
+          <span>Test: </span>
+          <button class="btn btn-success" ng-class="{disabled : disable_sound}" 
+            ng-click="test_audio('success')">[% l('Success') %]</button>
+          <button class="btn btn-info" ng-class="{disabled : disable_sound}" 
+            ng-click="test_audio('info')">[% l('Info') %]</button>
+          <button class="btn btn-warning" ng-class="{disabled : disable_sound}" 
+            ng-click="test_audio('warning')">[% l('Warning') %]</button>
+          <button class="btn btn-danger" ng-class="{disabled : disable_sound}" 
+            ng-click="test_audio('error')">[% l('Error') %]</button>
+        </div>
+      </div>
+
+    </div><!-- left column -->
+    <div class="col-md-6"><!-- right column -->
+
+      <div class="row">
+        <div class="col-md-8">
+            <label for="search_lib_selector">[% ('Default Search Library') %]</label>
+            <p>[% l('The default search library setting determines what library is searched from the advanced search screen and portal page by default. Manual selection of a search library will override it. One recommendation is to set the search library to the highest point you would normally want to search.') %]</p>
+        </div>
+        <div class="col-md-4">
+          <eg-org-selector id="search_lib_selector"
+            selected="search_lib" nodefault
+            label="[% l('Select...') %]"
+            onchange="handle_search_lib_changed">
+          </eg-org-selector>
+        </div>
+      </div><!-- row -->
+
+      <div class="row new-entry">
+        <div class="col-md-8">
+            <label for="pref_lib_selector">[% ('Preferred Library') %]</label>
+            <p>[% l('The preferred library is used to show copies and URIs regardless of the library searched. One recommendation is to set this to your workstation library so that local copies show up first in search results.') %]</p>
+        </div>
+        <div class="col-md-4">
+          <eg-org-selector id="pref_lib_selector"
+            selected="pref_lib" nodefault
+            label="[% l('Select...') %]"
+            onchange="handle_pref_lib_changed">
+          </eg-org-selector>
+        </div>
       </div>
-    </div><!-- row -->
-  </div>
-
-
-  <div class="row new-entry">
-    <div class="col-md-2">
-      <div class="checkbox">
-        <label>
-          <input type="checkbox"
-            ng-model="disable_sound" 
-              ng-change="apply_sound()">
-            [% l('Disable Sounds?') %]
-        </label>
+
+      <div class="row new-entry">
+        <div class="col-md-8">
+            <label for="adv_pane_selector">[% ('Advanced Search Default Pane') %]</label>
+            <p>[% l('Advanced search has secondary panes for Numeric and MARC Expert searching. You can change which one is loaded by default when opening a new catalog window here.') %]</p>
+        </div>
+        <div class="col-md-4">
+          <select id="adv_pane_selector" ng-model="adv_pane">
+            <option value="advanced">[% l('Advanced (default)') %]</option>
+            <option value="numeric" >[% l('Numeric') %]</option>
+            <option value="expert"  >[% l('MARC Expert') %]</option>
+          </select>
+        </div>
       </div>
-    </div>
-    <div class="col-md-4">
-      <span>Test: </span>
-      <button class="btn btn-success" ng-class="{disabled : disable_sound}" 
-        ng-click="test_audio('success')">[% l('Success') %]</button>
-      <button class="btn btn-info" ng-class="{disabled : disable_sound}" 
-        ng-click="test_audio('info')">[% l('Info') %]</button>
-      <button class="btn btn-warning" ng-class="{disabled : disable_sound}" 
-        ng-click="test_audio('warning')">[% l('Warning') %]</button>
-      <button class="btn btn-danger" ng-class="{disabled : disable_sound}" 
-        ng-click="test_audio('error')">[% l('Error') %]</button>
-    </div>
-  </div>
-
-  <div class="row new-entry">
-    <div class="col-md-4">
-        <label for="search_lib_selector">[% ('Default Search Library') %]</label>
-        <p>[% l('The default search library setting determines what library is searched from the advanced search screen and portal page by default. Manual selection of a search library will override it. One recommendation is to set the search library to the highest point you would normally want to search.') %]</p>
-    </div>
-    <div class="col-md-2">
-      <eg-org-selector id="search_lib_selector"
-        selected="search_lib" nodefault
-        label="[% l('Select...') %]"
-        onchange="handle_search_lib_changed">
-      </eg-org-selector>
-    </div>
-  </div>
-
-  <div class="row new-entry">
-    <div class="col-md-4">
-        <label for="pref_lib_selector">[% ('Preferred Library') %]</label>
-        <p>[% l('The preferred library is used to show copies and URIs regardless of the library searched. One recommendation is to set this to your workstation library so that local copies show up first in search results.') %]</p>
-    </div>
-    <div class="col-md-2">
-      <eg-org-selector id="pref_lib_selector"
-        selected="pref_lib" nodefault
-        label="[% l('Select...') %]"
-        onchange="handle_pref_lib_changed">
-      </eg-org-selector>
-    </div>
-  </div>
-
-  <div class="row new-entry">
-    <div class="col-md-4">
-        <label for="adv_pane_selector">[% ('Advanced Search Default Pane') %]</label>
-        <p>[% l('Advanced search has secondary panes for Numeric and MARC Expert searching. You can change which one is loaded by default when opening a new catalog window here.') %]</p>
-    </div>
-    <div class="col-md-2">
-      <select id="adv_pane_selector" ng-model="adv_pane">
-        <option value="advanced">[% l('Advanced (default)') %]</option>
-        <option value="numeric" >[% l('Numeric') %]</option>
-        <option value="expert"  >[% l('MARC Expert') %]</option>
-      </select>
-    </div>
-  </div>
-
-  <div class="row new-entry">
-    <div class="col-md-6">
-      <span class="glyphicon glyphicon-pushpin"></span>
-      <a target="_self" href="./admin/workstation/workstations">
-        [% l('Registered Workstations') %]
-      </a>
-    </div>
-  </div>
-
-  <div class="row new-entry">
-    <div class="col-md-6">
-      <span class="glyphicon glyphicon-print"></span>
-      <a target="_self" href="./admin/workstation/print/config">
-        [% l('Printer Settings') %]
-      </a>
-    </div>
-  </div>
-
-  <div class="row new-entry">
-    <div class="col-md-6">
-      <span class="glyphicon glyphicon-film"></span>
-      <a target="_self" href="./admin/workstation/print/templates">
-        [% l('Print Templates') %]
-      </a>
-    </div>
-  </div>
-
-  <div class="row new-entry">
-    <div class="col-md-6">
-      <span class="glyphicon glyphicon-info-sign"></span>
-      <a target="_self" href="./admin/workstation/stored_prefs">
-        [% l('Stored Preferences') %]
-      </a>
-    </div>
-  </div>
+
+    </div><!-- col -->
+  </div><!-- row -->
+
+
+
 
 </div>
index 9ce6ac7..ccc7e5d 100644 (file)
         <button class="btn btn-default" 
           ng-click="print_receipt()">[% l('Print Receipt') %]</button>
       </div>
-      <div class="checkbox" ng-if="using_hatch">
+      <div class="checkbox" ng-if="using_hatch_printer">
         <label>
           <input ng-model="show_print_dialog" type="checkbox"/>
           [% l('Show Print Dialog') %]
         </label>
       </div>
-      <div class="pad-horiz" ng-if="using_hatch"></div>
+      <div class="pad-horiz" ng-if="using_hatch_printer"></div>
       <div class="checkbox">
         <label>
           <input ng-model="trim_list" type="checkbox"/>
index ab12816..b2df987 100644 (file)
       [% l('Strict Barcode') %]
     </label>
   </div>
-  <div class="pad-horiz" ng-if="using_hatch"></div>
-  <div class="checkbox" ng-if="using_hatch">
+  <div class="pad-horiz" ng-if="using_hatch_printer"></div>
+  <div class="checkbox" ng-if="using_hatch_printer">
     <label>
       <input ng-model="show_print_dialog" type="checkbox"/>
       [% l('Show Print Dialog') %]
index 310356a..7de3694 100644 (file)
@@ -38,6 +38,11 @@ angular.module('egWorkstationAdmin',
         resolve : resolver
     });
 
+    $routeProvider.when('/admin/workstation/hatch', {
+        templateUrl: './admin/workstation/t_hatch',
+        controller: 'HatchCtrl',
+        resolve : resolver
+    });
 
     // default page 
     $routeProvider.otherwise({
@@ -165,16 +170,6 @@ function($q , $timeout , $location , egCore , egConfirmDialog) {
        ['$scope','$window','$location','egCore','egConfirmDialog',
 function($scope , $window , $location , egCore , egConfirmDialog) {
 
-    // ---------------------
-    // Hatch Configs
-    $scope.hatchRequired = 
-        egCore.hatch.getLocalItem('eg.hatch.required');
-
-    $scope.updateHatchRequired = function() {
-        egCore.hatch.setLocalItem(
-            'eg.hatch.required', $scope.hatchRequired);
-    }
-
     egCore.hatch.getItem('eg.audio.disable').then(function(val) {
         $scope.disable_sound = val;
     });
@@ -198,7 +193,7 @@ function($scope , $window , $location , egCore , egConfirmDialog) {
         $scope.adv_pane = val;
     });
     $scope.$watch('adv_pane', function(newVal, oldVal) {
-        if (newVal != oldVal) {
+        if (typeof newVal != 'undefined' && newVal != oldVal) {
             egCore.hatch.setItem('eg.search.adv_pane', newVal);
         }
     });
@@ -228,8 +223,12 @@ function($scope , egCore) {
     }
     $scope.setContext('default');
 
-    $scope.hatchNotConnected = function() {
-        return !egCore.hatch.hatchAvailable;
+    $scope.useHatchPrinting = function() {
+        return egCore.hatch.usePrinting();
+    }
+
+    $scope.hatchIsOpen = function() {
+        return egCore.hatch.hatchAvailable;
     }
 
     $scope.getPrinterByAttr = function(attr, value) {
@@ -847,4 +846,33 @@ function($scope , $q , $window , $location , egCore , egAlertDialog , workstatio
     }
 }])
 
+.controller('HatchCtrl',
+       ['$scope','egCore',
+function($scope , egCore) {
+    var hatch = egCore.hatch;  // convenience
+
+    $scope.hatch_available = hatch.hatchAvailable;
+    $scope.hatch_printing = hatch.usePrinting();
+    $scope.hatch_settings = hatch.useSettings();
+    $scope.hatch_offline  = hatch.useOffline();
+
+    // Apply Hatch settings as changes occur in the UI.
+    
+    $scope.$watch('hatch_printing', function(newval) {
+        if (typeof newval != 'boolean') return;
+        hatch.setLocalItem('eg.hatch.enable.printing', newval);
+    });
+
+    $scope.$watch('hatch_settings', function(newval) {
+        if (typeof newval != 'boolean') return;
+        hatch.setLocalItem('eg.hatch.enable.settings', newval);
+    });
+
+    $scope.$watch('hatch_offline', function(newval) {
+        if (typeof newval != 'boolean') return;
+        hatch.setLocalItem('eg.hatch.enable.offline', newval);
+    });
+
+}])
+
 
index 5428f16..a9b70ca 100644 (file)
@@ -41,7 +41,7 @@ function($scope , $q , $window , $location , egCore , checkinSvc , egGridDataPro
     $scope.checkins = checkinSvc.checkins;
     var today = new Date();
     $scope.checkinArgs = {backdate : today}
-    $scope.using_hatch = egCore.hatch.usingHatch();
+    $scope.using_hatch_printer = egCore.hatch.usePrinting();
     $scope.modifiers = {};
     $scope.fine_total = 0;
     $scope.is_capture = $location.path().match(/capture$/);
index 82d6e82..a32687e 100644 (file)
@@ -63,7 +63,7 @@ function($scope , $q , $routeParams , egCore , egUser , patronSvc ,
         );
     }
 
-    $scope.using_hatch = egCore.hatch.usingHatch();
+    $scope.using_hatch_printer = egCore.hatch.usePrinting();
 
     egCore.hatch.getItem('circ.checkout.strict_barcode')
         .then(function(sb){ $scope.strict_barcode = sb });
index f60e58a..769f801 100644 (file)
@@ -3,10 +3,10 @@
  *
  * Dispatches print and data storage requests to the appropriate handler.
  *
- * With each top-level request, if a connection to Hatch is established,
- * the request is relayed.  If a connection has not been attempted, an
- * attempt is made then the request is handled.  If Hatch is known to be
- * inaccessible, requests are routed to local handlers.
+ * If Hatch is configured to honor the request -- current request types
+ * are 'settings', 'offline', and 'printing' -- the request will be
+ * relayed to the Hatch service.  Otherwise, the request is handled
+ * locally.
  *
  * Most handlers also provide direct remote and local variants to the
  * application can decide to which to use as needed.
@@ -31,13 +31,37 @@ angular.module('egCoreMod')
     var service = {};
     service.msgId = 1;
     service.messages = {};
-    service.pending = [];
-    service.hatchAvailable = null;
+    service.hatchAvailable = false;
 
     // key/value cache -- avoid unnecessary Hatch extension requests.
     // Only affects *RemoteItem calls.
     service.keyCache = {}; 
 
+    /**
+     * List string prefixes for On-Call storage keys. On-Call keys
+     * are those that can be set/get/remove'd from localStorage when
+     * Hatch is not avaialable, even though Hatch is configured as the
+     * primary storage location for the key in question.  On-Call keys
+     * are those that allow the user to login and perform basic admin
+     * tasks (like disabling Hatch) even when Hatch is down.
+     * AKA Browser Staff Run Level 3.
+     * Note that no attempt is made to synchronize data between Hatch
+     * and localStorage for On-Call keys.  Only one destation is active 
+     * at a time and each maintains its own data separately.
+     */
+    service.onCallPrefixes = ['eg.workstation'];
+
+    // Returns true if the key can be set/get in localStorage even when 
+    // Hatch is not available.
+    service.keyIsOnCall = function(key) {
+        var oncall = false;
+        angular.forEach(service.onCallPrefixes, function(pfx) {
+            if (key.match(new RegExp('^' + pfx))) 
+                oncall = true;
+        });
+        return oncall;
+    }
+
     // write a message to the Hatch port
     service.sendToHatch = function(msg) {
         var msg2 = {};
@@ -54,31 +78,21 @@ angular.module('egCoreMod')
         $window.postMessage(msg2, $window.location.origin);
     }
 
-    // Send the request to Hatch if it's available.  
-    // Otherwise handle the request locally.
+    // Send request to Hatch or reject if Hatch is unavailable
     service.attemptHatchDelivery = function(msg) {
-
         msg.msgid = service.msgId++;
         msg.deferred = $q.defer();
 
-        if (service.hatchAvailable === false) { // Hatch is closed.
-            msg.deferred.reject(msg);
-
-        } else if (service.hatchAvailable === true) { // Hatch is open.
+        if (service.hatchAvailable) {
             service.messages[msg.msgid] = msg;
             service.sendToHatch(msg);
 
-        } else { // Hatch state unknown
-            service.messages[msg.msgid] = msg;
-            service.pending.push(msg);
-            $timeout(service.openHatch);
+        } else {
+            console.error(
+                'Hatch request attempted but Hatch is not available');
+            msg.deferred.reject(msg);
         }
 
-        /*
-        console.debug(
-            Object.keys(service.messages).length + " pending Hatch requests");
-        */
-
         return msg.deferred.promise;
     }
 
@@ -112,7 +126,7 @@ angular.module('egCoreMod')
         // the page body to indicate it's available.
 
         if (!$window.document.body.getAttribute('hatch-is-open')) {
-            service.hatchWontOpen('Hatch is not available');
+            console.debug("Hatch is not available");
             return;
         }
 
@@ -132,38 +146,6 @@ angular.module('egCoreMod')
         }); 
 
         service.hatchAvailable = true; // public flag
-        service.hatchOpened();
-    }
-
-    service.hatchWontOpen = function(err) {
-        console.debug("Hatch connection failed: " + err);
-        service.hatchAvailable = false;
-        while ( (msg = service.pending.shift()) ) {
-            msg.deferred.reject(msg);
-            delete service.messages[msg.msgid];
-        }
-    }
-
-    // Returns true if Hatch is required or if we are currently
-    // communicating with the Hatch service. 
-    service.usingHatch = function() {
-        return service.hatchAvailable || service.hatchRequired();
-    }
-
-    // Returns true if this browser (via localStorage) is 
-    // configured to require Hatch.
-    service.hatchRequired = function() {
-        return service.getLocalItem('eg.hatch.required');
-    }
-
-    service.hatchOpened = function() {
-        // let others know we're connected
-        if (service.onHatchOpen) service.onHatchOpen();
-
-        // Deliver any previously queued requests 
-        while ( (msg = service.pending.shift()) ) {
-            service.sendToHatch(msg);
-        };
     }
 
     service.remotePrint = function(
@@ -217,17 +199,36 @@ angular.module('egCoreMod')
         );
     }
 
+    service.usePrinting = function() {
+        return service.getLocalItem('eg.hatch.enable.printing');
+    }
+
+    service.useSettings = function() {
+        return service.getLocalItem('eg.hatch.enable.settings');
+    }
+
+    service.useOffline = function() {
+        return service.getLocalItem('eg.hatch.enable.offline');
+    }
+
     // get the value for a stored item
     service.getItem = function(key) {
-        return service.getRemoteItem(key)['catch'](
-            function(msg) {
-                if (service.hatchRequired()) {
-                    console.error("getRemoteItem() failed for key " + key);
-                    return null;
-                }
-                return service.getLocalItem(msg.key);
-            }
-        );
+
+        if (!service.useSettings())
+            return $q.when(service.getLocalItem(key));
+
+        if (service.hatchAvailable) 
+            return service.getRemoteItem(key);
+
+        if (service.keyIsOnCall(key)) {
+            console.warn("Unable to getItem from Hatch: " + key + 
+                ". Retrieving item from local storage instead");
+
+            return $q.when(service.getLocalItem(key));
+        }
+
+        console.error("Unable to getItem from Hatch: " + key);
+        return $q.reject();
     }
 
     service.getRemoteItem = function(key) {
@@ -246,7 +247,14 @@ angular.module('egCoreMod')
     service.getLocalItem = function(key) {
         var val = $window.localStorage.getItem(key);
         if (val == null) return;
-        return JSON.parse(val);
+        try {
+            return JSON.parse(val);
+        } catch(E) {
+            console.error(
+                "Deleting invalid JSON for localItem: " + key + " => " + val);
+            service.removeLocalItem(key);
+            return null;
+        }
     }
 
     service.getLoginSessionItem = function(key) {
@@ -266,15 +274,21 @@ angular.module('egCoreMod')
      * tmp values are removed during logout or browser close.
      */
     service.setItem = function(key, value) {
-        return service.setRemoteItem(key, value)['catch'](
-            function(msg) {
-                if (service.hatchRequired()) {
-                    console.error("setRemoteItem() failed for key " + key);
-                     return null;
-                }
-                return service.setLocalItem(msg.key, value);
-            }
-        );
+        if (service.useSettings())
+            return $q.when(service.setLocalItem(key, value));
+
+        if (service.hatchAvailable)
+            return service.setRemoteItem(key, value);
+
+        if (service.keyIsOnCall(key)) {
+            console.warn("Unable to setItem in Hatch: " + 
+                key + ". Setting in local storage instead");
+
+            return $q.when(service.setLocalItem(key, value));
+        }
+
+        console.error("Unable to setItem in Hatch: " + key);
+        return $q.reject();
     }
 
     // set the value for a stored or new item
@@ -320,28 +334,23 @@ angular.module('egCoreMod')
         $window.sessionStorage.setItem(key, jsonified);
     }
 
-    // assumes the appender and appendee are both strings
-    // TODO: support arrays as well
-    service.appendLocalItem = function(key, value) {
-        var item = service.getLocalItem(key);
-        if (item) {
-            if (typeof item != 'string') {
-                logger.warn("egHatch.appendLocalItem => "
-                    + "cannot append to a non-string item: " + key);
-                return;
-            }
-            value = item + value; // concatenate our value
-        }
-        service.setLocalitem(key, value);
-    }
-
     // remove a stored item
     service.removeItem = function(key) {
-        return service.removeRemoteItem(key)['catch'](
-            function(msg) { 
-                return service.removeLocalItem(msg.key) 
-            }
-        );
+        if (!service.useSettings())
+            return $q.when(service.removeLocalItem(key));
+
+        if (service.hatchAvailable) 
+            return service.removeRemoteItem(key);
+
+        if (service.keyIsOnCall(key)) {
+            console.warn("Unable to removeItem from Hatch: " + key + 
+                ". Removing item from local storage instead");
+
+            return $q.when(service.removeLocalItem(key));
+        }
+
+        console.error("Unable to removeItem from Hatch: " + key);
+        return $q.reject();
     }
 
     service.removeRemoteItem = function(key) {
@@ -379,15 +388,9 @@ angular.module('egCoreMod')
 
     // if set, prefix limits the return set to keys starting with 'prefix'
     service.getKeys = function(prefix) {
-        return service.getRemoteKeys(prefix)['catch'](
-            function() { 
-                if (service.hatchRequired()) {
-                    console.error("getRemoteKeys() failed");
-                    return [];
-                }
-                return service.getLocalKeys(prefix) 
-            }
-        );
+        if (service.useSettings()) 
+            return service.getRemoteKeys(prefix);
+        return $q.when(service.getLocalKeys(prefix));
     }
 
     service.getRemoteKeys = function(prefix) {
@@ -444,6 +447,10 @@ angular.module('egCoreMod')
         service.setLocalItem('eg.hatch.login_keys', keys);
     }
 
+    // The only requirement for opening Hatch is that the DOM be loaded.
+    // Open the connection now so its state will be immediately available.
+    service.openHatch();
+
     return service;
 }])
 
index d2ffbc9..ef01410 100644 (file)
@@ -59,7 +59,7 @@ function($q , $window , $timeout , $http , egHatch , egAuth , egIDL , egOrg , eg
     service.print_content = function(args) {
         service.fleshPrintScope(args.scope);
 
-        var promise = egHatch.hatchAvailable ?
+        var promise = egHatch.usePrinting() ?
             service.print_via_hatch(args) :
             service.print_via_browser(args);