webstaff: toward label printing
authorJason Etheridge <jason@esilibrary.com>
Mon, 6 Mar 2017 16:02:33 +0000 (11:02 -0500)
committerJason Etheridge <jason@esilibrary.com>
Mon, 6 Mar 2017 16:02:33 +0000 (11:02 -0500)
Open-ILS/src/templates/staff/cat/printlabels/index.tt2 [new file with mode: 0644]
Open-ILS/src/templates/staff/cat/printlabels/t_view.tt2 [new file with mode: 0644]
Open-ILS/src/templates/staff/share/print_templates/t_item_label.tt2 [new file with mode: 0644]
Open-ILS/web/js/ui/default/staff/cat/printlabels/app.js [new file with mode: 0644]

diff --git a/Open-ILS/src/templates/staff/cat/printlabels/index.tt2 b/Open-ILS/src/templates/staff/cat/printlabels/index.tt2
new file mode 100644 (file)
index 0000000..5093f0c
--- /dev/null
@@ -0,0 +1,33 @@
+[%
+  WRAPPER "staff/base.tt2";
+  ctx.page_title = l("Print Item Labels"); 
+  ctx.page_app = "egPrintLabels";
+%]
+
+[% BLOCK APP_JS %]
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/services/grid.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/services/ui.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/services/file.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/services/eframe.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/cat/printlabels/app.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/cat/services/record.js"></script>
+<script>
+angular.module('egCoreMod').run(['egStrings', function(s) {
+    s.KEY_EXPIRED = "[% l('Key expired, please close this window; it no longer remembers which items you are printing labels for.') %]";
+}]);
+</script>
+[% END %]
+
+<style>
+  /* FIXME: MOVE ME */
+  #item-status-barcode {width: 16em;}
+  #item-status-form { 
+    margin-bottom: 20px; 
+  }
+</style>
+
+<div ng-view></div>
+
+[% END %]
+
+
diff --git a/Open-ILS/src/templates/staff/cat/printlabels/t_view.tt2 b/Open-ILS/src/templates/staff/cat/printlabels/t_view.tt2
new file mode 100644 (file)
index 0000000..0b6b287
--- /dev/null
@@ -0,0 +1,73 @@
+<style>
+  /* TODO: move me */
+  .print-template-text {
+    height: 36em;
+    width: 100%;
+  }
+</style>
+
+<h2>[% l('Print Item Labels') %]</h2>
+
+<div class="row">
+  <div class="col-md-5">
+    <div class="form-inline">
+      <div class="form-group">
+        <label for="print_tempate_name">[% l('Template Name') %]</label>
+        <select id="print_template_name" class="form-control" ng-model="print.template_name" ng-change="template_changed()">
+          <option value="item_label">[% l('Item Label') %]</option>
+        </select>
+        <label for="print_context">[% l('Force Printer Context') %]</label>
+        <select class="form-control" ng-model="print.template_context">
+          <option value="default">[% l('Default') %]</option>
+          <option value="receipt">[% l('Receipt') %]</option>
+          <option value="label">[% l('Label') %]</option>
+          <option value="mail">[% l('Mail') %]</option>
+          <option value="offline">[% l('Offline') %]</option>
+        </select>
+      </div>
+    </div>
+  </div>
+  <div class="col-md-7">
+    <button class="btn btn-default pull-left" ng-click="save_locally()">[% l('Save Locally') %]</button>
+    <div class="btn-group pull-right">
+      <span class="btn btn-default btn-file">
+        [% l('Import') %]
+        <input type="file" eg-file-reader container="imported_print_templates.data">
+      </span>
+      <label class="btn btn-default"
+          eg-json-exporter generator="exportable_templates"
+          default-file-name="'[% l('print_templates.json') %]'">
+          [% l('Export Customized Templates') %]
+      </label>
+    </div>
+  </div>
+  <!-- other stuff -->
+</div>
+
+<hr/>
+
+<div class="row">
+  <div class="col-md-5">
+    <h3>[% l('Template') %]</h3>
+    <div ng-if="print.load_failed" class="alert alert-danger">
+      [% l(
+        "Unable to load template '[_1]'.  The web server returned an error.", 
+        '{{print.template_name}}') 
+      %]
+    </div>
+    <div>
+      <textarea ng-model="print.template_content" class="print-template-text">
+      </textarea>
+    </div>
+  </div>
+  <div class="col-md-7">
+    <h3>
+        [% l('Preview') %]
+        <button class="btn btn-default pull-right" ng-click="print_labels()">[% l('Print') %]</button>
+    </h3>
+    <div eg-print-template-output 
+      content="print.template_content" 
+      context="preview_scope"></div>
+  </div> <!-- col -->
+</div>
diff --git a/Open-ILS/src/templates/staff/share/print_templates/t_item_label.tt2 b/Open-ILS/src/templates/staff/share/print_templates/t_item_label.tt2
new file mode 100644 (file)
index 0000000..d59ba3d
--- /dev/null
@@ -0,0 +1,20 @@
+<!--
+Template for printing spine and pocket labels.  Available
+macros include:
+
+copies - list; each entry is a fleshed, flattened copy record
+with a variety of keys, including
+
+  barcode
+  call_number.record.simple_record.title (title)
+  call_number.label 
+  location.name
+
+-->
+<div style="page-break-after: always;" class="row" ng-repeat="copy in copies">
+<div class="col-md-4"><pre>{{copy['call_number.label']}}</pre></div>
+<div class="col-md-8"><pre>
+{{copy['call_number.record.simple_record.title']}}
+{{copy['call_number.record.simple_record.author']}}
+</pre></div>
+</div>
diff --git a/Open-ILS/web/js/ui/default/staff/cat/printlabels/app.js b/Open-ILS/web/js/ui/default/staff/cat/printlabels/app.js
new file mode 100644 (file)
index 0000000..bc650c0
--- /dev/null
@@ -0,0 +1,259 @@
+/**
+ * Vol/Copy Editor
+ */
+
+angular.module('egPrintLabels',
+    ['ngRoute', 'ui.bootstrap', 'egCoreMod', 'egUiMod', 'egGridMod'])
+
+.config(function($routeProvider, $locationProvider, $compileProvider) {
+    $locationProvider.html5Mode(true);
+    $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|blob):/); // grid export
+
+    var resolver = {
+        delay : ['egStartup', function(egStartup) { return egStartup.go(); }]
+    };
+
+    $routeProvider.when('/cat/printlabels/:dataKey', {
+        templateUrl: './cat/printlabels/t_view',
+        controller: 'LabelCtrl',
+        resolve : resolver
+    });
+
+})
+
+.factory('itemSvc', 
+       ['egCore',
+function(egCore) {
+
+    var service = {
+        copies : [], // copy barcode search results
+        index : 0 // search grid index
+    };
+
+    service.flesh = {   
+        flesh : 3, 
+        flesh_fields : {
+            acp : ['call_number','location','status','location','floating','circ_modifier','age_protect'],
+            acn : ['record','prefix','suffix'],
+            bre : ['simple_record','creator','editor']
+        },
+        select : { 
+            // avoid fleshing MARC on the bre
+            // note: don't add simple_record.. not sure why
+            bre : ['id','tcn_value','creator','editor'],
+        } 
+    }
+
+    // resolved with the last received copy
+    service.fetch = function(barcode, id, noListDupes) {
+        var promise;
+
+        if (barcode) {
+            promise = egCore.pcrud.search('acp', 
+                {barcode : barcode, deleted : 'f'}, service.flesh);
+        } else {
+            promise = egCore.pcrud.retrieve('acp', id, service.flesh);
+        }
+
+        var lastRes;
+        return promise.then(
+            function() {return lastRes},
+            null, // error
+
+            // notify reads the stream of copies, one at a time.
+            function(copy) {
+
+                var flatCopy;
+                if (noListDupes) {
+                    // use the existing copy if possible
+                    flatCopy = service.copies.filter(
+                        function(c) {return c.id == copy.id()})[0];
+                }
+
+                if (!flatCopy) {
+                    flatCopy = egCore.idl.toHash(copy, true);
+                    flatCopy.index = service.index++;
+                    service.copies.unshift(flatCopy);
+                }
+
+                return lastRes = {
+                    copy : copy, 
+                    index : flatCopy.index
+                }
+            }
+        );
+    }
+
+    return service;
+}])
+
+/**
+ * Label controller!
+ */
+.controller('LabelCtrl', 
+       ['$scope','$q','$window','$routeParams','$location','$timeout','egCore','egNet','ngToast','itemSvc',
+function($scope , $q , $window , $routeParams , $location , $timeout , egCore , egNet , ngToast , itemSvc ) {
+
+    var dataKey = $routeParams.dataKey;
+    console.debug('dataKey: ' + dataKey);
+
+    $scope.print = {
+        template_name : 'item_label',
+        template_output : '',
+        template_context : 'default'
+    };
+
+
+    if (dataKey && dataKey.length > 0) {
+
+        egNet.request(
+            'open-ils.actor',
+            'open-ils.actor.anon_cache.get_value',
+            dataKey, 'print-labels-these-copies'
+        ).then(function (data) {
+
+            if (data) {
+
+                console.log(data);
+                $scope.preview_scope = { 'copies' : [] };
+
+                var promises = [];
+
+                angular.forEach(data.copies, function(copy) {
+                    promises.push(
+                        itemSvc.fetch(null,copy).then(function(res) {
+                            $scope.preview_scope.copies.push(egCore.idl.toHash(res.copy, true));
+                        })
+                    )
+                });
+
+                $q.all(promises).then(function() {
+
+                    // today, staff, current_location, etc.
+                    egCore.print.fleshPrintScope($scope.preview_scope);
+
+                    $scope.template_changed(); // load the default
+
+                })
+            } else {
+                ngToast.danger(egCore.strings.KEY_EXPIRED);
+            }
+
+        });
+    }
+
+    $scope.print_labels = function() {
+        return egCore.print.print({
+            context : $scope.print.template_context,
+            template : $scope.print.template_name,
+            scope : $scope.preview_scope,
+        });
+    }
+
+    $scope.template_changed = function() {
+        $scope.print.load_failed = false;
+        egCore.print.getPrintTemplate($scope.print.template_name)
+        .then(
+            function(html) { 
+                $scope.print.template_content = html;
+                console.log('set template content');
+            },
+            function() {
+                $scope.print.template_content = '';
+                $scope.print.load_failed = true;
+            }
+        );
+        egCore.print.getPrintTemplateContext($scope.print.template_name)
+        .then(function(template_context) {
+            $scope.print.template_context = template_context;
+        });
+    }
+
+    $scope.save_locally = function() {
+        egCore.print.storePrintTemplate(
+            $scope.print.template_name,
+            $scope.print.template_content
+        );
+        egCore.print.storePrintTemplateContext(
+            $scope.print.template_name,
+            $scope.print.template_context
+        );
+    }
+
+    $scope.exportable_templates = function() {
+        var templates = {};
+        var contexts = {};
+        var deferred = $q.defer();
+        var promises = [];
+        egCore.hatch.getKeys('eg.print.template').then(function(keys) {
+            angular.forEach(keys, function(key) {
+                if (key.match(/^eg\.print\.template\./)) {
+                    promises.push(egCore.hatch.getItem(key).then(function(value) {
+                        templates[key.replace('eg.print.template.', '')] = value;
+                    }));
+                } else {
+                    promises.push(egCore.hatch.getItem(key).then(function(value) {
+                        contexts[key.replace('eg.print.template_context.', '')] = value;
+                    }));
+                }
+            });
+            $q.all(promises).then(function() {
+                if (Object.keys(templates).length) {
+                    deferred.resolve({
+                        templates: templates,
+                        contexts: contexts
+                    });
+                } else {
+                    ngToast.warning(egCore.strings.PRINT_TEMPLATES_FAIL_EXPORT);
+                    deferred.reject();
+                }
+            });
+        });
+        return deferred.promise;
+    }
+
+    $scope.imported_print_templates = { data : '' };
+    $scope.$watch('imported_print_templates.data', function(newVal, oldVal) {
+        if (newVal && newVal != oldVal) {
+            try {
+                var data = JSON.parse(newVal);
+                angular.forEach(data.templates, function(template_content, template_name) {
+                    egCore.print.storePrintTemplate(template_name, template_content);
+                });
+                angular.forEach(data.contexts, function(template_context, template_name) {
+                    egCore.print.storePrintTemplateContext(template_name, template_context);
+                });
+                $scope.template_changed(); // refresh
+                ngToast.create(egCore.strings.PRINT_TEMPLATES_SUCCESS_IMPORT);
+            } catch (E) {
+                ngToast.warning(egCore.strings.PRINT_TEMPLATES_FAIL_IMPORT);
+            }
+        }
+    });
+
+}])
+
+// 
+.directive('egPrintTemplateOutput', ['$compile',function($compile) {
+    return function(scope, element, attrs) {
+        scope.$watch(
+            function(scope) {
+                return scope.$eval(attrs.content);
+            },
+            function(value) {
+                // create an isolate scope and copy the print context
+                // data into the new scope.
+                // TODO: see also print security concerns in egHatch
+                var result = element.html(value);
+                var context = scope.$eval(attrs.context);
+                var print_scope = scope.$new(true);
+                angular.forEach(context, function(val, key) {
+                    print_scope[key] = val;
+                })
+                $compile(element.contents())(print_scope);
+            }
+        );
+    };
+}])
+
+