]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/web/js/ui/default/staff/services/ui.js
LP#1641708: follow-up - only record hold_id when logging hold placement
[working/Evergreen.git] / Open-ILS / web / js / ui / default / staff / services / ui.js
1 /**
2   * UI tools and directives.
3   */
4 angular.module('egUiMod', ['egCoreMod', 'ui.bootstrap'])
5
6
7 /**
8  * <input focus-me="iAmOpen"/>
9  * $scope.iAmOpen = true;
10  */
11 .directive('focusMe', 
12        ['$timeout','$parse', 
13 function($timeout , $parse) {
14     return {
15         link: function(scope, element, attrs) {
16             var model = $parse(attrs.focusMe);
17             scope.$watch(model, function(value) {
18                 if(value === true) 
19                     $timeout(function() {element[0].focus()});
20             });
21             element.bind('blur', function() {
22                 $timeout(function() {
23                     scope.$apply(model.assign(scope, false));
24                 });
25             })
26         }
27     };
28 }])
29
30 /**
31  * <input blur-me="pleaseBlurMe"/>
32  * $scope.pleaseBlurMe = true
33  * Useful for de-focusing when no other obvious focus target exists
34  */
35 .directive('blurMe', 
36        ['$timeout','$parse', 
37 function($timeout , $parse) {
38     return {
39         link: function(scope, element, attrs) {
40             var model = $parse(attrs.blurMe);
41             scope.$watch(model, function(value) {
42                 if(value === true) 
43                     $timeout(function() {element[0].blur()});
44             });
45             element.bind('focus', function() {
46                 $timeout(function() {
47                     scope.$apply(model.assign(scope, false));
48                 });
49             })
50         }
51     };
52 }])
53
54
55 // <input select-me="iWantToBeSelected"/>
56 // $scope.iWantToBeSelected = true;
57 .directive('selectMe', 
58        ['$timeout','$parse', 
59 function($timeout , $parse) {
60     return {
61         link: function(scope, element, attrs) {
62             var model = $parse(attrs.selectMe);
63             scope.$watch(model, function(value) {
64                 if(value === true) 
65                     $timeout(function() {element[0].select()});
66             });
67             element.bind('blur', function() {
68                 $timeout(function() {
69                     scope.$apply(model.assign(scope, false));
70                 });
71             })
72         }
73     };
74 }])
75
76
77 // 'reverse' filter 
78 // <div ng-repeat="item in items | reverse">{{item.name}}</div>
79 // http://stackoverflow.com/questions/15266671/angular-ng-repeat-in-reverse
80 // TODO: perhaps this should live elsewhere
81 .filter('reverse', function() {
82     return function(items) {
83         return items.slice().reverse();
84     };
85 })
86
87 /**
88  * Progress Dialog. 
89  *
90  * egProgressDialog.open();
91  * egProgressDialog.open({value : 0});
92  * egProgressDialog.open({value : 0, max : 123});
93  * egProgressDialog.increment();
94  * egProgressDialog.increment();
95  * egProgressDialog.close();
96  *
97  * Each dialog has 2 numbers, 'max' and 'value'.
98  * The content of these values determines how the dialog displays.  
99  *
100  * There are 3 flavors:
101  *
102  * -- value is set, max is set
103  * determinate: shows a progression with a percent complete.
104  *
105  * -- value is set, max is unset
106  * semi-determinate, with a value report.  Shows a value-less
107  * <progress/>, but shows the value as a number in the dialog.
108  *
109  * This is useful in cases where the total number of items to retrieve
110  * from the server is unknown, but we know how many items we've
111  * retrieved thus far.  It helps to reinforce that something specific
112  * is happening, but we don't know when it will end.
113  *
114  * -- value is unset
115  * indeterminate: shows a generic value-less <progress/> with no 
116  * clear indication of progress.
117  *
118  * Only 1 egProgressDialog instance will be activate at a time.
119  * Each invocation of .open() destroys any existing instance.
120  */
121
122 /* Simple storage class for egProgressDialog data maintenance.
123  * This data lives outside of egProgressDialog so it can be 
124  * directly imported into egProgressDialog's $uibModalInstance.
125  */
126 .factory('egProgressData', [
127     function() {
128         var service = {}; // max/value initially unset
129
130         service.reset = function() {
131             delete service.max;
132             delete service.value;
133         }
134
135         service.hasvalue = function() {
136             return Number.isInteger(service.value);
137         }
138
139         service.hasmax = function() {
140             return Number.isInteger(service.max);
141         }
142
143         service.percent = function() {
144             if (service.hasvalue()  && 
145                 service.hasmax()    && 
146                 service.max > 0     &&
147                 service.value <= service.max)
148                 return Math.floor((service.value / service.max) * 100);
149             return 100;
150         }
151
152         return service;
153     }
154 ])
155
156 .factory('egProgressDialog', [
157             'egProgressData','$uibModal', 
158     function(egProgressData , $uibModal) {
159     var service = {};
160
161     service.open = function(args) {
162         service.close(); // force-kill existing instances.
163
164         // Reset to an indeterminate progress bar, 
165         // overlay with caller values.
166         egProgressData.reset();
167         service.update(angular.extend({}, args));
168
169         return $uibModal.open({
170             templateUrl: './share/t_progress_dialog',
171             controller: ['$scope','$uibModalInstance','egProgressData',
172                 function( $scope , $uibModalInstance , egProgressData) {
173                   service.currentInstance = $uibModalInstance;
174                   $scope.data = egProgressData; // tiny service
175                 }
176             ]
177         });
178     };
179
180     service.close = function() {
181         if (service.currentInstance) {
182             service.currentInstance.close();
183             delete service.currentInstance;
184         }
185     }
186
187     // Set the current state of the progress bar.
188     service.update = function(args) {
189         if (args.max != undefined) 
190             egProgressData.max = args.max;
191         if (args.value != undefined) 
192             egProgressData.value = args.value;
193     }
194
195     // Increment the current value.  If no amount is specified,
196     // it increments by 1.  Calling increment() on an indetermite
197     // progress bar will force it to be a (semi-)determinate bar.
198     service.increment = function(amt) {
199         if (!Number.isInteger(amt)) amt = 1;
200
201         if (!egProgressData.hasvalue())
202             egProgressData.value = 0;
203
204         egProgressData.value += amt;
205     }
206
207     return service;
208 }])
209
210 /**
211  * egAlertDialog.open({message : 'hello {{name}}'}).result.then(
212  *     function() { console.log('alert closed') });
213  */
214 .factory('egAlertDialog', 
215
216         ['$uibModal','$interpolate',
217 function($uibModal , $interpolate) {
218     var service = {};
219
220     service.open = function(message, msg_scope) {
221         return $uibModal.open({
222             templateUrl: './share/t_alert_dialog',
223             controller: ['$scope', '$uibModalInstance',
224                 function($scope, $uibModalInstance) {
225                     $scope.message = $interpolate(message)(msg_scope);
226                     $scope.ok = function() {
227                         if (msg_scope && msg_scope.ok) msg_scope.ok();
228                         $uibModalInstance.close()
229                     }
230                 }
231             ]
232         });
233     }
234
235     return service;
236 }])
237
238 /**
239  * egConfirmDialog.open("some message goes {{here}}", {
240  *  here : 'foo', ok : function() {}, cancel : function() {}},
241  *  'OK', 'Cancel');
242  */
243 .factory('egConfirmDialog', 
244     
245        ['$uibModal','$interpolate',
246 function($uibModal, $interpolate) {
247     var service = {};
248
249     service.open = function(title, message, msg_scope, ok_button_label, cancel_button_label) {
250         return $uibModal.open({
251             templateUrl: './share/t_confirm_dialog',
252             controller: ['$scope', '$uibModalInstance',
253                 function($scope, $uibModalInstance) {
254                     $scope.title = $interpolate(title)(msg_scope);
255                     $scope.message = $interpolate(message)(msg_scope);
256                     $scope.ok_button_label = $interpolate(ok_button_label || '')(msg_scope);
257                     $scope.cancel_button_label = $interpolate(cancel_button_label || '')(msg_scope);
258                     $scope.ok = function() {
259                         if (msg_scope.ok) msg_scope.ok();
260                         $uibModalInstance.close()
261                     }
262                     $scope.cancel = function() {
263                         if (msg_scope.cancel) msg_scope.cancel();
264                         $uibModalInstance.dismiss();
265                     }
266                 }
267             ]
268         })
269     }
270
271     return service;
272 }])
273
274 /**
275  * egPromptDialog.open(
276  *    "prompt message goes {{here}}", 
277  *    promptValue,  // optional
278  *    {
279  *      here : 'foo',  
280  *      ok : function(value) {console.log(value)}, 
281  *      cancel : function() {console.log('prompt denied')}
282  *    }
283  *  );
284  */
285 .factory('egPromptDialog', 
286     
287        ['$uibModal','$interpolate',
288 function($uibModal, $interpolate) {
289     var service = {};
290
291     service.open = function(message, promptValue, msg_scope) {
292         return $uibModal.open({
293             templateUrl: './share/t_prompt_dialog',
294             controller: ['$scope', '$uibModalInstance',
295                 function($scope, $uibModalInstance) {
296                     $scope.message = $interpolate(message)(msg_scope);
297                     $scope.args = {value : promptValue || ''};
298                     $scope.focus = true;
299                     $scope.ok = function() {
300                         if (msg_scope.ok) msg_scope.ok($scope.args.value);
301                         $uibModalInstance.close()
302                     }
303                     $scope.cancel = function() {
304                         if (msg_scope.cancel) msg_scope.cancel();
305                         $uibModalInstance.dismiss();
306                     }
307                 }
308             ]
309         })
310     }
311
312     return service;
313 }])
314
315 /**
316  * egSelectDialog.open(
317  *    "message goes {{here}}", 
318  *    list,           // ['values','for','dropdown'],
319  *    selectedValue,  // optional
320  *    {
321  *      here : 'foo',
322  *      ok : function(value) {console.log(value)}, 
323  *      cancel : function() {console.log('prompt denied')}
324  *    }
325  *  );
326  */
327 .factory('egSelectDialog', 
328     
329        ['$uibModal','$interpolate',
330 function($uibModal, $interpolate) {
331     var service = {};
332
333     service.open = function(message, inputList, selectedValue, msg_scope) {
334         return $uibModal.open({
335             templateUrl: './share/t_select_dialog',
336             controller: ['$scope', '$uibModalInstance',
337                 function($scope, $uibModalInstance) {
338                     $scope.message = $interpolate(message)(msg_scope);
339                     $scope.args = {
340                         list  : inputList,
341                         value : selectedValue
342                     };
343                     $scope.focus = true;
344                     $scope.ok = function() {
345                         if (msg_scope.ok) msg_scope.ok($scope.args.value);
346                         $uibModalInstance.close()
347                     }
348                     $scope.cancel = function() {
349                         if (msg_scope.cancel) msg_scope.cancel();
350                         $uibModalInstance.dismiss();
351                     }
352                 }
353             ]
354         })
355     }
356
357     return service;
358 }])
359
360 /**
361  * Warn on page unload and give the user a chance to avoid navigating
362  * away from the current page.  
363  * Only one handler is supported per page.
364  * NOTE: we can't use an egUnloadDialog as the dialog builder, because
365  * it renders asynchronously, which allows the page to redirect before
366  * the dialog appears.
367  */
368 .factory('egUnloadPrompt', [
369         '$window','egStrings', 
370 function($window , egStrings) {
371     var service = {attached : false};
372
373     // attach a page/scope unload prompt
374     service.attach = function($scope, msg) {
375         if (service.attached) return;
376         service.attached = true;
377
378         // handle page change
379         $($window).on('beforeunload', function() { 
380             service.clear();
381             return msg || egStrings.EG_UNLOAD_PAGE_PROMPT_MSG;
382         });
383
384         if (!$scope) return;
385
386         // If a scope was provided, attach a scope-change handler,
387         // similar to the page-page prompt.
388         service.locChangeCancel = 
389             $scope.$on('$locationChangeStart', function(evt, next, current) {
390             if (confirm(msg || egStrings.EG_UNLOAD_CTRL_PROMPT_MSG)) {
391                 // user allowed the page to change.  
392                 // Clear the unload handler.
393                 service.clear();
394             } else {
395                 evt.preventDefault();
396             }
397         });
398     };
399
400     // remove the page unload prompt
401     service.clear = function() {
402         $($window).off('beforeunload');
403         if (service.locChangeCancel)
404             service.locChangeCancel();
405         service.attached = false;
406     }
407
408     return service;
409 }])
410
411 .directive('aDisabled', function() {
412     return {
413         restrict : 'A',
414         compile: function(tElement, tAttrs, transclude) {
415             //Disable ngClick
416             tAttrs["ngClick"] = ("ng-click", "!("+tAttrs["aDisabled"]+") && ("+tAttrs["ngClick"]+")");
417
418             //Toggle "disabled" to class when aDisabled becomes true
419             return function (scope, iElement, iAttrs) {
420                 scope.$watch(iAttrs["aDisabled"], function(newValue) {
421                     if (newValue !== undefined) {
422                         iElement.toggleClass("disabled", newValue);
423                     }
424                 });
425
426                 //Disable href on click
427                 iElement.on("click", function(e) {
428                     if (scope.$eval(iAttrs["aDisabled"])) {
429                         e.preventDefault();
430                     }
431                 });
432             };
433         }
434     };
435 })
436
437 .directive('egBasicComboBox', function() {
438     return {
439         restrict: 'E',
440         replace: true,
441         scope: {
442             list: "=", // list of strings
443             selected: "=",
444             egDisabled: "=",
445             allowAll: "@",
446         },
447         template:
448             '<div class="input-group">'+
449                 '<input type="text" ng-disabled="egDisabled" class="form-control" ng-model="selected" ng-change="makeOpen()">'+
450                 '<div class="input-group-btn" dropdown ng-class="{open:isopen}">'+
451                     '<button type="button" ng-click="showAll()" class="btn btn-default dropdown-toggle"><span class="caret"></span></button>'+
452                     '<ul class="dropdown-menu dropdown-menu-right">'+
453                         '<li ng-repeat="item in list|filter:selected"><a href ng-click="changeValue(item)">{{item}}</a></li>'+
454                         '<li ng-if="complete_list" class="divider"><span></span></li>'+
455                         '<li ng-if="complete_list" ng-repeat="item in list"><a href ng-click="changeValue(item)">{{item}}</a></li>'+
456                     '</ul>'+
457                 '</div>'+
458             '</div>',
459         controller: ['$scope','$filter',
460             function( $scope , $filter) {
461
462                 $scope.complete_list = false;
463                 $scope.isopen = false;
464                 $scope.clickedopen = false;
465                 $scope.clickedclosed = null;
466
467                 $scope.showAll = function () {
468
469                     $scope.clickedopen = !$scope.clickedopen;
470
471                     if ($scope.clickedclosed === null) {
472                         if (!$scope.clickedopen) {
473                             $scope.clickedclosed = true;
474                         }
475                     } else {
476                         $scope.clickedclosed = !$scope.clickedopen;
477                     }
478
479                     if ($scope.selected.length > 0) $scope.complete_list = true;
480                     if ($scope.selected.length == 0) $scope.complete_list = false;
481                     $scope.makeOpen();
482                 }
483
484                 $scope.makeOpen = function () {
485                     $scope.isopen = $scope.clickedopen || ($filter('filter')(
486                         $scope.list,
487                         $scope.selected
488                     ).length > 0 && $scope.selected.length > 0);
489                     if ($scope.clickedclosed) $scope.isopen = false;
490                 }
491
492                 $scope.changeValue = function (newVal) {
493                     $scope.selected = newVal;
494                     $scope.isopen = false;
495                     $scope.clickedclosed = null;
496                     $scope.clickedopen = false;
497                     if ($scope.selected.length == 0) $scope.complete_list = false;
498                 }
499
500             }
501         ]
502     };
503 })
504
505 /**
506  * Nested org unit selector modeled as a Bootstrap dropdown button.
507  */
508 .directive('egOrgSelector', function() {
509     return {
510         restrict : 'AE',
511         transclude : true,
512         replace : true, // makes styling easier
513         scope : {
514             selected : '=', // defaults to workstation or root org,
515                             // unless the nodefault attibute exists
516
517             // Each org unit is passed into this function and, for
518             // any org units where the response value is true, the
519             // org unit will not be added to the selector.
520             hiddenTest : '=',
521
522             // Each org unit is passed into this function and, for
523             // any org units where the response value is true, the
524             // org unit will not be available for selection.
525             disableTest : '=',
526
527             // if set to true, disable the UI element altogether
528             alldisabled : '@',
529
530             // Caller can either $watch(selected, ..) or register an
531             // onchange handler.
532             onchange : '=',
533
534             // optional primary drop-down button label
535             label : '@',
536
537             // optional name of settings key for persisting
538             // the last selected org unit
539             stickySetting : '@'
540         },
541
542         // any reason to move this into a TT2 template?
543         template : 
544             '<div class="btn-group eg-org-selector" uib-dropdown>'
545             + '<button type="button" class="btn btn-default" uib-dropdown-toggle ng-disabled="disable_button">'
546              + '<span style="padding-right: 5px;">{{getSelectedName()}}</span>'
547              + '<span class="caret"></span>'
548            + '</button>'
549            + '<ul uib-dropdown-menu class="scrollable-menu">'
550              + '<li ng-repeat="org in orgList" ng-hide="hiddenTest(org.id)">'
551                + '<a href ng-click="orgChanged(org)" a-disabled="disableTest(org.id)" '
552                  + 'style="padding-left: {{org.depth * 10 + 5}}px">'
553                  + '{{org.shortname}}'
554                + '</a>'
555              + '</li>'
556            + '</ul>'
557           + '</div>',
558
559         controller : ['$scope','$timeout','egCore','egStartup',
560               function($scope , $timeout , egCore , egStartup) {
561
562             if ($scope.alldisabled) {
563                 $scope.disable_button = $scope.alldisabled == 'true' ? true : false;
564             } else {
565                 $scope.disable_button = false;
566             }
567
568             // avoid linking the full fleshed tree to the scope by 
569             // tossing in a flattened list.
570             // --
571             // Run-time code referencing post-start data should be run
572             // from within a startup block, otherwise accessing this
573             // module before startup completes will lead to failure.
574             //
575             // controller() runs before link().
576             // This post-startup code runs after link().
577             egStartup.go().then(function() {
578
579                 $scope.orgList = egCore.org.list().map(function(org) {
580                     return {
581                         id : org.id(),
582                         shortname : org.shortname(), 
583                         depth : org.ou_type().depth()
584                     }
585                 });
586
587                 // Apply default values
588
589                 if ($scope.stickySetting) {
590                     var orgId = egCore.hatch.getLocalItem($scope.stickySetting);
591                     if (orgId) {
592                         $scope.selected = egCore.org.get(orgId);
593                     }
594                 }
595
596                 if (!$scope.selected && !$scope.nodefault) {
597                     $scope.selected = 
598                         egCore.org.get(egCore.auth.user().ws_ou());
599                 }
600
601                 fire_orgsel_onchange(); // no-op if nothing is selected
602             });
603
604             /**
605              * Fire onchange handler after a timeout, so the
606              * $scope.selected value has a chance to propagate to
607              * the page controllers before the onchange fires.  This
608              * way, the caller does not have to manually capture the
609              * $scope.selected value during onchange.
610              */
611             function fire_orgsel_onchange() {
612                 if (!$scope.selected || !$scope.onchange) return;
613                 $timeout(function() {
614                     console.debug(
615                         'egOrgSelector onchange('+$scope.selected.id()+')');
616                     $scope.onchange($scope.selected)
617                 });
618             }
619
620             $scope.getSelectedName = function() {
621                 if ($scope.selected && $scope.selected.shortname)
622                     return $scope.selected.shortname();
623                 return $scope.label;
624             }
625
626             $scope.orgChanged = function(org) {
627                 $scope.selected = egCore.org.get(org.id);
628                 if ($scope.stickySetting) {
629                     egCore.hatch.setLocalItem($scope.stickySetting, org.id);
630                 }
631                 fire_orgsel_onchange();
632             }
633
634         }],
635         link : function(scope, element, attrs, egGridCtrl) {
636
637             // boolean fields are presented as value-less attributes
638             angular.forEach(
639                 ['nodefault'],
640                 function(field) {
641                     if (angular.isDefined(attrs[field]))
642                         scope[field] = true;
643                     else
644                         scope[field] = false;
645                 }
646             );
647         }
648     }
649 })
650
651 /* http://eric.sau.pe/angularjs-detect-enter-key-ngenter/ */
652 .directive('egEnter', function () {
653     return function (scope, element, attrs) {
654         element.bind("keydown keypress", function (event) {
655             if(event.which === 13) {
656                 scope.$apply(function (){
657                     scope.$eval(attrs.egEnter);
658                 });
659  
660                 event.preventDefault();
661             }
662         });
663     };
664 })
665
666 /*
667 * Handy wrapper directive for uib-datapicker-popup
668 */
669 .directive(
670     'egDateInput', ['egStrings', 'egCore',
671     function(egStrings, egCore) {
672         return {
673             scope : {
674                 closeText : '@',
675                 ngModel : '=',
676                 ngChange : '=',
677                 ngBlur : '=',
678                 ngDisabled : '=',
679                 ngRequired : '=',
680                 hideDatePicker : '=',
681                 dateFormat : '=?'
682             },
683             require: 'ngModel',
684             templateUrl: './share/t_datetime',
685             replace: true,
686             link : function(scope, elm, attrs) {
687                 if (!scope.closeText)
688                     scope.closeText = egStrings.EG_DATE_INPUT_CLOSE_TEXT;
689
690                 if ('showTimePicker' in attrs)
691                     scope.showTimePicker = true;
692
693                 var default_format = 'mediumDate';
694                 egCore.org.settings(['format.date']).then(function(set) {
695                     default_format = set['format.date'];
696                     scope.date_format = (scope.dateFormat) ?
697                         scope.dateFormat :
698                         default_format;
699                 });
700             }
701         };
702     }
703 ])
704
705 /*
706  *  egFmValueSelector - widget for selecting a value from list specified
707  *                      by IDL class
708  */
709 .directive('egFmValueSelector', function() {
710     return {
711         restrict : 'E',
712         transclude : true,
713         scope : {
714             idlClass : '@',
715             ngModel : '=',
716
717             // optional filter for refining the set of rows that
718             // get returned. Example:
719             //
720             // filter="{'column':{'=':null}}"
721             filter : '=',
722
723             // optional name of settings key for persisting
724             // the last selected value
725             stickySetting : '@',
726
727             // optional OU setting for fetching default value;
728             // used only if sticky setting not set
729             ouSetting : '@'
730         },
731         require: 'ngModel',
732         templateUrl : './share/t_fm_value_selector',
733         controller : ['$scope','egCore', function($scope , egCore) {
734
735             $scope.org = egCore.org; // for use in the link function
736             $scope.auth = egCore.auth; // for use in the link function
737             $scope.hatch = egCore.hatch // for use in the link function
738
739             function flatten_linked_values(cls, list) {
740                 var results = [];
741                 var fields = egCore.idl.classes[cls].fields;
742                 var id_field;
743                 var selector;
744                 angular.forEach(fields, function(fld) {
745                     if (fld.datatype == 'id') {
746                         id_field = fld.name;
747                         selector = fld.selector ? fld.selector : id_field;
748                         return;
749                     }
750                 });
751                 angular.forEach(list, function(item) {
752                     var rec = egCore.idl.toHash(item);
753                     results.push({
754                         id : rec[id_field],
755                         name : rec[selector]
756                     });
757                 });
758                 return results;
759             }
760
761             var search = {};
762             search[egCore.idl.classes[$scope.idlClass].pkey] = {'!=' : null};
763             if ($scope.filter) {
764                 angular.extend(search, $scope.filter);
765             }
766             egCore.pcrud.search(
767                 $scope.idlClass, search, {}, {atomic : true}
768             ).then(function(list) {
769                 $scope.linked_values = flatten_linked_values($scope.idlClass, list);
770             });
771
772             $scope.handleChange = function(value) {
773                 if ($scope.stickySetting) {
774                     egCore.hatch.setLocalItem($scope.stickySetting, value);
775                 }
776             }
777
778         }],
779         link : function(scope, element, attrs) {
780             if (scope.stickySetting && (angular.isUndefined(scope.ngModel) || (scope.ngModel === null))) {
781                 var value = scope.hatch.getLocalItem(scope.stickySetting);
782                 scope.ngModel = value;
783             }
784             if (scope.ouSetting && (angular.isUndefined(scope.ngModel) || (scope.ngModel === null))) {
785                 scope.org.settings([scope.ouSetting], scope.auth.user().ws_ou())
786                 .then(function(set) {
787                     var value = parseInt(set[scope.ouSetting]);
788                     if (!isNaN(value))
789                         scope.ngModel = value;
790                 });
791             }
792         }
793     }
794 })
795
796 .factory('egWorkLog', ['egCore', function(egCore) {
797     var service = {};
798
799     service.retrieve_all = function() {
800         var workLog = egCore.hatch.getLocalItem('eg.work_log') || [];
801         var patronLog = egCore.hatch.getLocalItem('eg.patron_log') || [];
802
803         return { 'work_log' : workLog, 'patron_log' : patronLog };
804     }
805
806     service.record = function(message,data) {
807         var max_entries;
808         var max_patrons;
809         if (typeof egCore != 'undefined') {
810             if (typeof egCore.env != 'undefined') {
811                 if (typeof egCore.env.aous != 'undefined') {
812                     max_entries = egCore.env.aous['ui.admin.work_log.max_entries'];
813                     max_patrons = egCore.env.aous['ui.admin.patron_log.max_entries'];
814                 } else {
815                     console.log('worklog: missing egCore.env.aous');
816                 }
817             } else {
818                 console.log('worklog: missing egCore.env');
819             }
820         } else {
821             console.log('worklog: missing egCore');
822         }
823         if (!max_entries) {
824             if (typeof egCore.org != 'undefined') {
825                 if (typeof egCore.org.cachedSettings != 'undefined') {
826                     max_entries = egCore.org.cachedSettings['ui.admin.work_log.max_entries'];
827                 } else {
828                     console.log('worklog: missing egCore.org.cachedSettings');
829                 }
830             } else {
831                 console.log('worklog: missing egCore.org');
832             }
833         }
834         if (!max_patrons) {
835             if (typeof egCore.org != 'undefined') {
836                 if (typeof egCore.org.cachedSettings != 'undefined') {
837                     max_patrons = egCore.org.cachedSettings['ui.admin.patron_log.max_entries'];
838                 } else {
839                     console.log('worklog: missing egCore.org.cachedSettings');
840                 }
841             } else {
842                 console.log('worklog: missing egCore.org');
843             }
844         }
845         if (!max_entries) {
846             max_entries = 20;
847             console.log('worklog: defaulting to max_entries = ' + max_entries);
848         }
849         if (!max_patrons) {
850             max_patrons = 10;
851             console.log('worklog: defaulting to max_patrons = ' + max_patrons);
852         }
853
854         var workLog = egCore.hatch.getLocalItem('eg.work_log') || [];
855         var patronLog = egCore.hatch.getLocalItem('eg.patron_log') || [];
856         var entry = {
857             'when' : new Date(),
858             'msg' : message,
859             'action' : data.action,
860             'actor' : egCore.auth.user().usrname()
861         };
862         if (data.action == 'checkin') {
863             entry['item'] = data.response.params.copy_barcode;
864             entry['item_id'] = data.response.data.acp.id();
865             if (data.response.data.au) {
866                 entry['user'] = data.response.data.au.family_name();
867                 entry['patron_id'] = data.response.data.au.id();
868             }
869         }
870         if (data.action == 'checkout') {
871             entry['item'] = data.response.params.copy_barcode;
872             entry['user'] = data.response.data.au.family_name();
873             entry['item_id'] = data.response.data.acp.id();
874             entry['patron_id'] = data.response.data.au.id();
875         }
876         if (data.action == 'renew') {
877             entry['item'] = data.response.params.copy_barcode;
878             entry['user'] = data.response.data.au.family_name();
879             entry['item_id'] = data.response.data.acp.id();
880             entry['patron_id'] = data.response.data.au.id();
881         }
882         if (data.action == 'requested_hold'
883             || data.action == 'edited_patron'
884             || data.action == 'registered_patron'
885             || data.action == 'paid_bill') {
886             entry['patron_id'] = data.patron_id;
887         }
888         if (data.action == 'requested_hold') {
889             entry['hold_id'] = data.hold_id;
890         }
891         if (data.action == 'paid_bill') {
892             entry['amount'] = data.total_amount;
893         }
894
895         workLog.push( entry );
896         if (workLog.length > max_entries) workLog.shift();
897         egCore.hatch.setLocalItem('eg.work_log',workLog); // hatch JSONifies the data, so should be okay re: memory leaks?
898
899         if (entry['patron_id']) {
900             var temp = [];
901             for (var i = 0; i < patronLog.length; i++) { // filter out any matching patron
902                 if (patronLog[i]['patron_id'] != entry['patron_id']) temp.push(patronLog[i]);
903             }
904             temp.push( entry );
905             if (temp.length > max_patrons) temp.shift();
906             patronLog = temp;
907             egCore.hatch.setLocalItem('eg.patron_log',patronLog);
908         }
909
910         console.log('worklog',entry);
911     }
912
913     return service;
914 }]);