Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular UI Datepicker Limiting Days per month

I am using using the angular datepicker:

http://angular-ui.github.io/bootstrap/#/datepicker

Currently it is hardcoded to display 42 days or 6 weeks.

I would like to know how to overwrite (ie. angular decorator) this function in ui-bootstrap-0.13.1.js to display 4 weeks.

Here is the function:

ctrl._refreshView = function() {
  var year = ctrl.activeDate.getFullYear(),
    month = ctrl.activeDate.getMonth(),
    firstDayOfMonth = new Date(year, month, 1),
    difference = ctrl.startingDay - firstDayOfMonth.getDay(),
    numDisplayedFromPreviousMonth = (difference > 0) ? 7 - difference : -difference,
    firstDate = new Date(firstDayOfMonth);

  if (numDisplayedFromPreviousMonth > 0) {
    firstDate.setDate(-numDisplayedFromPreviousMonth + 1);
  }

  // 42 is the number of days on a six-month calendar
  var days = getDates(firstDate, 42);
  for (var i = 0; i < 42; i++) {
    days[i] = angular.extend(ctrl.createDateObject(days[i], ctrl.formatDay), {
      secondary: days[i].getMonth() !== month,
      uid: scope.uniqueId + '-' + i
    });
  }

  scope.labels = new Array(7);
  for (var j = 0; j < 7; j++) {
    scope.labels[j] = {
      abbr: dateFilter(days[j].date, ctrl.formatDayHeader),
      full: dateFilter(days[j].date, 'EEEE')
    };
  }

  scope.title = dateFilter(ctrl.activeDate, ctrl.formatDayTitle);
  scope.rows = ctrl.split(days, 7);

  if (scope.showWeeks) {
    scope.weekNumbers = [];
    var thursdayIndex = (4 + 7 - ctrl.startingDay) % 7,
      numWeeks = scope.rows.length;
    for (var curWeek = 0; curWeek < numWeeks; curWeek++) {
      scope.weekNumbers.push(
        getISO8601WeekNumber(scope.rows[curWeek][thursdayIndex].date));
    }
  }
};

I've tried hard coding the 42 days to something smaller but it breaks the calender calculation. Just looking if anyone has extended this before?

Cheers

like image 474
MichaelBell Avatar asked Oct 19 '22 06:10

MichaelBell


1 Answers

You should be able to wrap _refreshView within a new compile function wrapping the old link function, if you use decorator. Within your ctrl._refreshView wrapper, you can do something like:

directive.compile = function() {
  return function(scope, element, attrs, ctrl) {
    link.apply(this, arguments);

    var _refreshView = ctrl._refreshView;
    ctrl._refreshView = function() {
      _refreshView.apply(this, arguments);
      removeAnySecondaryOnlyRows(scope.rows);
    };

    removeAnySecondaryOnlyRows(scope.rows);
  };
};

And:

//Remove any rows that contain only 'secondary' dates (those from other months)
function removeAnySecondaryOnlyRows(rows) {
  for (var i = 0; i < rows.length; i++) {
    var row = rows[i],
      areAllSecondary = false;

    //check the first and the last cell's .secondary property
    areAllSecondary = row[0].secondary && row[(row.length - 1)].secondary;

    if (areAllSecondary) {
      rows.splice(i, 1);
      i--;
    }
  }
}

And all together as a functioning snippet, including a custom template and some css to hide the 'secondary' days (those from a different month):

