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() {
22 scope.$apply(model.assign(scope, false));
29 * <input blur-me="pleaseBlurMe"/>
30 * $scope.pleaseBlurMe = true
31 * Useful for de-focusing when no other obvious focus target exists
35 function($timeout , $parse) {
37 link: function(scope, element, attrs) {
38 var model = $parse(attrs.blurMe);
39 scope.$watch(model, function(value) {
41 $timeout(function() {element[0].blur()});
43 element.bind('focus', function() {
44 scope.$apply(model.assign(scope, false));
51 // <input select-me="iWantToBeSelected"/>
52 // $scope.iWantToBeSelected = true;
53 .directive('selectMe',
55 function($timeout , $parse) {
57 link: function(scope, element, attrs) {
58 var model = $parse(attrs.selectMe);
59 scope.$watch(model, function(value) {
61 $timeout(function() {element[0].select()});
63 element.bind('blur', function() {
64 scope.$apply(model.assign(scope, false));
72 // <div ng-repeat="item in items | reverse">{{item.name}}</div>
73 // http://stackoverflow.com/questions/15266671/angular-ng-repeat-in-reverse
74 // TODO: perhaps this should live elsewhere
75 .filter('reverse', function() {
76 return function(items) {
77 return items.slice().reverse();
83 * egAlertDialog.open({message : 'hello {{name}}'}).result.then(
84 * function() { console.log('alert closed') });
86 .factory('egAlertDialog',
88 ['$uibModal','$interpolate',
89 function($uibModal , $interpolate) {
92 service.open = function(message, msg_scope) {
93 return $uibModal.open({
94 templateUrl: './share/t_alert_dialog',
95 controller: ['$scope', '$uibModalInstance',
96 function($scope, $uibModalInstance) {
97 $scope.message = $interpolate(message)(msg_scope);
98 $scope.ok = function() {
99 if (msg_scope && msg_scope.ok) msg_scope.ok();
100 $uibModalInstance.close()
111 * egConfirmDialog.open("some message goes {{here}}", {
112 * here : 'foo', ok : function() {}, cancel : function() {}},
115 .factory('egConfirmDialog',
117 ['$uibModal','$interpolate',
118 function($uibModal, $interpolate) {
121 service.open = function(title, message, msg_scope, ok_button_label, cancel_button_label) {
122 return $uibModal.open({
123 templateUrl: './share/t_confirm_dialog',
124 controller: ['$scope', '$uibModalInstance',
125 function($scope, $uibModalInstance) {
126 $scope.title = $interpolate(title)(msg_scope);
127 $scope.message = $interpolate(message)(msg_scope);
128 $scope.ok_button_label = $interpolate(ok_button_label || '')(msg_scope);
129 $scope.cancel_button_label = $interpolate(cancel_button_label || '')(msg_scope);
130 $scope.ok = function() {
131 if (msg_scope.ok) msg_scope.ok();
132 $uibModalInstance.close()
134 $scope.cancel = function() {
135 if (msg_scope.cancel) msg_scope.cancel();
136 $uibModalInstance.dismiss();
147 * egPromptDialog.open(
148 * "prompt message goes {{here}}",
149 * promptValue, // optional
152 * ok : function(value) {console.log(value)},
153 * cancel : function() {console.log('prompt denied')}
157 .factory('egPromptDialog',
159 ['$uibModal','$interpolate',
160 function($uibModal, $interpolate) {
163 service.open = function(message, promptValue, msg_scope) {
164 return $uibModal.open({
165 templateUrl: './share/t_prompt_dialog',
166 controller: ['$scope', '$uibModalInstance',
167 function($scope, $uibModalInstance) {
168 $scope.message = $interpolate(message)(msg_scope);
169 $scope.args = {value : promptValue || ''};
171 $scope.ok = function() {
172 if (msg_scope.ok) msg_scope.ok($scope.args.value);
173 $uibModalInstance.close()
175 $scope.cancel = function() {
176 if (msg_scope.cancel) msg_scope.cancel();
177 $uibModalInstance.dismiss();
188 * egSelectDialog.open(
189 * "message goes {{here}}",
190 * list, // ['values','for','dropdown'],
191 * selectedValue, // optional
194 * ok : function(value) {console.log(value)},
195 * cancel : function() {console.log('prompt denied')}
199 .factory('egSelectDialog',
201 ['$uibModal','$interpolate',
202 function($uibModal, $interpolate) {
205 service.open = function(message, inputList, selectedValue, msg_scope) {
206 return $uibModal.open({
207 templateUrl: './share/t_select_dialog',
208 controller: ['$scope', '$uibModalInstance',
209 function($scope, $uibModalInstance) {
210 $scope.message = $interpolate(message)(msg_scope);
213 value : selectedValue
216 $scope.ok = function() {
217 if (msg_scope.ok) msg_scope.ok($scope.args.value);
218 $uibModalInstance.close()
220 $scope.cancel = function() {
221 if (msg_scope.cancel) msg_scope.cancel();
222 $uibModalInstance.dismiss();
233 * Warn on page unload and give the user a chance to avoid navigating
234 * away from the current page.
235 * Only one handler is supported per page.
236 * NOTE: we can't use an egUnloadDialog as the dialog builder, because
237 * it renders asynchronously, which allows the page to redirect before
238 * the dialog appears.
240 .factory('egUnloadPrompt', [
241 '$window','egStrings',
242 function($window , egStrings) {
243 var service = {attached : false};
245 // attach a page/scope unload prompt
246 service.attach = function($scope, msg) {
247 if (service.attached) return;
248 service.attached = true;
250 // handle page change
251 $($window).on('beforeunload', function() {
253 return msg || egStrings.EG_UNLOAD_PAGE_PROMPT_MSG;
258 // If a scope was provided, attach a scope-change handler,
259 // similar to the page-page prompt.
260 service.locChangeCancel =
261 $scope.$on('$locationChangeStart', function(evt, next, current) {
262 if (confirm(msg || egStrings.EG_UNLOAD_CTRL_PROMPT_MSG)) {
263 // user allowed the page to change.
264 // Clear the unload handler.
267 evt.preventDefault();
272 // remove the page unload prompt
273 service.clear = function() {
274 $($window).off('beforeunload');
275 if (service.locChangeCancel)
276 service.locChangeCancel();
277 service.attached = false;
283 .directive('aDisabled', function() {
286 compile: function(tElement, tAttrs, transclude) {
288 tAttrs["ngClick"] = ("ng-click", "!("+tAttrs["aDisabled"]+") && ("+tAttrs["ngClick"]+")");
290 //Toggle "disabled" to class when aDisabled becomes true
291 return function (scope, iElement, iAttrs) {
292 scope.$watch(iAttrs["aDisabled"], function(newValue) {
293 if (newValue !== undefined) {
294 iElement.toggleClass("disabled", newValue);
298 //Disable href on click
299 iElement.on("click", function(e) {
300 if (scope.$eval(iAttrs["aDisabled"])) {
309 .directive('egBasicComboBox', function() {
314 list: "=", // list of strings
320 '<div class="input-group">'+
321 '<input type="text" ng-disabled="egDisabled" class="form-control" ng-model="selected" ng-change="makeOpen()">'+
322 '<div class="input-group-btn" dropdown ng-class="{open:isopen}">'+
323 '<button type="button" ng-click="showAll()" class="btn btn-default dropdown-toggle"><span class="caret"></span></button>'+
324 '<ul class="dropdown-menu dropdown-menu-right">'+
325 '<li ng-repeat="item in list|filter:selected"><a href ng-click="changeValue(item)">{{item}}</a></li>'+
326 '<li ng-if="complete_list" class="divider"><span></span></li>'+
327 '<li ng-if="complete_list" ng-repeat="item in list"><a href ng-click="changeValue(item)">{{item}}</a></li>'+
331 controller: ['$scope','$filter',
332 function( $scope , $filter) {
334 $scope.complete_list = false;
335 $scope.isopen = false;
336 $scope.clickedopen = false;
337 $scope.clickedclosed = null;
339 $scope.showAll = function () {
341 $scope.clickedopen = !$scope.clickedopen;
343 if ($scope.clickedclosed === null) {
344 if (!$scope.clickedopen) {
345 $scope.clickedclosed = true;
348 $scope.clickedclosed = !$scope.clickedopen;
351 if ($scope.selected.length > 0) $scope.complete_list = true;
352 if ($scope.selected.length == 0) $scope.complete_list = false;
356 $scope.makeOpen = function () {
357 $scope.isopen = $scope.clickedopen || ($filter('filter')(
360 ).length > 0 && $scope.selected.length > 0);
361 if ($scope.clickedclosed) $scope.isopen = false;
364 $scope.changeValue = function (newVal) {
365 $scope.selected = newVal;
366 $scope.isopen = false;
367 $scope.clickedclosed = null;
368 $scope.clickedopen = false;
369 if ($scope.selected.length == 0) $scope.complete_list = false;
378 * Nested org unit selector modeled as a Bootstrap dropdown button.
380 .directive('egOrgSelector', function() {
384 replace : true, // makes styling easier
386 selected : '=', // defaults to workstation or root org,
387 // unless the nodefault attibute exists
389 // Each org unit is passed into this function and, for
390 // any org units where the response value is true, the
391 // org unit will not be added to the selector.
394 // Each org unit is passed into this function and, for
395 // any org units where the response value is true, the
396 // org unit will not be available for selection.
399 // if set to true, disable the UI element altogether
402 // Caller can either $watch(selected, ..) or register an
406 // optional primary drop-down button label
409 // optional name of settings key for persisting
410 // the last selected org unit
414 // any reason to move this into a TT2 template?
416 '<div class="btn-group eg-org-selector" uib-dropdown>'
417 + '<button type="button" class="btn btn-default" uib-dropdown-toggle ng-disabled="disable_button">'
418 + '<span style="padding-right: 5px;">{{getSelectedName()}}</span>'
419 + '<span class="caret"></span>'
421 + '<ul uib-dropdown-menu class="scrollable-menu">'
422 + '<li ng-repeat="org in orgList" ng-hide="hiddenTest(org.id)">'
423 + '<a href ng-click="orgChanged(org)" a-disabled="disableTest(org.id)" '
424 + 'style="padding-left: {{org.depth * 10 + 5}}px">'
425 + '{{org.shortname}}'
431 controller : ['$scope','$timeout','egOrg','egAuth','egCore','egStartup',
432 function($scope , $timeout , egOrg , egAuth , egCore , egStartup) {
434 if ($scope.alldisabled) {
435 $scope.disable_button = $scope.alldisabled == 'true' ? true : false;
437 $scope.disable_button = false;
440 $scope.egOrg = egOrg; // for use in the link function
441 $scope.egAuth = egAuth; // for use in the link function
442 $scope.hatch = egCore.hatch // for use in the link function
444 // avoid linking the full fleshed tree to the scope by
445 // tossing in a flattened list.
447 // Run-time code referencing post-start data should be run
448 // from within a startup block, otherwise accessing this
449 // module before startup completes will lead to failure.
450 egStartup.go().then(function() {
452 $scope.orgList = egOrg.list().map(function(org) {
455 shortname : org.shortname(),
456 depth : org.ou_type().depth()
460 if (!$scope.selected)
461 $scope.selected = egOrg.get(egAuth.user().ws_ou());
464 $scope.getSelectedName = function() {
465 if ($scope.selected && $scope.selected.shortname)
466 return $scope.selected.shortname();
470 $scope.orgChanged = function(org) {
471 $scope.selected = egOrg.get(org.id);
472 if ($scope.stickySetting) {
473 egCore.hatch.setLocalItem($scope.stickySetting, org.id);
475 if ($scope.onchange) $scope.onchange($scope.selected);
479 link : function(scope, element, attrs, egGridCtrl) {
481 // boolean fields are presented as value-less attributes
485 if (angular.isDefined(attrs[field]))
488 scope[field] = false;
492 if (scope.stickySetting) {
493 var orgId = scope.hatch.getLocalItem(scope.stickySetting);
495 scope.selected = scope.egOrg.get(orgId);
499 if (!scope.selected && !scope.nodefault)
500 scope.selected = scope.egOrg.get(scope.egAuth.user().ws_ou());
506 /* http://eric.sau.pe/angularjs-detect-enter-key-ngenter/ */
507 .directive('egEnter', function () {
508 return function (scope, element, attrs) {
509 element.bind("keydown keypress", function (event) {
510 if(event.which === 13) {
511 scope.$apply(function (){
512 scope.$eval(attrs.egEnter);
515 event.preventDefault();
522 * Handy wrapper directive for uib-datapicker-popup
525 'egDateInput', ['egStrings', 'egCore',
526 function(egStrings, egCore) {
535 hideDatePicker : '=',
539 templateUrl: './share/t_datetime',
541 link : function(scope, elm, attrs) {
542 if (!scope.closeText)
543 scope.closeText = egStrings.EG_DATE_INPUT_CLOSE_TEXT;
545 if ('showTimePicker' in attrs)
546 scope.showTimePicker = true;
548 var default_format = 'mediumDate';
549 egCore.org.settings(['format.date']).then(function(set) {
550 default_format = set['format.date'];
551 scope.date_format = (scope.dateFormat) ?
560 .factory('egWorkLog', ['egCore', function(egCore) {
563 service.retrieve_all = function() {
564 var workLog = egCore.hatch.getLocalItem('eg.work_log') || [];
565 var patronLog = egCore.hatch.getLocalItem('eg.patron_log') || [];
567 return { 'work_log' : workLog, 'patron_log' : patronLog };
570 service.record = function(message,data) {
573 if (typeof egCore != 'undefined') {
574 if (typeof egCore.env != 'undefined') {
575 if (typeof egCore.env.aous != 'undefined') {
576 max_entries = egCore.env.aous['ui.admin.work_log.max_entries'];
577 max_patrons = egCore.env.aous['ui.admin.patron_log.max_entries'];
579 console.log('worklog: missing egCore.env.aous');
582 console.log('worklog: missing egCore.env');
585 console.log('worklog: missing egCore');
588 if (typeof egCore.org != 'undefined') {
589 if (typeof egCore.org.cachedSettings != 'undefined') {
590 max_entries = egCore.org.cachedSettings['ui.admin.work_log.max_entries'];
592 console.log('worklog: missing egCore.org.cachedSettings');
595 console.log('worklog: missing egCore.org');
599 if (typeof egCore.org != 'undefined') {
600 if (typeof egCore.org.cachedSettings != 'undefined') {
601 max_patrons = egCore.org.cachedSettings['ui.admin.patron_log.max_entries'];
603 console.log('worklog: missing egCore.org.cachedSettings');
606 console.log('worklog: missing egCore.org');
611 console.log('worklog: defaulting to max_entries = ' + max_entries);
615 console.log('worklog: defaulting to max_patrons = ' + max_patrons);
618 var workLog = egCore.hatch.getLocalItem('eg.work_log') || [];
619 var patronLog = egCore.hatch.getLocalItem('eg.patron_log') || [];
624 'action' : data.action,
625 'actor' : egCore.auth.user().usrname()
627 if (data.action == 'checkin') {
628 entry['item'] = data.response.params.copy_barcode;
629 entry['user'] = data.response.data.au.family_name();
630 entry['item_id'] = data.response.data.acp.id();
631 entry['patron_id'] = data.response.data.au.id();
633 if (data.action == 'checkout') {
634 entry['item'] = data.response.params.copy_barcode;
635 entry['user'] = data.response.data.au.family_name();
636 entry['item_id'] = data.response.data.acp.id();
637 entry['patron_id'] = data.response.data.au.id();
639 if (data.action == 'renew') {
640 entry['item'] = data.response.params.copy_barcode;
641 entry['user'] = data.response.data.au.family_name();
642 entry['item_id'] = data.response.data.acp.id();
643 entry['patron_id'] = data.response.data.au.id();
645 if (data.action == 'requested_hold'
646 || data.action == 'edited_patron'
647 || data.action == 'registered_patron'
648 || data.action == 'paid_bill') {
649 entry['patron_id'] = data.patron_id;
651 if (data.action == 'paid_bill') {
652 entry['amount'] = data.total_amount;
655 workLog.push( entry );
656 if (workLog.length > max_entries) workLog.shift();
657 egCore.hatch.setLocalItem('eg.work_log',workLog); // hatch JSONifies the data, so should be okay re: memory leaks?
659 if (entry['patron_id']) {
661 for (var i = 0; i < patronLog.length; i++) { // filter out any matching patron
662 if (patronLog[i]['patron_id'] != entry['patron_id']) temp.push(patronLog[i]);
665 if (temp.length > max_patrons) temp.shift();
667 egCore.hatch.setLocalItem('eg.patron_log',patronLog);
670 console.log('worklog',entry);