BasicView.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425
  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. initialize: function() {
  16. this.dayGrid = this.instantiateDayGrid();
  17. this.scroller = new Scroller({
  18. overflowX: 'hidden',
  19. overflowY: 'auto'
  20. });
  21. },
  22. // Generates the DayGrid object this view needs. Draws from this.dayGridClass
  23. instantiateDayGrid: function() {
  24. // generate a subclass on the fly with BasicView-specific behavior
  25. // TODO: cache this subclass
  26. var subclass = this.dayGridClass.extend(basicDayGridMethods);
  27. return new subclass(this);
  28. },
  29. // Computes the date range that will be rendered.
  30. buildRenderRange: function(currentRange, currentRangeUnit) {
  31. var renderRange = View.prototype.buildRenderRange.apply(this, arguments);
  32. // year and month views should be aligned with weeks. this is already done for week
  33. if (/^(year|month)$/.test(currentRangeUnit)) {
  34. renderRange.start.startOf('week');
  35. // make end-of-week if not already
  36. if (renderRange.end.weekday()) {
  37. renderRange.end.add(1, 'week').startOf('week'); // exclusively move backwards
  38. }
  39. }
  40. return this.trimHiddenDays(renderRange);
  41. },
  42. // Renders the view into `this.el`, which should already be assigned
  43. renderDates: function() {
  44. this.dayGrid.breakOnWeeks = /year|month|week/.test(this.currentRangeUnit); // do before Grid::setRange
  45. this.dayGrid.setRange(this.renderRange);
  46. this.dayNumbersVisible = this.dayGrid.rowCnt > 1; // TODO: make grid responsible
  47. if (this.opt('weekNumbers')) {
  48. if (this.opt('weekNumbersWithinDays')) {
  49. this.cellWeekNumbersVisible = true;
  50. this.colWeekNumbersVisible = false;
  51. }
  52. else {
  53. this.cellWeekNumbersVisible = false;
  54. this.colWeekNumbersVisible = true;
  55. };
  56. }
  57. this.dayGrid.numbersVisible = this.dayNumbersVisible ||
  58. this.cellWeekNumbersVisible || this.colWeekNumbersVisible;
  59. this.el.addClass('fc-basic-view').html(this.renderSkeletonHtml());
  60. this.renderHead();
  61. this.scroller.render();
  62. var dayGridContainerEl = this.scroller.el.addClass('fc-day-grid-container');
  63. var dayGridEl = $('<div class="fc-day-grid" />').appendTo(dayGridContainerEl);
  64. this.el.find('.fc-body > tr > td').append(dayGridContainerEl);
  65. this.dayGrid.setElement(dayGridEl);
  66. this.dayGrid.renderDates(this.hasRigidRows());
  67. },
  68. // render the day-of-week headers
  69. renderHead: function() {
  70. this.headContainerEl =
  71. this.el.find('.fc-head-container')
  72. .html(this.dayGrid.renderHeadHtml());
  73. this.headRowEl = this.headContainerEl.find('.fc-row');
  74. },
  75. // Unrenders the content of the view. Since we haven't separated skeleton rendering from date rendering,
  76. // always completely kill the dayGrid's rendering.
  77. unrenderDates: function() {
  78. this.dayGrid.unrenderDates();
  79. this.dayGrid.removeElement();
  80. this.scroller.destroy();
  81. },
  82. renderBusinessHours: function() {
  83. this.dayGrid.renderBusinessHours();
  84. },
  85. unrenderBusinessHours: function() {
  86. this.dayGrid.unrenderBusinessHours();
  87. },
  88. // Builds the HTML skeleton for the view.
  89. // The day-grid component will render inside of a container defined by this HTML.
  90. renderSkeletonHtml: function() {
  91. return '' +
  92. '<table class="' + this.calendar.theme.getClass('tableGrid') + '">' +
  93. '<thead class="fc-head">' +
  94. '<tr>' +
  95. '<td class="fc-head-container ' + this.calendar.theme.getClass('widgetHeader') + '"></td>' +
  96. '</tr>' +
  97. '</thead>' +
  98. '<tbody class="fc-body">' +
  99. '<tr>' +
  100. '<td class="' + this.calendar.theme.getClass('widgetContent') + '"></td>' +
  101. '</tr>' +
  102. '</tbody>' +
  103. '</table>';
  104. },
  105. // Generates an HTML attribute string for setting the width of the week number column, if it is known
  106. weekNumberStyleAttr: function() {
  107. if (this.weekNumberWidth !== null) {
  108. return 'style="width:' + this.weekNumberWidth + 'px"';
  109. }
  110. return '';
  111. },
  112. // Determines whether each row should have a constant height
  113. hasRigidRows: function() {
  114. var eventLimit = this.opt('eventLimit');
  115. return eventLimit && typeof eventLimit !== 'number';
  116. },
  117. /* Dimensions
  118. ------------------------------------------------------------------------------------------------------------------*/
  119. // Refreshes the horizontal dimensions of the view
  120. updateWidth: function() {
  121. if (this.colWeekNumbersVisible) {
  122. // Make sure all week number cells running down the side have the same width.
  123. // Record the width for cells created later.
  124. this.weekNumberWidth = matchCellWidths(
  125. this.el.find('.fc-week-number')
  126. );
  127. }
  128. },
  129. // Adjusts the vertical dimensions of the view to the specified values
  130. setHeight: function(totalHeight, isAuto) {
  131. var eventLimit = this.opt('eventLimit');
  132. var scrollerHeight;
  133. var scrollbarWidths;
  134. // reset all heights to be natural
  135. this.scroller.clear();
  136. uncompensateScroll(this.headRowEl);
  137. this.dayGrid.removeSegPopover(); // kill the "more" popover if displayed
  138. // is the event limit a constant level number?
  139. if (eventLimit && typeof eventLimit === 'number') {
  140. this.dayGrid.limitRows(eventLimit); // limit the levels first so the height can redistribute after
  141. }
  142. // distribute the height to the rows
  143. // (totalHeight is a "recommended" value if isAuto)
  144. scrollerHeight = this.computeScrollerHeight(totalHeight);
  145. this.setGridHeight(scrollerHeight, isAuto);
  146. // is the event limit dynamically calculated?
  147. if (eventLimit && typeof eventLimit !== 'number') {
  148. this.dayGrid.limitRows(eventLimit); // limit the levels after the grid's row heights have been set
  149. }
  150. if (!isAuto) { // should we force dimensions of the scroll container?
  151. this.scroller.setHeight(scrollerHeight);
  152. scrollbarWidths = this.scroller.getScrollbarWidths();
  153. if (scrollbarWidths.left || scrollbarWidths.right) { // using scrollbars?
  154. compensateScroll(this.headRowEl, scrollbarWidths);
  155. // doing the scrollbar compensation might have created text overflow which created more height. redo
  156. scrollerHeight = this.computeScrollerHeight(totalHeight);
  157. this.scroller.setHeight(scrollerHeight);
  158. }
  159. // guarantees the same scrollbar widths
  160. this.scroller.lockOverflow(scrollbarWidths);
  161. }
  162. },
  163. // given a desired total height of the view, returns what the height of the scroller should be
  164. computeScrollerHeight: function(totalHeight) {
  165. return totalHeight -
  166. subtractInnerElHeight(this.el, this.scroller.el); // everything that's NOT the scroller
  167. },
  168. // Sets the height of just the DayGrid component in this view
  169. setGridHeight: function(height, isAuto) {
  170. if (isAuto) {
  171. undistributeHeight(this.dayGrid.rowEls); // let the rows be their natural height with no expanding
  172. }
  173. else {
  174. distributeHeight(this.dayGrid.rowEls, height, true); // true = compensate for height-hogging rows
  175. }
  176. },
  177. /* Scroll
  178. ------------------------------------------------------------------------------------------------------------------*/
  179. computeInitialDateScroll: function() {
  180. return { top: 0 };
  181. },
  182. queryDateScroll: function() {
  183. return { top: this.scroller.getScrollTop() };
  184. },
  185. applyDateScroll: function(scroll) {
  186. if (scroll.top !== undefined) {
  187. this.scroller.setScrollTop(scroll.top);
  188. }
  189. },
  190. /* Hit Areas
  191. ------------------------------------------------------------------------------------------------------------------*/
  192. // forward all hit-related method calls to dayGrid
  193. hitsNeeded: function() {
  194. this.dayGrid.hitsNeeded();
  195. },
  196. hitsNotNeeded: function() {
  197. this.dayGrid.hitsNotNeeded();
  198. },
  199. prepareHits: function() {
  200. this.dayGrid.prepareHits();
  201. },
  202. releaseHits: function() {
  203. this.dayGrid.releaseHits();
  204. },
  205. queryHit: function(left, top) {
  206. return this.dayGrid.queryHit(left, top);
  207. },
  208. getHitSpan: function(hit) {
  209. return this.dayGrid.getHitSpan(hit);
  210. },
  211. getHitEl: function(hit) {
  212. return this.dayGrid.getHitEl(hit);
  213. },
  214. /* Events
  215. ------------------------------------------------------------------------------------------------------------------*/
  216. // Renders the given events onto the view and populates the segments array
  217. renderEvents: function(events) {
  218. this.dayGrid.renderEvents(events);
  219. this.updateHeight(); // must compensate for events that overflow the row
  220. },
  221. // Retrieves all segment objects that are rendered in the view
  222. getEventSegs: function() {
  223. return this.dayGrid.getEventSegs();
  224. },
  225. // Unrenders all event elements and clears internal segment data
  226. unrenderEvents: function() {
  227. this.dayGrid.unrenderEvents();
  228. // we DON'T need to call updateHeight() because
  229. // a renderEvents() call always happens after this, which will eventually call updateHeight()
  230. },
  231. /* Dragging (for both events and external elements)
  232. ------------------------------------------------------------------------------------------------------------------*/
  233. // A returned value of `true` signals that a mock "helper" event has been rendered.
  234. renderDrag: function(dropLocation, seg) {
  235. return this.dayGrid.renderDrag(dropLocation, seg);
  236. },
  237. unrenderDrag: function() {
  238. this.dayGrid.unrenderDrag();
  239. },
  240. /* Selection
  241. ------------------------------------------------------------------------------------------------------------------*/
  242. // Renders a visual indication of a selection
  243. renderSelection: function(span) {
  244. this.dayGrid.renderSelection(span);
  245. },
  246. // Unrenders a visual indications of a selection
  247. unrenderSelection: function() {
  248. this.dayGrid.unrenderSelection();
  249. }
  250. });
  251. // Methods that will customize the rendering behavior of the BasicView's dayGrid
  252. var basicDayGridMethods = {
  253. // Generates the HTML that will go before the day-of week header cells
  254. renderHeadIntroHtml: function() {
  255. var view = this.view;
  256. if (view.colWeekNumbersVisible) {
  257. return '' +
  258. '<th class="fc-week-number ' + view.calendar.theme.getClass('widgetHeader') + '" ' + view.weekNumberStyleAttr() + '>' +
  259. '<span>' + // needed for matchCellWidths
  260. htmlEscape(view.opt('weekNumberTitle')) +
  261. '</span>' +
  262. '</th>';
  263. }
  264. return '';
  265. },
  266. // Generates the HTML that will go before content-skeleton cells that display the day/week numbers
  267. renderNumberIntroHtml: function(row) {
  268. var view = this.view;
  269. var weekStart = this.getCellDate(row, 0);
  270. if (view.colWeekNumbersVisible) {
  271. return '' +
  272. '<td class="fc-week-number" ' + view.weekNumberStyleAttr() + '>' +
  273. view.buildGotoAnchorHtml( // aside from link, important for matchCellWidths
  274. { date: weekStart, type: 'week', forceOff: this.colCnt === 1 },
  275. weekStart.format('w') // inner HTML
  276. ) +
  277. '</td>';
  278. }
  279. return '';
  280. },
  281. // Generates the HTML that goes before the day bg cells for each day-row
  282. renderBgIntroHtml: function() {
  283. var view = this.view;
  284. if (view.colWeekNumbersVisible) {
  285. return '<td class="fc-week-number ' + view.calendar.theme.getClass('widgetContent') + '" ' +
  286. view.weekNumberStyleAttr() + '></td>';
  287. }
  288. return '';
  289. },
  290. // Generates the HTML that goes before every other type of row generated by DayGrid.
  291. // Affects helper-skeleton and highlight-skeleton rows.
  292. renderIntroHtml: function() {
  293. var view = this.view;
  294. if (view.colWeekNumbersVisible) {
  295. return '<td class="fc-week-number" ' + view.weekNumberStyleAttr() + '></td>';
  296. }
  297. return '';
  298. }
  299. };