2 * UI tools and directives.
4 angular.module('egUiMod', ['egCoreMod', 'ui.bootstrap'])
8 * <input focus-me="iAmOpen"/>
9 * $scope.iAmOpen = true;
13 function($timeout , $parse) {
15 link: function(scope, element, attrs) {
16 var model = $parse(attrs.focusMe);
17 scope.$watch(model, function(value) {
19 $timeout(function() {element[0].focus()});
21 element.bind('blur', function() {
23 scope.$apply(model.assign(scope, false));
31 * <input blur-me="pleaseBlurMe"/>
32 * $scope.pleaseBlurMe = true
33 * Useful for de-focusing when no other obvious focus target exists
37 function($timeout , $parse) {
39 link: function(scope, element, attrs) {
40 var model = $parse(attrs.blurMe);
41 scope.$watch(model, function(value) {
43 $timeout(function() {element[0].blur()});
45 element.bind('focus', function() {
47 scope.$apply(model.assign(scope, false));
55 // <input select-me="iWantToBeSelected"/>
56 // $scope.iWantToBeSelected = true;
57 .directive('selectMe',
59 function($timeout , $parse) {
61 link: function(scope, element, attrs) {
62 var model = $parse(attrs.selectMe);
63 scope.$watch(model, function(value) {
65 $timeout(function() {element[0].select()});
67 element.bind('blur', function() {
69 scope.$apply(model.assign(scope, false));
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();
88 // Overriding the core angular date filter with a moment-js based one for
89 // better timezone and formatting support.
90 .filter('date',function() {
107 var formatReplace = [
121 return function (date, format, tz) {
122 if (!date) return '';
125 date = new Date().toISOString();
128 var fmt = formatMap[format] || format;
129 angular.forEach(formatReplace, function (r) {
130 fmt = fmt.replace(r[0],r[1]);
134 var d = moment(date);
135 if (tz && tz !== '-') d.tz(tz);
137 return d.isValid() ? d.format(fmt) : '';
142 // 'egOrgDate' filter
143 // Uses moment.js and moment-timezone.js to put dates into the most appropriate
144 // timezone for a given (optional) org unit based on its lib.timezone setting
145 .filter('egOrgDate',['$filter','egCore',
146 function($filter , egCore) {
150 function eg_date_filter (date, fmt, ouID) {
152 if (angular.isObject(ouID)) {
153 if (angular.isFunction(ouID.id)) {
160 if (!tzcache[ouID]) {
162 egCore.org.settings('lib.timezone', ouID)
164 tzcache[ouID] = s['lib.timezone'] || OpenSRF.tz;
169 return $filter('date')(date, fmt, tzcache[ouID]);
172 eg_date_filter.$stateful = true;
174 return eg_date_filter;
177 // 'egOrgDateInContext' filter
178 // Uses the egOrgDate filter to make time and date location aware, and further
179 // modifies the format if one of [short, medium, long, full] to show only the
180 // date if the optional interval parameter is day-granular. This is
181 // particularly useful for due dates on circulations.
182 .filter('egOrgDateInContext',['$filter','egCore',
183 function($filter , egCore) {
185 function eg_context_date_filter (date, format, orgID, interval) {
187 if (!fmt) fmt = 'shortDate';
189 // if this is a simple, one-word format, and it doesn't say "Date" in it...
190 if (['short','medium','long','full'].filter(function(x){return fmt == x}).length > 0 && interval) {
191 var secs = egCore.date.intervalToSeconds(interval);
192 if (secs !== null && secs % 86400 == 0) fmt += 'Date';
195 return $filter('egOrgDate')(date, fmt, orgID);
198 eg_context_date_filter.$stateful = true;
200 return eg_context_date_filter;
203 // 'egDueDate' filter
204 // Uses the egOrgDateInContext filter to make time and date location aware, but
205 // only if the supplied interval is day-granular. This is as wrapper for
206 // egOrgDateInContext to be used for circulation due date /only/.
207 .filter('egDueDate',['$filter','egCore',
208 function($filter , egCore) {
210 function eg_context_due_date_filter (date, format, orgID, interval) {
212 var secs = egCore.date.intervalToSeconds(interval);
213 if (secs === null || secs % 86400 != 0) {
218 return $filter('egOrgDateInContext')(date, format, orgID, interval);
221 eg_context_due_date_filter.$stateful = true;
223 return eg_context_due_date_filter;
227 // TODO: perhaps this should live elsewhere
228 .filter('join', function() {
229 return function(arr,sep) {
230 if (typeof arr == 'object' && arr.constructor == Array) {
231 return arr.join(sep || ',');
241 * egProgressDialog.open();
242 * egProgressDialog.open({value : 0});
243 * egProgressDialog.open({value : 0, max : 123});
244 * egProgressDialog.increment();
245 * egProgressDialog.increment();
246 * egProgressDialog.close();
248 * Each dialog has 2 numbers, 'max' and 'value'.
249 * The content of these values determines how the dialog displays.
251 * There are 3 flavors:
253 * -- value is set, max is set
254 * determinate: shows a progression with a percent complete.
256 * -- value is set, max is unset
257 * semi-determinate, with a value report. Shows a value-less
258 * <progress/>, but shows the value as a number in the dialog.
260 * This is useful in cases where the total number of items to retrieve
261 * from the server is unknown, but we know how many items we've
262 * retrieved thus far. It helps to reinforce that something specific
263 * is happening, but we don't know when it will end.
266 * indeterminate: shows a generic value-less <progress/> with no
267 * clear indication of progress.
269 * Only 1 egProgressDialog instance will be activate at a time.
270 * Each invocation of .open() destroys any existing instance.
273 /* Simple storage class for egProgressDialog data maintenance.
274 * This data lives outside of egProgressDialog so it can be
275 * directly imported into egProgressDialog's $uibModalInstance.
277 .factory('egProgressData', [
279 var service = {}; // max/value initially unset
281 service.reset = function() {
283 delete service.value;
286 service.hasvalue = function() {
287 return Number.isInteger(service.value);
290 service.hasmax = function() {
291 return Number.isInteger(service.max);
294 service.percent = function() {
295 if (service.hasvalue() &&
298 service.value <= service.max)
299 return Math.floor((service.value / service.max) * 100);
307 .factory('egProgressDialog', [
308 'egProgressData','$uibModal',
309 function(egProgressData , $uibModal) {
312 service.open = function(args) {
313 return $uibModal.open({
314 templateUrl: './share/t_progress_dialog',
315 /* backdrop: 'static', */ /* allow 'cancelling' of progress dialog */
316 controller: ['$scope','$uibModalInstance','egProgressData',
317 function( $scope , $uibModalInstance , egProgressData) {
318 // Once the new modal instance is available, force-
319 // kill any other instances
322 // Reset to an indeterminate progress bar,
323 // overlay with caller values.
324 egProgressData.reset();
325 service.update(angular.extend({}, args));
327 service.currentInstance = $uibModalInstance;
328 $scope.data = egProgressData; // tiny service
334 service.close = function(warn) {
335 if (service.currentInstance) {
337 console.warn("egProgressDialog replacing existing instance. "
338 + "Only one may be open at a time.");
340 service.currentInstance.close();
341 delete service.currentInstance;
345 // Set the current state of the progress bar.
346 service.update = function(args) {
347 if (args.max != undefined)
348 egProgressData.max = args.max;
349 if (args.value != undefined)
350 egProgressData.value = args.value;
353 // Increment the current value. If no amount is specified,
354 // it increments by 1. Calling increment() on an indetermite
355 // progress bar will force it to be a (semi-)determinate bar.
356 service.increment = function(amt) {
357 if (!Number.isInteger(amt)) amt = 1;
359 if (!egProgressData.hasvalue())
360 egProgressData.value = 0;
362 egProgressData.value += amt;
369 * egAlertDialog.open({message : 'hello {{name}}'}).result.then(
370 * function() { console.log('alert closed') });
372 .factory('egAlertDialog',
374 ['$uibModal','$interpolate',
375 function($uibModal , $interpolate) {
378 service.open = function(message, msg_scope) {
379 return $uibModal.open({
380 templateUrl: './share/t_alert_dialog',
382 controller: ['$scope', '$uibModalInstance',
383 function($scope, $uibModalInstance) {
384 $scope.message = $interpolate(message)(msg_scope);
385 $scope.ok = function() {
386 if (msg_scope && msg_scope.ok) msg_scope.ok();
387 $uibModalInstance.close()
398 * egConfirmDialog.open("some message goes {{here}}", {
399 * here : 'foo', ok : function() {}, cancel : function() {}},
402 .factory('egConfirmDialog',
404 ['$uibModal','$interpolate',
405 function($uibModal, $interpolate) {
408 service.open = function(title, message, msg_scope, ok_button_label, cancel_button_label) {
409 msg_scope = msg_scope || {};
410 return $uibModal.open({
411 templateUrl: './share/t_confirm_dialog',
413 controller: ['$scope', '$uibModalInstance',
414 function($scope, $uibModalInstance) {
415 $scope.title = $interpolate(title)(msg_scope);
416 $scope.message = $interpolate(message)(msg_scope);
417 $scope.ok_button_label = $interpolate(ok_button_label || '')(msg_scope);
418 $scope.cancel_button_label = $interpolate(cancel_button_label || '')(msg_scope);
419 $scope.ok = function() {
420 if (msg_scope.ok) msg_scope.ok();
421 $uibModalInstance.close()
423 $scope.cancel = function() {
424 if (msg_scope.cancel) msg_scope.cancel();
425 $uibModalInstance.dismiss();
436 * egPromptDialog.open(
437 * "prompt message goes {{here}}",
438 * promptValue, // optional
441 * ok : function(value) {console.log(value)},
442 * cancel : function() {console.log('prompt denied')}
446 .factory('egPromptDialog',
448 ['$uibModal','$interpolate',
449 function($uibModal, $interpolate) {
452 service.open = function(message, promptValue, msg_scope) {
453 return $uibModal.open({
454 templateUrl: './share/t_prompt_dialog',
456 controller: ['$scope', '$uibModalInstance',
457 function($scope, $uibModalInstance) {
458 $scope.message = $interpolate(message)(msg_scope);
459 $scope.args = {value : promptValue || ''};
461 $scope.ok = function() {
462 if (msg_scope && msg_scope.ok) msg_scope.ok($scope.args.value);
463 $uibModalInstance.close($scope.args);
465 $scope.cancel = function() {
466 if (msg_scope && msg_scope.cancel) msg_scope.cancel();
467 $uibModalInstance.dismiss();
478 * egSelectDialog.open(
479 * "message goes {{here}}",
480 * list, // ['values','for','dropdown'],
481 * selectedValue, // optional
484 * ok : function(value) {console.log(value)},
485 * cancel : function() {console.log('prompt denied')}
489 .factory('egSelectDialog',
491 ['$uibModal','$interpolate',
492 function($uibModal, $interpolate) {
495 service.open = function(message, inputList, selectedValue, msg_scope) {
496 return $uibModal.open({
497 templateUrl: './share/t_select_dialog',
499 controller: ['$scope', '$uibModalInstance',
500 function($scope, $uibModalInstance) {
501 $scope.message = $interpolate(message)(msg_scope);
504 value : selectedValue
507 $scope.ok = function() {
508 if (msg_scope.ok) msg_scope.ok($scope.args.value);
509 $uibModalInstance.close()
511 $scope.cancel = function() {
512 if (msg_scope.cancel) msg_scope.cancel();
513 $uibModalInstance.dismiss();
524 * Warn on page unload and give the user a chance to avoid navigating
525 * away from the current page.
526 * Only one handler is supported per page.
527 * NOTE: we can't use an egUnloadDialog as the dialog builder, because
528 * it renders asynchronously, which allows the page to redirect before
529 * the dialog appears.
531 .factory('egUnloadPrompt', [
532 '$window','egStrings',
533 function($window , egStrings) {
534 var service = {attached : false};
536 // attach a page/scope unload prompt
537 service.attach = function($scope, msg) {
538 if (service.attached) return;
539 service.attached = true;
541 // handle page change
542 $($window).on('beforeunload', function() {
544 return msg || egStrings.EG_UNLOAD_PAGE_PROMPT_MSG;
549 // If a scope was provided, attach a scope-change handler,
550 // similar to the page-page prompt.
551 service.locChangeCancel =
552 $scope.$on('$locationChangeStart', function(evt, next, current) {
553 if (confirm(msg || egStrings.EG_UNLOAD_CTRL_PROMPT_MSG)) {
554 // user allowed the page to change.
555 // Clear the unload handler.
558 evt.preventDefault();
563 // remove the page unload prompt
564 service.clear = function() {
565 $($window).off('beforeunload');
566 if (service.locChangeCancel)
567 service.locChangeCancel();
568 service.attached = false;
574 .directive('aDisabled', function() {
577 compile: function(tElement, tAttrs, transclude) {
579 tAttrs["ngClick"] = ("ng-click", "!("+tAttrs["aDisabled"]+") && ("+tAttrs["ngClick"]+")");
581 //Toggle "disabled" to class when aDisabled becomes true
582 return function (scope, iElement, iAttrs) {
583 scope.$watch(iAttrs["aDisabled"], function(newValue) {
584 if (newValue !== undefined) {
585 iElement.toggleClass("disabled", newValue);
589 //Disable href on click
590 iElement.on("click", function(e) {
591 if (scope.$eval(iAttrs["aDisabled"])) {
600 .directive('egBasicComboBox', function() {
605 list: "=", // list of strings
614 '<div class="input-group">'+
615 '<input placeholder="{{placeholder}}" type="text" ng-disabled="egDisabled" class="form-control" ng-model="selected" ng-change="makeOpen()" focus-me="focusMe">'+
616 '<div class="input-group-btn" dropdown ng-class="{open:isopen}">'+
617 '<button type="button" ng-click="showAll()" ng-disabled="egDisabled" class="btn btn-default dropdown-toggle"><span class="caret"></span></button>'+
618 '<ul class="dropdown-menu dropdown-menu-right">'+
619 '<li ng-repeat="item in list|filter:selected:compare"><a href ng-click="changeValue(item)">{{item}}</a></li>'+
620 '<li ng-if="complete_list" class="divider"><span></span></li>'+
621 '<li ng-if="complete_list" ng-repeat="item in list"><a href ng-click="changeValue(item)">{{item}}</a></li>'+
625 controller: ['$scope','$filter',
626 function( $scope , $filter) {
628 $scope.complete_list = false;
629 $scope.isopen = false;
630 $scope.clickedopen = false;
631 $scope.clickedclosed = null;
633 $scope.compare = function (ex, act) {
634 if (act === null || act === undefined) return true;
635 if (act.toString) act = act.toString();
636 return new RegExp(act.toLowerCase()).test(ex)
639 $scope.showAll = function () {
641 $scope.clickedopen = !$scope.clickedopen;
643 if ($scope.clickedclosed === null) {
644 if (!$scope.clickedopen) {
645 $scope.clickedclosed = true;
648 $scope.clickedclosed = !$scope.clickedopen;
651 if ($scope.selected && $scope.selected.length > 0) $scope.complete_list = true;
652 if (!$scope.selected || $scope.selected.length == 0) $scope.complete_list = false;
656 $scope.makeOpen = function () {
657 $scope.isopen = $scope.clickedopen || ($filter('filter')(
660 ).length > 0 && $scope.selected.length > 0);
661 if ($scope.clickedclosed) {
662 $scope.isopen = false;
663 $scope.clickedclosed = null;
667 $scope.changeValue = function (newVal) {
668 $scope.selected = newVal;
669 $scope.isopen = false;
670 $scope.clickedclosed = null;
671 $scope.clickedopen = false;
672 if ($scope.selected.length == 0) $scope.complete_list = false;
673 if ($scope.onSelect) $scope.onSelect();
682 * Nested org unit selector modeled as a Bootstrap dropdown button.
684 .directive('egOrgSelector', function() {
688 replace : true, // makes styling easier
690 selected : '=', // defaults to workstation or root org,
691 // unless the nodefault attibute exists
693 // Each org unit is passed into this function and, for
694 // any org units where the response value is true, the
695 // org unit will not be added to the selector.
698 // Each org unit is passed into this function and, for
699 // any org units where the response value is true, the
700 // org unit will not be available for selection.
703 // if set to true, disable the UI element altogether
706 // Caller can either $watch(selected, ..) or register an
710 // optional primary drop-down button label
713 // optional name of settings key for persisting
714 // the last selected org unit
718 // any reason to move this into a TT2 template?
720 '<div class="btn-group eg-org-selector" uib-dropdown>'
721 + '<button type="button" class="btn btn-default" uib-dropdown-toggle ng-disabled="disable_button">'
722 + '<span style="padding-right: 5px;">{{getSelectedName()}}</span>'
723 + '<span class="caret"></span>'
725 + '<ul uib-dropdown-menu class="scrollable-menu">'
726 + '<li ng-repeat="org in orgList" ng-hide="hiddenTest(org.id)">'
727 + '<a href ng-click="orgChanged(org)" a-disabled="disableTest(org.id)" '
728 + 'style="padding-left: {{org.depth * 10 + 5}}px">'
729 + '{{org.shortname}}'
735 controller : ['$scope','$timeout','egCore','egStartup','egLovefield','$q',
736 function($scope , $timeout , egCore , egStartup , egLovefield , $q) {
738 if ($scope.alldisabled) {
739 $scope.disable_button = $scope.alldisabled == 'true' ? true : false;
741 $scope.disable_button = false;
744 // avoid linking the full fleshed tree to the scope by
745 // tossing in a flattened list.
747 // Run-time code referencing post-start data should be run
748 // from within a startup block, otherwise accessing this
749 // module before startup completes will lead to failure.
751 // controller() runs before link().
752 // This post-startup code runs after link().
756 return egCore.env.classLoaders.aou();
761 $scope.orgList = egCore.org.list().map(function(org) {
764 shortname : org.shortname(),
765 depth : org.ou_type().depth()
770 // Apply default values
772 if ($scope.stickySetting) {
773 var orgId = egCore.hatch.getLocalItem($scope.stickySetting);
775 $scope.selected = egCore.org.get(orgId);
779 if (!$scope.selected && !$scope.nodefault && egCore.auth.user()) {
781 egCore.org.get(egCore.auth.user().ws_ou());
784 fire_orgsel_onchange(); // no-op if nothing is selected
789 * Fire onchange handler after a timeout, so the
790 * $scope.selected value has a chance to propagate to
791 * the page controllers before the onchange fires. This
792 * way, the caller does not have to manually capture the
793 * $scope.selected value during onchange.
795 function fire_orgsel_onchange() {
796 if (!$scope.selected || !$scope.onchange) return;
797 $timeout(function() {
799 'egOrgSelector onchange('+$scope.selected.id()+')');
800 $scope.onchange($scope.selected)
804 $scope.getSelectedName = function() {
805 if ($scope.selected && $scope.selected.shortname)
806 return $scope.selected.shortname();
810 $scope.orgChanged = function(org) {
811 $scope.selected = egCore.org.get(org.id);
812 if ($scope.stickySetting) {
813 egCore.hatch.setLocalItem($scope.stickySetting, org.id);
815 fire_orgsel_onchange();
819 link : function(scope, element, attrs, egGridCtrl) {
821 // boolean fields are presented as value-less attributes
825 if (angular.isDefined(attrs[field]))
828 scope[field] = false;
835 .directive('nextOnEnter', function () {
836 return function (scope, element, attrs) {
837 element.bind("keydown keypress", function (event) {
838 if(event.which === 13) {
839 $('#'+attrs.nextOnEnter).focus();
840 event.preventDefault();
846 /* http://eric.sau.pe/angularjs-detect-enter-key-ngenter/ */
847 .directive('egEnter', function () {
848 return function (scope, element, attrs) {
849 element.bind("keydown keypress", function (event) {
850 if(event.which === 13) {
851 scope.$apply(function (){
852 scope.$eval(attrs.egEnter);
855 event.preventDefault();
862 * Handy wrapper directive for uib-datapicker-popup
865 'egDateInput', ['egStrings', 'egCore',
866 function(egStrings, egCore) {
878 hideDatePicker : '=',
884 templateUrl: './share/t_datetime',
886 controller : ['$scope', function($scope) {
888 minDate : $scope.minDate,
889 maxDate : $scope.maxDate
892 var maxDateObj = $scope.maxDate ? new Date($scope.maxDate) : null;
893 var minDateObj = $scope.minDate ? new Date($scope.minDate) : null;
895 if ($scope.outOfRange !== undefined && (maxDateObj || minDateObj)) {
896 $scope.$watch('ngModel', function (n,o) {
899 var newdate = new Date(n);
900 if (maxDateObj && newdate.getTime() > maxDateObj.getTime()) bad = true;
901 if (minDateObj && newdate.getTime() < minDateObj.getTime()) bad = true;
902 $scope.outOfRange = bad;
907 link : function(scope, elm, attrs) {
908 if (!scope.closeText)
909 scope.closeText = egStrings.EG_DATE_INPUT_CLOSE_TEXT;
911 if ('showTimePicker' in attrs)
912 scope.showTimePicker = true;
914 var default_format = 'mediumDate';
915 egCore.org.settings(['format.date']).then(function(set) {
916 default_format = set['format.date'];
917 scope.date_format = (scope.dateFormat) ?
927 * egFmValueSelector - widget for selecting a value from list specified
930 .directive('egFmValueSelector', function() {
938 // optional filter for refining the set of rows that
939 // get returned. Example:
941 // filter="{'column':{'=':null}}"
944 // optional name of settings key for persisting
945 // the last selected value
948 // optional OU setting for fetching default value;
949 // used only if sticky setting not set
953 templateUrl : './share/t_fm_value_selector',
954 controller : ['$scope','egCore', function($scope , egCore) {
956 $scope.org = egCore.org; // for use in the link function
957 $scope.auth = egCore.auth; // for use in the link function
958 $scope.hatch = egCore.hatch // for use in the link function
960 function flatten_linked_values(cls, list) {
962 var fields = egCore.idl.classes[cls].fields;
965 angular.forEach(fields, function(fld) {
966 if (fld.datatype == 'id') {
968 selector = fld.selector ? fld.selector : id_field;
972 angular.forEach(list, function(item) {
973 var rec = egCore.idl.toHash(item);
983 search[egCore.idl.classes[$scope.idlClass].pkey] = {'!=' : null};
985 angular.extend(search, $scope.filter);
988 $scope.idlClass, search, {}, {atomic : true}
989 ).then(function(list) {
990 $scope.linked_values = flatten_linked_values($scope.idlClass, list);
993 $scope.handleChange = function(value) {
994 if ($scope.stickySetting) {
995 egCore.hatch.setLocalItem($scope.stickySetting, value);
1000 link : function(scope, element, attrs) {
1001 if (scope.stickySetting && (angular.isUndefined(scope.ngModel) || (scope.ngModel === null))) {
1002 var value = scope.hatch.getLocalItem(scope.stickySetting);
1003 scope.ngModel = value;
1005 if (scope.ouSetting && (angular.isUndefined(scope.ngModel) || (scope.ngModel === null))) {
1006 scope.org.settings([scope.ouSetting], scope.auth.user().ws_ou())
1007 .then(function(set) {
1008 var value = parseInt(set[scope.ouSetting]);
1010 scope.ngModel = value;
1018 * egShareDepthSelector - widget for selecting a share depth
1020 .directive('egShareDepthSelector', function() {
1028 templateUrl : './share/t_share_depth_selector',
1029 controller : ['$scope','egCore', function($scope , egCore) {
1031 egCore.pcrud.search('aout',
1032 { id : {'!=' : null} },
1033 { order_by : {aout : ['depth', 'name']} },
1035 ).then(function(list) {
1037 angular.forEach(list, function(aout) {
1038 var depth = parseInt(aout.depth());
1039 if (depth in scratch) {
1040 scratch[depth].push(aout.name());
1042 scratch[depth] = [ aout.name() ]
1045 scratch.forEach(function(val, idx) {
1046 $scope.values.push({ id : idx, name : scratch[idx].join(' / ') });
1054 * egHelpPopover - a helpful widget
1056 .directive('egHelpPopover', function() {
1064 templateUrl : './share/t_help_popover',
1065 controller : ['$scope','$sce', function($scope , $sce) {
1066 if ($scope.helpLink) {
1067 $scope.helpHtml = $sce.trustAsHtml(
1068 '<a target="_new" href="' + $scope.helpLink + '">' +
1069 $scope.helpText + '</a>'
1076 .factory('egWorkLog', ['egCore', function(egCore) {
1079 service.retrieve_all = function() {
1080 var workLog = egCore.hatch.getLocalItem('eg.work_log') || [];
1081 var patronLog = egCore.hatch.getLocalItem('eg.patron_log') || [];
1083 return { 'work_log' : workLog, 'patron_log' : patronLog };
1086 service.record = function(message,data) {
1089 if (typeof egCore != 'undefined') {
1090 if (typeof egCore.env != 'undefined') {
1091 if (typeof egCore.env.aous != 'undefined') {
1092 max_entries = egCore.env.aous['ui.admin.work_log.max_entries'];
1093 max_patrons = egCore.env.aous['ui.admin.patron_log.max_entries'];
1095 console.log('worklog: missing egCore.env.aous');
1098 console.log('worklog: missing egCore.env');
1101 console.log('worklog: missing egCore');
1104 if (typeof egCore.org != 'undefined') {
1105 if (typeof egCore.org.cachedSettings != 'undefined') {
1106 max_entries = egCore.org.cachedSettings['ui.admin.work_log.max_entries'];
1108 console.log('worklog: missing egCore.org.cachedSettings');
1111 console.log('worklog: missing egCore.org');
1115 if (typeof egCore.org != 'undefined') {
1116 if (typeof egCore.org.cachedSettings != 'undefined') {
1117 max_patrons = egCore.org.cachedSettings['ui.admin.patron_log.max_entries'];
1119 console.log('worklog: missing egCore.org.cachedSettings');
1122 console.log('worklog: missing egCore.org');
1127 console.log('worklog: defaulting to max_entries = ' + max_entries);
1131 console.log('worklog: defaulting to max_patrons = ' + max_patrons);
1134 var workLog = egCore.hatch.getLocalItem('eg.work_log') || [];
1135 var patronLog = egCore.hatch.getLocalItem('eg.patron_log') || [];
1137 'when' : new Date(),
1139 'action' : data.action,
1140 'actor' : egCore.auth.user().usrname()
1142 if (data.action == 'checkin') {
1143 entry['item'] = data.response.params.copy_barcode;
1144 entry['item_id'] = data.response.data.acp.id();
1145 if (data.response.data.au) {
1146 entry['user'] = data.response.data.au.family_name();
1147 entry['patron_id'] = data.response.data.au.id();
1150 if (data.action == 'checkout') {
1151 entry['item'] = data.response.params.copy_barcode;
1152 entry['user'] = data.response.data.au.family_name();
1153 entry['item_id'] = data.response.data.acp.id();
1154 entry['patron_id'] = data.response.data.au.id();
1156 if (data.action == 'noncat_checkout') {
1157 entry['user'] = data.response.data.au.family_name();
1158 entry['patron_id'] = data.response.data.au.id();
1160 if (data.action == 'renew') {
1161 entry['item'] = data.response.params.copy_barcode;
1162 entry['user'] = data.response.data.au.family_name();
1163 entry['item_id'] = data.response.data.acp.id();
1164 entry['patron_id'] = data.response.data.au.id();
1166 if (data.action == 'requested_hold'
1167 || data.action == 'edited_patron'
1168 || data.action == 'registered_patron'
1169 || data.action == 'paid_bill') {
1170 entry['patron_id'] = data.patron_id;
1172 if (data.action == 'requested_hold') {
1173 entry['hold_id'] = data.hold_id;
1175 if (data.action == 'paid_bill') {
1176 entry['amount'] = data.total_amount;
1179 workLog.push( entry );
1180 if (workLog.length > max_entries) workLog.shift();
1181 egCore.hatch.setLocalItem('eg.work_log',workLog); // hatch JSONifies the data, so should be okay re: memory leaks?
1183 if (entry['patron_id']) {
1185 for (var i = 0; i < patronLog.length; i++) { // filter out any matching patron
1186 if (patronLog[i]['patron_id'] != entry['patron_id']) temp.push(patronLog[i]);
1189 if (temp.length > max_patrons) temp.shift();
1191 egCore.hatch.setLocalItem('eg.patron_log',patronLog);
1194 console.log('worklog',entry);