Date picker on Service Portal - sideBySide

Alex Macdonel
Tera Expert

On the Service Portal the date time picker is very nice but our users have told us that when they use the arrows to increase the hours they sometimes don't notice that the date changed and this causes problems.

The Options - Bootstrap 3 Datepicker   tell me we can show both date and times side by side but now the question is, how can I enable this behavior in the Portal?

find_real_file.png

23 REPLIES 23

How can you get the value of the selected datetime in ng-model angular?


michal_v
Giga Contributor

Hey,

I managed to achieve this view with dynamic CSS, I know it's not best practice but I couldn't affect the view with the .datetimepicker({
sideBySide: true});, I tried putting it on each class that was relevant to this specific HTML block.

This is what I put in my SC Catalog Item client controller:

$timeout( function(){

//Date time picker CSS to be side by side
$('.bootstrap-datetimepicker-widget').each(function( ) {
$(this).css('width','32em');
$(this).css('padding','4px');
$(this).css('margin','2px 0');
});

$('.bootstrap-datetimepicker-widget .list-unstyled ').each(function( ) {
$(this).css('display','flex');
});

$('li.picker-switch').each(function( ) {
$(this).css('display','none');
});

$('.bootstrap-datetimepicker-widget ul.list-unstyled li.collapse').each(function( ) {
$(this).css('display','inline-block');
$(this).css('width','50%');
});

$('.timepicker').each(function( ) {
$(this).css('padding','0 10%');

});

}, 0 );

 

I tested it on chrome and IE, also with 14'' and 21'' screens and it looked fine:

find_real_file.png

vitaly_sukharev
Kilo Guru

Hi Alex,

Another alternative is to create a custom AngularJS directive and include it as a UI script dependency:

angular.module('sn.$sp').directive('spDatePickerCustom', function(spConf, $rootScope, $document, spAriaUtil, i18n, spDatePickerUtil) {
  var dateFormat = g_user_date_format || spConf.SYS_DATE_FORMAT;
  var dateTimeFormat = g_user_date_time_format || spConf.SYS_TIME_FORMAT;
  if ($rootScope.user && $rootScope.user.date_format)
    dateFormat = $rootScope.user.date_format;
  if ($rootScope.user && $rootScope.user.date_time_format)
    dateTimeFormat = $rootScope.user.date_time_format;
  return {
    template: '<div ng-class="{\'input-group\': !snDisabled, \'has-error\': field.isInvalid}" style="width: 100%;">' +
      '<input id="sp_formfield_{{::field.name}}" type="text" name="{{field.name}}" class="form-control" placeholder="{{field.placeholder}}" title="{{g_accessibility ? translations[\'Enter date in format\']: \'\'}}{{g_accessibility ? format : \'\'}}" tooltip-top="true" tooltip-enable="{{g_accessibility}}" ng-model="formattedDate" ng-model-options="{updateOn: \'blur\', getterSetter: true}" ng-readonly="snDisabled" />' +
      '<span class="input-group-btn" ng-hide="snDisabled">' +
      '<input type="hidden" class="datepickerinput" ng-model="formattedDate" ng-readonly="true" />' +
      '<button class="btn btn-default" type="button" tabindex="-1" aria-hidden="true">' +
      '<glyph sn-char="calendar" />' +
      '</button>' +
      '</span>' +
      '<span ng-if="field.isInvalid" class="sp-date-format-info" style="display:table-row;" aria-hidden="true">{{translations[\'Enter date in format\']}} {{format}}</span>' +
      '</div>',
    restrict: 'E',
    replace: true,
    require: '?ngModel',
    scope: {
      field: '=',
      snDisabled: '=',
      snIncludeTime: '=',
      sideBySide: '=',
      snChange: '&'
    },
    link: function(scope, element, attrs, ngModel) {
      scope.g_accessibility = spAriaUtil.isAccessibilityEnabled();
      var includeTime = scope.snIncludeTime;
      var sideBySide = scope.sideBySide;
      var format;
      format = includeTime ? dateTimeFormat.trim() : dateFormat.trim();
      format = format.replace(/y/g, 'Y').replace(/d/g, 'D').replace(/a/g, 'A');
      scope.format = format;
      var dp = element.find('.input-group-btn').datetimepicker({
        keepInvalid: true,
        pickTime: includeTime,
        format: format,
        sideBySide: sideBySide,
        locale: g_lang,
        language: g_lang
      }).on('dp.change', onDpChange);

      function validate(formattedDate) {
        scope.field.isInvalid = false;
        return spDatePickerUtil.validate(dp, format, formattedDate, function(error) {
          if (error) {
            spAriaUtil.sendLiveMessage(scope.translations["Entered date not valid. Enter date in format"] + " " + format);
            scope.field.isInvalid = true;
          }
        });
      }

      function closeOnTouch(evt) {
        if (!jQuery.contains(dp.data('DateTimePicker').widget[0], evt.target)) {
          dp.data('DateTimePicker').hide();
        }
      }

      function bindTouchClose() {
        $document.on('touchstart', closeOnTouch);
      }

      function unBindTouchClose() {
        $document.off('touchstart', closeOnTouch);
      }
      dp.on('dp.show', bindTouchClose).on('dp.hide', unBindTouchClose);

      function onDpChange(e) {
        scope.formattedDate(e.date.format(format));
        if (!scope.$root.$$phase)
          scope.$apply();
      }
      if (ngModel) {
        ngModel.$parsers.push(validate);
        ngModel.$render = function() {
          validate(ngModel.$viewValue);
        };
        scope.formattedDate = function(formattedValue) {
          if (angular.isDefined(formattedValue)) {
            ngModel.$setViewValue(formattedValue);
            if (scope.snChange) scope.snChange({
              newValue: formattedValue
            });
          }
          return ngModel.$viewValue;
        };
      } else {
        scope.formattedDate = function(formattedValue) {
          if (angular.isDefined(formattedValue)) {
            scope.field.value = validate(formattedValue);
            if (scope.snChange) scope.snChange({
              newValue: formattedValue
            });
          }
          return scope.field.value;
        };
        scope.$watch('field.value', function(newValue, oldValue) {
          if (newValue != oldValue)
            validate(newValue);
        });
      }
      scope.$on('$destroy', function() {
        dp.off('dp.change', onDpChange);
        unBindTouchClose();
      });
      scope.translations = [];
      i18n.getMessages(["Enter date in format", "Use format", "Entered date not valid. Enter date in format"], function(msgs) {
        scope.translations = msgs;
      });
    }
  }
});

This new directive can be used like this:

<sp-date-picker-custom sn-include-time="true"
                       field="{'name':'date'}"
                       ng-model="date1"
                       side-by-side="false">
</sp-date-picker-custom>
<sp-date-picker-custom sn-include-time="true"
                       field="{'name':'date'}"
                       ng-model="date2"
                       side-by-side="true">
</sp-date-picker-custom>

 

This worked well.  I can't seem to get the datepicker to be required however.  Any advice?

 

<sp-date-picker-custom name="board_ex_date" id="board_ex_date" sn-include-time="false"
field="{'name':'board_ex_date'}"
side-by-side="true"
ng-model="c.board_ex_date"
ng-required="true">
</sp-date-picker-custom>

Don't know if anyone has tried this custom directive lately, but it's throwing errors...

find_real_file.png

and the ng-model seems to be unable to return the value in the field back to the client script. I'm currently on Orlando.