Selaa lähdekoodia

simple list view

Adam Shaw 9 vuotta sitten
vanhempi
sitoutus
677261c355
5 muutettua tiedostoa jossa 397 lisäystä ja 4 poistoa
  1. 4 1
      src.json
  2. 10 3
      src/common/common.css
  3. 232 0
      src/list/ListView.js
  4. 44 0
      src/list/config.js
  5. 107 0
      src/list/list.css

+ 4 - 1
src.json

@@ -36,13 +36,16 @@
     "basic/config.js",
     "agenda/AgendaView.js",
     "agenda/config.js",
+    "list/ListView.js",
+    "list/config.js",
     "outro.js"
   ],
   "fullcalendar.css": [
     "common/common.css",
     "main.css",
     "basic/basic.css",
-    "agenda/agenda.css"
+    "agenda/agenda.css",
+    "list/list.css"
   ],
   "fullcalendar.print.css": [
     "common/print.css"

+ 10 - 3
src/common/common.css

@@ -29,7 +29,9 @@ body .fc { /* extra precedence to overcome jqui */
 .fc-unthemed .fc-divider,
 .fc-unthemed .fc-row,
 .fc-unthemed .fc-content, /* for gutter border */
-.fc-unthemed .fc-popover {
+.fc-unthemed .fc-popover,
+.fc-unthemed .fc-list-view,
+.fc-unthemed .fc-list-heading td {
 	border-color: #ddd;
 }
 
@@ -38,7 +40,8 @@ body .fc { /* extra precedence to overcome jqui */
 }
 
 .fc-unthemed .fc-divider,
-.fc-unthemed .fc-popover .fc-header {
+.fc-unthemed .fc-popover .fc-header,
+.fc-unthemed .fc-list-heading td {
 	background: #eee;
 }
 
@@ -509,10 +512,14 @@ temporary rendered events).
 	line-height: 1.3;
 	border-radius: 3px;
 	border: 1px solid #3a87ad; /* default BORDER color */
-	background-color: #3a87ad; /* default BACKGROUND color */
 	font-weight: normal; /* undo jqui's ui-widget-header bold */
 }
 
+.fc-event,
+.fc-event-dot {
+	background-color: #3a87ad; /* default BACKGROUND color */
+}
+
 /* overpower some of bootstrap's and jqui's styles on <a> tags */
 .fc-event,
 .fc-event:hover,

+ 232 - 0
src/list/ListView.js

@@ -0,0 +1,232 @@
+
+/*
+Responsible for the scroller, and forwarding event-related actions into the "grid"
+*/
+var ListView = View.extend({
+
+	grid: null,
+	scroller: null,
+
+	initialize: function() {
+		this.grid = new ListViewGrid(this);
+		this.scroller = new Scroller({
+			overflowX: 'hidden',
+			overflowY: 'auto'
+		});
+	},
+
+	setRange: function(range) {
+		View.prototype.setRange.call(this, range); // super
+
+		this.grid.setRange(range); // needs to process range-related options
+	},
+
+	renderSkeleton: function() {
+		this.el.addClass(
+			'fc-list-view ' +
+			this.widgetContentClass
+		);
+
+		this.scroller.render();
+		this.scroller.el.appendTo(this.el);
+
+		this.grid.setElement(this.scroller.scrollEl);
+	},
+
+	unrenderSkeleton: function() {
+		this.scroller.destroy(); // will remove the Grid too
+	},
+
+	setHeight: function(totalHeight, isAuto) {
+		this.scroller.setHeight(this.computeScrollerHeight(totalHeight));
+	},
+
+	computeScrollerHeight: function(totalHeight) {
+		return totalHeight -
+			subtractInnerElHeight(this.el, this.scroller.el); // everything that's NOT the scroller
+	},
+
+	renderEvents: function(events) {
+		this.grid.renderEvents(events);
+	},
+
+	unrenderEvents: function() {
+		this.grid.unrenderEvents();
+	},
+
+	isEventResizable: function(event) {
+		return false;
+	},
+
+	isEventDraggable: function(event) {
+		return false;
+	}
+
+});
+
+/*
+Responsible for event rendering and user-interaction.
+Its "el" is the inner-content of the above view's scroller.
+*/
+var ListViewGrid = Grid.extend({
+
+	segSelector: '.fc-list-item', // which elements accept event actions
+	hasDayInteractions: false, // no day selection or day clicking
+
+	// slices by day
+	spanToSegs: function(span) {
+		var view = this.view;
+		var dayStart = view.start.clone();
+		var dayEnd;
+		var seg;
+		var segs = [];
+
+		while (dayStart < view.end) {
+			dayEnd = dayStart.clone().add(1, 'day');
+			seg = intersectRanges(span, {
+				start: dayStart,
+				end: dayEnd
+			});
+			if (seg) {
+				segs.push(seg);
+			}
+			dayStart = dayEnd;
+		}
+
+		return segs;
+	},
+
+	// like "4:00am"
+	computeEventTimeFormat: function() {
+		return this.view.opt('mediumTimeFormat');
+	},
+
+	// for events with a url, the whole <tr> should be clickable,
+	// but it's impossible to wrap with an <a> tag. simulate this.
+	handleSegClick: function(seg, ev) {
+		var url;
+
+		Grid.prototype.handleSegClick.apply(this, arguments); // super. might prevent the default action
+
+		// not clicking on or within an <a> with an href
+		if (!$(ev.target).closest('a[href]').length) {
+			url = seg.event.url;
+			if (url && !ev.isDefaultPrevented()) { // jsEvent not cancelled in handler
+				window.location.href = url; // simulate link click
+			}
+		}
+	},
+
+	// returns list of foreground segs that were actually rendered
+	renderFgSegs: function(segs) {
+		segs = this.renderFgSegEls(segs); // might filter away hidden events
+
+		if (!segs.length) {
+			this.renderEmptyMessage();
+			return segs;
+		}
+		else {
+			return this.renderSegList(segs);
+		}
+	},
+
+	renderEmptyMessage: function() {
+		this.el.html(
+			'<div class="fc-list-empty-wrap2">' + // TODO: try less wraps
+			'<div class="fc-list-empty-wrap1">' +
+			'<div class="fc-list-empty">' +
+				htmlEscape(this.view.opt('noEventsMessage')) +
+			'</div>' +
+			'</div>' +
+			'</div>'
+		);
+	},
+
+	// render the event segments in the view. returns the mutated array.
+	renderSegList: function(segs) {
+		var tableEl = $('<table class="fc-list-table"><tbody/></table>');
+		var tbodyEl = tableEl.find('tbody');
+		var i, seg;
+		var dayDate;
+
+		this.sortEventSegs(segs);
+
+		for (i = 0; i < segs.length; i++) {
+			seg = segs[i];
+
+			// append a day header
+			if (!dayDate || !seg.start.isSame(dayDate, 'day')) {
+				dayDate = seg.start.clone().stripTime();
+				tbodyEl.append(this.dayHeaderHtml(dayDate));
+			}
+
+			tbodyEl.append(seg.el); // append event row
+		}
+
+		this.el.empty().append(tableEl);
+
+		return segs; // return the sorted list
+	},
+
+	// generates the HTML for the day headers that live amongst the event rows
+	dayHeaderHtml: function(dayDate) {
+		var mainFormat = this.view.opt('listDayFormat');
+		var altFormat = this.view.opt('listDayAltFormat');
+
+		return '<tr class="fc-list-heading">' +
+			'<td class="' + this.view.widgetHeaderClass + '" colspan="3">' +
+				(mainFormat ?
+					'<span class="fc-list-heading-main">' +
+						htmlEscape(dayDate.format(mainFormat)) +
+					'</span>' :
+					'') +
+				(altFormat ?
+					'<span class="fc-list-heading-alt">' +
+						htmlEscape(dayDate.format(altFormat)) +
+					'</span>' :
+					'')
+			'</td>' +
+		'</tr>';
+	},
+
+	// generates the HTML for a single event row
+	fgSegHtml: function(seg) {
+		var view = this.view;
+		var classes = [ 'fc-list-item' ].concat(this.getSegCustomClasses(seg));
+		var bgColor = this.getSegBackgroundColor(seg);
+		var url = seg.event.url;
+		var timeHtml;
+
+		if (!seg.start.hasTime()) {
+			timeHtml = view.getAllDayHtml();
+		}
+		else {
+			timeHtml = htmlEscape(this.getEventTimeText(seg));
+		}
+
+		if (url) {
+			classes.push('fc-has-url');
+		}
+
+		return '<tr class="' + classes.join(' ') + '">' +
+			'<td class="fc-list-item-marker ' + view.widgetContentClass + '">' +
+				'<span class="fc-event-dot"' +
+				(bgColor ?
+					' style="background-color:' + bgColor + '"' :
+					'') +
+				'></span>' +
+			'</td>' +
+			(view.opt('listTime') ?
+				'<td class="fc-list-item-time ' + view.widgetContentClass + '">' +
+					timeHtml +
+				'</td>' :
+				'') +
+			'<td class="fc-list-item-title ' + view.widgetContentClass + '">' +
+				'<a' + (url ? ' href="' + htmlEscape(url) + '"' : '') + '>' +
+					htmlEscape(seg.event.title) +
+				'</a>' +
+			'</td>' +
+		'</tr>';
+	}
+
+});

+ 44 - 0
src/list/config.js

@@ -0,0 +1,44 @@
+
+fcViews.list = {
+	'class': ListView,
+	buttonTextKey: 'list', // what to lookup in lang files
+	defaults: {
+		buttonText: 'list', // text to display for English
+		listTime: true, // show the time column?
+		listDayFormat: 'LL', // like "January 1, 2016"
+		noEventsMessage: 'No events to display'
+	}
+};
+
+fcViews.listDay = {
+	type: 'list',
+	duration: { days: 1 },
+	defaults: {
+		listDayFormat: 'dddd' // day-of-week is all we need. full date is probably in header
+	}
+};
+
+fcViews.listWeek = {
+	type: 'list',
+	duration: { weeks: 1 },
+	defaults: {
+		listDayFormat: 'dddd', // day-of-week is more important
+		listDayAltFormat: 'LL'
+	}
+};
+
+fcViews.listMonth = {
+	type: 'list',
+	duration: { month: 1 },
+	defaults: {
+		listDayAltFormat: 'dddd' // day-of-week is nice-to-have
+	}
+};
+
+fcViews.listYear = {
+	type: 'list',
+	duration: { year: 1 },
+	defaults: {
+		listDayAltFormat: 'dddd' // day-of-week is nice-to-have
+	}
+};

+ 107 - 0
src/list/list.css

@@ -0,0 +1,107 @@
+
+/* List View
+--------------------------------------------------------------------------------------------------*/
+
+/* possibly reusable */
+
+.fc-event-dot {
+	display: inline-block;
+	width: 10px;
+	height: 10px;
+	border-radius: 5px;
+}
+
+/* view wrapper */
+
+.fc-rtl .fc-list-view {
+	direction: rtl; /* unlike core views, leverage browser RTL */
+}
+
+.fc-list-view {
+	border-width: 1px;
+	border-style: solid;
+}
+
+/* table resets */
+
+.fc .fc-list-table {
+	table-layout: auto; /* for shrinkwrapping cell content */
+}
+
+.fc-list-table td {
+	border-width: 1px 0 0;
+	padding: 8px 14px;
+}
+
+.fc-list-table tr:first-child td {
+	border-top-width: 0;
+}
+
+/* day headings with the list */
+
+.fc-list-heading {
+	border-bottom-width: 1px;
+}
+
+.fc-list-heading td {
+	font-weight: bold;
+}
+
+.fc-ltr .fc-list-heading-main { float: left; }
+.fc-ltr .fc-list-heading-alt { float: right; }
+
+.fc-rtl .fc-list-heading-main { float: right; }
+.fc-rtl .fc-list-heading-alt { float: left; }
+
+/* event list items */
+
+.fc-list-item.fc-has-url {
+	cursor: pointer; /* whole row will be clickable */
+}
+
+.fc-list-item:hover td {
+	background-color: #f5f5f5;
+}
+
+.fc-list-item-marker,
+.fc-list-item-time {
+	white-space: nowrap;
+	width: 1px;
+}
+
+.fc-list-item-title a {
+	/* every event title cell has an <a> tag */
+	text-decoration: none;
+	color: inherit;
+}
+
+.fc-list-item-title a[href]:hover {
+	/* hover effect only on titles with hrefs */
+	text-decoration: underline;
+}
+
+/* message when no events */
+
+.fc-list-empty-wrap2 {
+	position: absolute;
+	top: 0;
+	left: 0;
+	right: 0;
+	bottom: 0;
+}
+
+.fc-list-empty-wrap1 {
+	width: 100%;
+	height: 100%;
+	display: table;
+}
+
+.fc-list-empty {
+	display: table-cell;
+	vertical-align: middle;
+	text-align: center;
+}
+
+.fc-unthemed .fc-list-empty { /* theme will provide own background */
+	background-color: #eee;
+}