(function() {
  "use strict";

  angular.module('myApp', ['ui.bootstrap'])
    .config(['$provide', Decorate])
    .controller("MainController", ['$scope', MainController]);

  function Decorate($provide) {

    //fix for Datepicker 'day mode' to show only rows with days from the current month
    $provide.decorator('daypickerDirective', ['$delegate', daypickerDirectiveDecorator]);

    function daypickerDirectiveDecorator($delegate) {
      var directive = $delegate[0],
        link = directive.link;

      // custom html template:
      directive.templateUrl = "common/directives/uiBootstrapDatepickerDay.tpl.html";

      directive.compile = function() {
        return function(scope, element, attrs, ctrl) {
          link.apply(this, arguments);

          var _refreshView = ctrl._refreshView;
          ctrl._refreshView = function() {
            _refreshView.apply(this, arguments);
            removeAnySecondaryOnlyRows(scope.rows);
          };

          removeAnySecondaryOnlyRows(scope.rows);
        };
      };

      return $delegate;
    }

    //Remove any rows that contain only 'secondary' dates (those from other months)
    function removeAnySecondaryOnlyRows(rows) {
      for (var i = 0; i < rows.length; i++) {
        var row = rows[i],
          areAllSecondary = false;

        //check the first and the last cell's .secondary property
        areAllSecondary = row[0].secondary && row[(row.length - 1)].secondary;

        if (areAllSecondary) {
          rows.splice(i, 1);
          i--;
        }
      }
    }
  }

  function MainController($scope) {
    $scope.dt = new Date();
  }
})();
/*
Example of hiding days from other months:
We've put a class of 'datepicker' on the element loading the datepicker directive,
and we've customised the day template to add an 'invis' class to the day's button if
it's a 'secondary' day (a day from another month).
*/

.datepicker .invis {
  visibility: hidden;
}
<link href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css" rel="stylesheet">
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.28/angular.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/0.13.0/ui-bootstrap-tpls.min.js"></script>

<div ng-app="myApp" ng-controller="MainController">

  <!--
   Modified day template to add an 'invis' class to secondary day buttons.
   This could/should be kept in a separate file (http://stackoverflow.com/a/26339919/446030)
   -->
  <script id="common/directives/uiBootstrapDatepickerDay.tpl.html" type="text/ng-template">
    <table role="grid" aria-labelledby="{{uniqueId}}-title" aria-activedescendant="{{activeDateId}}">
      <thead>
        <tr>
          <th>
            <button type="button" class="btn btn-default btn-sm pull-left" ng-click="move(-1)" tabindex="-1"><i class="glyphicon glyphicon-chevron-left"></i>
            </button>
          </th>
          <th colspan="{{5 + showWeeks}}">
            <button id="{{uniqueId}}-title" role="heading" aria-live="assertive" aria-atomic="true" type="button" class="btn btn-default btn-sm" ng-click="toggleMode()" ng-disabled="datepickerMode === maxMode" tabindex="-1" style="width:100%;"><strong>{{title}}</strong>
            </button>
          </th>
          <th>
            <button type="button" class="btn btn-default btn-sm pull-right" ng-click="move(1)" tabindex="-1"><i class="glyphicon glyphicon-chevron-right"></i>
            </button>
          </th>
        </tr>
        <tr>
          <th ng-if="showWeeks" class="text-center"></th>
          <th ng-repeat="label in labels track by $index" class="text-center"><small aria-label="{{label.full}}">{{label.abbr}}</small>
          </th>
        </tr>
      </thead>
      <tbody>
        <tr ng-repeat="row in rows track by $index">
          <td ng-if="showWeeks" class="text-center h6"><em>{{ weekNumbers[$index] }}</em>
          </td>
          <td ng-repeat="dt in row track by dt.date" class="text-center" role="gridcell" id="{{dt.uid}}" ng-class="dt.customClass">
            <!--######## 'invis' class added in ng-class on the button: #######-->
            <button type="button" style="min-width:100%;" class="btn btn-default btn-sm" ng-class="{'btn-info': dt.selected, active: isActive(dt), 'invis': dt.secondary}" ng-click="select(dt.date)" ng-disabled="dt.disabled" tabindex="-1"><span ng-class="{'text-muted': dt.secondary, 'text-info': dt.current}">{{dt.label}}</span>
            </button>
          </td>
        </tr>
      </tbody>
    </table>
  </script>

  <div>
    <strong>Selected date:</strong> {{dt.toDateString()}}
  </div>
  <div style="display:inline-block; min-height:290px;">
    <div datepicker ng-model="dt" class="well well-sm datepicker"></div>
  </div>

</div>
like image 170
JcT Avatar answered Oct 29 '22 19:10

JcT