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 ['$modal','$interpolate',
89 function($modal , $interpolate) {
92 service.open = function(message, msg_scope) {
94 templateUrl: './share/t_alert_dialog',
95 controller: ['$scope', '$modalInstance',
96 function($scope, $modalInstance) {
97 $scope.message = $interpolate(message)(msg_scope);
98 $scope.ok = function() {
99 if (msg_scope && msg_scope.ok) msg_scope.ok();
100 $modalInstance.close()
111 * egConfirmDialog.open("some message goes {{here}}", {
112 * here : 'foo', ok : function() {}, cancel : function() {}},
115 .factory('egConfirmDialog',
117 ['$modal','$interpolate',
118 function($modal, $interpolate) {
121 service.open = function(title, message, msg_scope, ok_button_label, cancel_button_label) {
123 templateUrl: './share/t_confirm_dialog',
124 controller: ['$scope', '$modalInstance',
125 function($scope, $modalInstance) {
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 $modalInstance.close()
134 $scope.cancel = function() {
135 if (msg_scope.cancel) msg_scope.cancel();
136 $modalInstance.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 ['$modal','$interpolate',
160 function($modal, $interpolate) {
163 service.open = function(message, promptValue, msg_scope) {
165 templateUrl: './share/t_prompt_dialog',
166 controller: ['$scope', '$modalInstance',
167 function($scope, $modalInstance) {
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 $modalInstance.close()
175 $scope.cancel = function() {
176 if (msg_scope.cancel) msg_scope.cancel();
177 $modalInstance.dismiss();
188 * Warn on page unload and give the user a chance to avoid navigating
189 * away from the current page.
190 * Only one handler is supported per page.
191 * NOTE: we can't use an egUnloadDialog as the dialog builder, because
192 * it renders asynchronously, which allows the page to redirect before
193 * the dialog appears.
195 .factory('egUnloadPrompt', [
196 '$window','egStrings',
197 function($window , egStrings) {
200 // attach a page/scope unload prompt
201 service.attach = function($scope, msg) {
203 // handle page change
204 $($window).on('beforeunload', function() {
206 return msg || egStrings.EG_UNLOAD_PAGE_PROMPT_MSG;
211 // If a scope was provided, attach a scope-change handler,
212 // similar to the page-page prompt.
213 service.locChangeCancel =
214 $scope.$on('$locationChangeStart', function(evt, next, current) {
215 if (confirm(msg || egStrings.EG_UNLOAD_CTRL_PROMPT_MSG)) {
216 // user allowed the page to change.
217 // Clear the unload handler.
220 evt.preventDefault();
225 // remove the page unload prompt
226 service.clear = function() {
227 $($window).off('beforeunload');
228 if (service.locChangeCancel)
229 service.locChangeCancel();
235 .directive('aDisabled', function() {
238 compile: function(tElement, tAttrs, transclude) {
240 tAttrs["ngClick"] = ("ng-click", "!("+tAttrs["aDisabled"]+") && ("+tAttrs["ngClick"]+")");
242 //Toggle "disabled" to class when aDisabled becomes true
243 return function (scope, iElement, iAttrs) {
244 scope.$watch(iAttrs["aDisabled"], function(newValue) {
245 if (newValue !== undefined) {
246 iElement.toggleClass("disabled", newValue);
250 //Disable href on click
251 iElement.on("click", function(e) {
252 if (scope.$eval(iAttrs["aDisabled"])) {
261 .directive('egBasicComboBox', function() {
266 list: "=", // list of strings
271 '<div class="input-group">'+
272 '<input type="text" ng-disabled="egDisabled" class="form-control" ng-model="selected" ng-change="makeOpen()">'+
273 '<div class="input-group-btn" dropdown ng-class="{open:isopen}">'+
274 '<button type="button" ng-click="showAll()" class="btn btn-default dropdown-toggle"><span class="caret"></span></button>'+
275 '<ul class="dropdown-menu dropdown-menu-right">'+
276 '<li ng-repeat="item in list|filter:selected"><a href ng-click="changeValue(item)">{{item}}</a></li>'+
277 '<li ng-if="all" class="divider"><span></span></li>'+
278 '<li ng-if="all" ng-repeat="item in list"><a href ng-click="changeValue(item)">{{item}}</a></li>'+
282 controller: ['$scope','$filter',
283 function( $scope , $filter) {
286 $scope.isopen = false;
288 $scope.showAll = function () {
289 if ($scope.selected.length > 0)
293 $scope.makeOpen = function () {
294 $scope.isopen = $filter('filter')(
297 ).length > 0 && $scope.selected.length > 0;
301 $scope.changeValue = function (newVal) {
302 $scope.selected = newVal;
303 $scope.isopen = false;
312 * Nested org unit selector modeled as a Bootstrap dropdown button.
314 .directive('egOrgSelector', function() {
318 replace : true, // makes styling easier
320 selected : '=', // defaults to workstation or root org,
321 // unless the nodefault attibute exists
323 // Each org unit is passed into this function and, for
324 // any org units where the response value is true, the
325 // org unit will not be added to the selector.
328 // Each org unit is passed into this function and, for
329 // any org units where the response value is true, the
330 // org unit will not be available for selection.
333 // if set to true, disable the UI element altogether
336 // Caller can either $watch(selected, ..) or register an
340 // optional primary drop-down button label
343 // optional name of settings key for persisting
344 // the last selected org unit
348 // any reason to move this into a TT2 template?
350 '<div class="btn-group eg-org-selector" dropdown>'
351 + '<button type="button" class="btn btn-default dropdown-toggle" ng-disabled="disable_button">'
352 + '<span style="padding-right: 5px;">{{getSelectedName()}}</span>'
353 + '<span class="caret"></span>'
355 + '<ul class="dropdown-menu scrollable-menu">'
356 + '<li ng-repeat="org in orgList" ng-hide="hiddenTest(org.id)">'
357 + '<a href ng-click="orgChanged(org)" a-disabled="disableTest(org.id)" '
358 + 'style="padding-left: {{org.depth * 10 + 5}}px">'
359 + '{{org.shortname}}'
365 controller : ['$scope','$timeout','egOrg','egAuth','egCore','egStartup',
366 function($scope , $timeout , egOrg , egAuth , egCore , egStartup) {
368 if ($scope.alldisabled) {
369 $scope.disable_button = $scope.alldisabled == 'true' ? true : false;
371 $scope.disable_button = false;
374 $scope.egOrg = egOrg; // for use in the link function
375 $scope.egAuth = egAuth; // for use in the link function
376 $scope.hatch = egCore.hatch // for use in the link function
378 // avoid linking the full fleshed tree to the scope by
379 // tossing in a flattened list.
381 // Run-time code referencing post-start data should be run
382 // from within a startup block, otherwise accessing this
383 // module before startup completes will lead to failure.
384 egStartup.go().then(function() {
386 $scope.orgList = egOrg.list().map(function(org) {
389 shortname : org.shortname(),
390 depth : org.ou_type().depth()
394 if (!$scope.selected)
395 $scope.selected = egOrg.get(egAuth.user().ws_ou());
398 $scope.getSelectedName = function() {
399 if ($scope.selected && $scope.selected.shortname)
400 return $scope.selected.shortname();
404 $scope.orgChanged = function(org) {
405 $scope.selected = egOrg.get(org.id);
406 if ($scope.stickySetting) {
407 egCore.hatch.setLocalItem($scope.stickySetting, org.id);
409 if ($scope.onchange) $scope.onchange($scope.selected);
413 link : function(scope, element, attrs, egGridCtrl) {
415 // boolean fields are presented as value-less attributes
419 if (angular.isDefined(attrs[field]))
422 scope[field] = false;
426 if (scope.stickySetting) {
427 var orgId = scope.hatch.getLocalItem(scope.stickySetting);
429 scope.selected = scope.egOrg.get(orgId);
433 if (!scope.selected && !scope.nodefault)
434 scope.selected = scope.egOrg.get(scope.egAuth.user().ws_ou());
440 /* http://eric.sau.pe/angularjs-detect-enter-key-ngenter/ */
441 .directive('egEnter', function () {
442 return function (scope, element, attrs) {
443 element.bind("keydown keypress", function (event) {
444 if(event.which === 13) {
445 scope.$apply(function (){
446 scope.$eval(attrs.egEnter);
449 event.preventDefault();
456 http://stackoverflow.com/questions/18061757/angular-js-and-html5-date-input-value-how-to-get-firefox-to-show-a-readable-d
458 This directive allows us to use html5 input type="date" (for Chrome) and
459 gracefully fall back to a regular ISO text input for Firefox.
460 It also allows us to abstract away some browser finickiness.
464 function(dateFilter) {
467 template: '<input type="date"></input>',
469 link: function(scope, elm, attrs, ngModelCtrl) {
471 // since this is a date-only selector, set the time
472 // portion to 00:00:00, which should better match the
473 // user's expectations. Note this allows us to retain
475 function strip_time(date) {
476 if (!date) date = new Date();
480 date.setMilliseconds(0);
484 ngModelCtrl.$formatters.unshift(function (modelValue) {
485 // apply strip_time here in case the user never
486 // modifies the date value.
487 return dateFilter(strip_time(modelValue), 'yyyy-MM-dd');
490 ngModelCtrl.$parsers.unshift(function(viewValue) {
491 return strip_time(new Date(viewValue));