BasicView.js 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  1. /* An abstract class for the "basic" views, as well as month view. Renders one or more rows of day cells.
  2. ----------------------------------------------------------------------------------------------------------------------*/
  3. // It is a manager for a DayGrid subcomponent, which does most of the heavy lifting.
  4. // It is responsible for managing width/height.
  5. var BasicView = FC.BasicView = View.extend({
  6. scroller: null,
  7. dayGridClass: DayGrid, // class the dayGrid will be instantiated from (overridable by subclasses)
  8. dayGrid: null, // the main subcomponent that does most of the heavy lifting
  9. dayNumbersVisible: false, // display day numbers on each day cell?
  10. colWeekNumbersVisible: false, // display week numbers along the side?
  11. cellWeekNumbersVisible: false, // display week numbers in day cell?
  12. weekNumberWidth: null, // width of all the week-number cells running down the side
  13. headContainerEl: null, // div that hold's the dayGrid's rendered date header
  14. headRowEl: null, // the fake row element of the day-of-week header
  15. constructor: function() {
  16. View.apply(this, arguments);
  17. this.dayGrid = this.instantiateDayGrid();
  18. this.addChild(this.dayGrid);
  19. this.scroller = new Scroller({
  20. overflowX: 'hidden',
  21. overflowY: 'auto'
  22. });
  23. },
  24. // Generates the DayGrid object this view needs. Draws from this.dayGridClass
  25. instantiateDayGrid: function() {
  26. // generate a subclass on the fly with BasicView-specific behavior
  27. // TODO: cache this subclass
  28. var subclass = this.dayGridClass.extend(basicDayGridMethods);
  29. return new subclass(this);
  30. },
  31. // Computes the date range that will be rendered.
  32. buildRenderRange: function(currentUnzonedRange, currentRangeUnit, isRangeAllDay) {
  33. var renderUnzonedRange = View.prototype.buildRenderRange.apply(this, arguments); // an UnzonedRange
  34. var start = this.calendar.msToUtcMoment(renderUnzonedRange.startMs, isRangeAllDay);
  35. var end = this.calendar.msToUtcMoment(renderUnzonedRange.endMs, isRangeAllDay);
  36. // year and month views should be aligned with weeks. this is already done for week
  37. if (/^(year|month)$/.test(currentRangeUnit)) {
  38. start.startOf('week');
  39. // make end-of-week if not already
  40. if (end.weekday()) {
  41. end.add(1, 'week').startOf('week'); // exclusively move backwards
  42. }
  43. }
  44. return this.trimHiddenDays(new UnzonedRange(start, end));
  45. },
  46. handleDateProfileSet: function(dateProfile) {
  47. this.dayGrid.breakOnWeeks = /year|month|week/.test(dateProfile.currentRangeUnit);
  48. // needs breakOnWeeks. will populate dayGrid.rowCnt :(
  49. View.prototype.handleDateProfileSet.apply(this, arguments);
  50. this.dayNumbersVisible = this.dayGrid.rowCnt > 1; // TODO: make grid responsible
  51. if (this.opt('weekNumbers')) {
  52. if (this.opt('weekNumbersWithinDays')) {
  53. this.cellWeekNumbersVisible = true;
  54. this.colWeekNumbersVisible = false;
  55. }
  56. else {
  57. this.cellWeekNumbersVisible = false;
  58. this.colWeekNumbersVisible = true;
  59. };
  60. }
  61. this.dayGrid.numbersVisible =
  62. this.dayNumbersVisible ||
  63. this.cellWeekNumbersVisible ||
  64. this.colWeekNumbersVisible;
  65. this.dayGrid.isRigid = this.hasRigidRows();
  66. },
  67. renderSkeleton: function() {
  68. var dayGridContainerEl;
  69. var dayGridEl;
  70. this.el.addClass('fc-basic-view').html(this.renderSkeletonHtml());
  71. this.scroller.render();
  72. dayGridContainerEl = this.scroller.el.addClass('fc-day-grid-container');
  73. dayGridEl = $('<div class="fc-day-grid" />').appendTo(dayGridContainerEl);
  74. this.el.find('.fc-body > tr > td').append(dayGridContainerEl);
  75. this.dayGrid.setElement(dayGridEl);
  76. },
  77. unrenderSkeleton: function() {
  78. this.dayGrid.removeElement();
  79. this.scroller.destroy();
  80. },
  81. // Renders the view into `this.el`, which should already be assigned
  82. renderDates: function(dateProfile) {
  83. this.renderHead();
  84. },
  85. // render the day-of-week headers
  86. renderHead: function() {
  87. this.headContainerEl =
  88. this.el.find('.fc-head-container')
  89. .html(this.dayGrid.renderHeadHtml());
  90. this.headRowEl = this.headContainerEl.find('.fc-row');
  91. },
  92. // Builds the HTML skeleton for the view.
  93. // The day-grid component will render inside of a container defined by this HTML.
  94. renderSkeletonHtml: function() {
  95. var theme = this.calendar.theme;
  96. return '' +
  97. '<table class="' + theme.getClass('tableGrid') + '">' +
  98. '<thead class="fc-head">' +
  99. '<tr>' +
  100. '<td class="fc-head-container ' + theme.getClass('widgetHeader') + '"></td>' +
  101. '</tr>' +
  102. '</thead>' +
  103. '<tbody class="fc-body">' +
  104. '<tr>' +
  105. '<td class="' + theme.getClass('widgetContent') + '"></td>' +
  106. '</tr>' +
  107. '</tbody>' +
  108. '</table>';
  109. },
  110. // Generates an HTML attribute string for setting the width of the week number column, if it is known
  111. weekNumberStyleAttr: function() {
  112. if (this.weekNumberWidth !== null) {
  113. return 'style="width:' + this.weekNumberWidth + 'px"';
  114. }
  115. return '';
  116. },
  117. // Determines whether each row should have a constant height
  118. hasRigidRows: function() {
  119. var eventLimit = this.opt('eventLimit');
  120. return eventLimit && typeof eventLimit !== 'number';
  121. },
  122. /* Dimensions
  123. ------------------------------------------------------------------------------------------------------------------*/
  124. // Refreshes the horizontal dimensions of the view
  125. updateSize: function(totalHeight, isAuto, isResize) {
  126. View.prototype.updateSize.apply(this, arguments);
  127. if (this.colWeekNumbersVisible) {
  128. // Make sure all week number cells running down the side have the same width.
  129. // Record the width for cells created later.
  130. this.weekNumberWidth = matchCellWidths(
  131. this.el.find('.fc-week-number')
  132. );
  133. }
  134. var eventLimit = this.opt('eventLimit');
  135. var scrollerHeight;
  136. var scrollbarWidths;
  137. // reset all heights to be natural
  138. this.scroller.clear();
  139. uncompensateScroll(this.headRowEl);
  140. this.dayGrid.removeSegPopover(); // kill the "more" popover if displayed
  141. // is the event limit a constant level number?
  142. if (eventLimit && typeof eventLimit === 'number') {
  143. this.dayGrid.limitRows(eventLimit); // limit the levels first so the height can redistribute after
  144. }
  145. // distribute the height to the rows
  146. // (totalHeight is a "recommended" value if isAuto)
  147. scrollerHeight = this.computeScrollerHeight(totalHeight);
  148. this.setGridHeight(scrollerHeight, isAuto);
  149. // is the event limit dynamically calculated?
  150. if (eventLimit && typeof eventLimit !== 'number') {
  151. this.dayGrid.limitRows(eventLimit); // limit the levels after the grid's row heights have been set
  152. }
  153. if (!isAuto) { // should we force dimensions of the scroll container?
  154. this.scroller.setHeight(scrollerHeight);
  155. scrollbarWidths = this.scroller.getScrollbarWidths();
  156. if (scrollbarWidths.left || scrollbarWidths.right) { // using scrollbars?
  157. compensateScroll(this.headRowEl, scrollbarWidths);
  158. // doing the scrollbar compensation might have created text overflow which created more height. redo
  159. scrollerHeight = this.computeScrollerHeight(totalHeight);
  160. this.scroller.setHeight(scrollerHeight);
  161. }
  162. // guarantees the same scrollbar widths
  163. this.scroller.lockOverflow(scrollbarWidths);
  164. }
  165. },
  166. // given a desired total height of the view, returns what the height of the scroller should be
  167. computeScrollerHeight: function(totalHeight) {
  168. return totalHeight -
  169. subtractInnerElHeight(this.el, this.scroller.el); // everything that's NOT the scroller
  170. },
  171. // Sets the height of just the DayGrid component in this view
  172. setGridHeight: function(height, isAuto) {
  173. if (isAuto) {
  174. undistributeHeight(this.dayGrid.rowEls); // let the rows be their natural height with no expanding
  175. }
  176. else {
  177. distributeHeight(this.dayGrid.rowEls, height, true); // true = compensate for height-hogging rows
  178. }
  179. },
  180. /* Scroll
  181. ------------------------------------------------------------------------------------------------------------------*/
  182. computeInitialDateScroll: function() {
  183. return { top: 0 };
  184. },
  185. queryDateScroll: function() {
  186. return { top: this.scroller.getScrollTop() };
  187. },
  188. applyDateScroll: function(scroll) {
  189. if (scroll.top !== undefined) {
  190. this.scroller.setScrollTop(scroll.top);
  191. }
  192. }
  193. });
  194. // Methods that will customize the rendering behavior of the BasicView's dayGrid
  195. var basicDayGridMethods = {
  196. // Generates the HTML that will go before the day-of week header cells
  197. renderHeadIntroHtml: function() {
  198. var view = this.view;
  199. if (view.colWeekNumbersVisible) {
  200. return '' +
  201. '<th class="fc-week-number ' + view.calendar.theme.getClass('widgetHeader') + '" ' + view.weekNumberStyleAttr() + '>' +
  202. '<span>' + // needed for matchCellWidths
  203. htmlEscape(this.opt('weekNumberTitle')) +
  204. '</span>' +
  205. '</th>';
  206. }
  207. return '';
  208. },
  209. // Generates the HTML that will go before content-skeleton cells that display the day/week numbers
  210. renderNumberIntroHtml: function(row) {
  211. var view = this.view;
  212. var weekStart = this.getCellDate(row, 0);
  213. if (view.colWeekNumbersVisible) {
  214. return '' +
  215. '<td class="fc-week-number" ' + view.weekNumberStyleAttr() + '>' +
  216. view.buildGotoAnchorHtml( // aside from link, important for matchCellWidths
  217. { date: weekStart, type: 'week', forceOff: this.colCnt === 1 },
  218. weekStart.format('w') // inner HTML
  219. ) +
  220. '</td>';
  221. }
  222. return '';
  223. },
  224. // Generates the HTML that goes before the day bg cells for each day-row
  225. renderBgIntroHtml: function() {
  226. var view = this.view;
  227. if (view.colWeekNumbersVisible) {
  228. return '<td class="fc-week-number ' + view.calendar.theme.getClass('widgetContent') + '" ' +
  229. view.weekNumberStyleAttr() + '></td>';
  230. }
  231. return '';
  232. },
  233. // Generates the HTML that goes before every other type of row generated by DayGrid.
  234. // Affects helper-skeleton and highlight-skeleton rows.
  235. renderIntroHtml: function() {
  236. var view = this.view;
  237. if (view.colWeekNumbersVisible) {
  238. return '<td class="fc-week-number" ' + view.weekNumberStyleAttr() + '></td>';
  239. }
  240. return '';
  241. }
  242. };