Parcourir la source

formalize a simple OOP system

Adam Shaw il y a 11 ans
Parent
commit
3929b1db15

+ 4 - 3
lumbar.json

@@ -16,12 +16,10 @@
         "src/defaults.js",
         "src/main.js",
         "src/lang.js",
-        "src/Calendar.js",
-        "src/Header.js",
-        "src/EventManager.js",
         "src/util.js",
         "src/moment-ext.js",
         "src/date-formatting.js",
+        "src/common/Class.js",
         "src/common/Popover.js",
         "src/common/CoordMap.js",
         "src/common/DragListener.js",
@@ -35,6 +33,9 @@
         "src/common/TimeGrid.js",
         "src/common/TimeGrid.events.js",
         "src/common/View.js",
+        "src/Calendar.js",
+        "src/Header.js",
+        "src/EventManager.js",
         "src/basic/BasicView.js",
         "src/basic/MonthView.js",
         "src/basic/BasicWeekView.js",

+ 21 - 25
src/agenda/AgendaView.js

@@ -1,6 +1,4 @@
 
-fcViews.agenda = AgendaView;
-
 /* An abstract class for all agenda-related views. Displays one more columns with time slots running vertically.
 ----------------------------------------------------------------------------------------------------------------------*/
 // Is a manager for the TimeGrid subcomponent and possibly the DayGrid subcomponent (if allDaySlot is on).
@@ -18,29 +16,7 @@ setDefaults({
 
 var AGENDA_ALL_DAY_EVENT_LIMIT = 5;
 
-
-function AgendaView() {
-	View.apply(this, arguments); // call the super-constructor
-
-	this.timeGrid = new TimeGrid(this);
-
-	if (this.opt('allDaySlot')) { // should we display the "all-day" area?
-		this.dayGrid = new DayGrid(this); // the all-day subcomponent of this view
-
-		// the coordinate grid will be a combination of both subcomponents' grids
-		this.coordMap = new ComboCoordMap([
-			this.dayGrid.coordMap,
-			this.timeGrid.coordMap
-		]);
-	}
-	else {
-		this.coordMap = this.timeGrid.coordMap;
-	}
-}
-
-
-AgendaView.prototype = createObject(View.prototype); // define the super-class
-$.extend(AgendaView.prototype, {
+var AgendaView = fcViews.agenda = View.extend({
 
 	timeGrid: null, // the main time-grid subcomponent of this view
 	dayGrid: null, // the "all-day" subcomponent. if all-day is turned off, this will be null
@@ -54,6 +30,26 @@ $.extend(AgendaView.prototype, {
 	bottomRuleHeight: null,
 
 
+	constructor: function() {
+		View_constructor.apply(this, arguments); // call the super-constructor
+
+		this.timeGrid = new TimeGrid(this);
+
+		if (this.opt('allDaySlot')) { // should we display the "all-day" area?
+			this.dayGrid = new DayGrid(this); // the all-day subcomponent of this view
+
+			// the coordinate grid will be a combination of both subcomponents' grids
+			this.coordMap = new ComboCoordMap([
+				this.dayGrid.coordMap,
+				this.timeGrid.coordMap
+			]);
+		}
+		else {
+			this.coordMap = this.timeGrid.coordMap;
+		}
+	},
+
+
 	/* Rendering
 	------------------------------------------------------------------------------------------------------------------*/
 

+ 9 - 12
src/basic/BasicView.js

@@ -1,21 +1,10 @@
 
-fcViews.basic = BasicView;
-
 /* An abstract class for the "basic" views, as well as month view. Renders one or more rows of day cells.
 ----------------------------------------------------------------------------------------------------------------------*/
 // It is a manager for a DayGrid subcomponent, which does most of the heavy lifting.
 // It is responsible for managing width/height.
 
-function BasicView() {
-	View.apply(this, arguments); // call the super-constructor
-
-	this.dayGrid = new DayGrid(this);
-	this.coordMap = this.dayGrid.coordMap; // the view's date-to-cell mapping is identical to the subcomponent's
-}
-
-
-BasicView.prototype = createObject(View.prototype); // define the super-class
-$.extend(BasicView.prototype, {
+var BasicView = fcViews.basic = View.extend({
 
 	dayGrid: null, // the main subcomponent that does most of the heavy lifting
 
@@ -27,6 +16,14 @@ $.extend(BasicView.prototype, {
 	headRowEl: null, // the fake row element of the day-of-week header
 
 
+	constructor: function() {
+		View_constructor.apply(this, arguments); // call the super-constructor
+
+		this.dayGrid = new DayGrid(this);
+		this.coordMap = this.dayGrid.coordMap; // the view's date-to-cell mapping is identical to the subcomponent's
+	},
+
+
 	// Sets the display range and computes all necessary dates
 	setRange: function(range) {
 		View.prototype.setRange.call(this, range); // call the super-method

+ 1 - 10
src/basic/MonthView.js

@@ -6,16 +6,7 @@ setDefaults({
 	fixedWeekCount: true
 });
 
-fcViews.month = MonthView; // register the view
-
-function MonthView() {
-	BasicView.apply(this, arguments); // call the super-constructor
-}
-
-
-MonthView.prototype = createObject(BasicView.prototype); // define the super-class
-$.extend(MonthView.prototype, {
-
+var MonthView = fcViews.month = BasicView.extend({
 
 	computeRange: function(date) {
 		var rowCnt;

+ 40 - 0
src/common/Class.js

@@ -0,0 +1,40 @@
+
+fc.Class = Class; // export
+
+// class that all other classes will inherit from
+function Class() { }
+
+// called upon a class to create a subclass
+Class.extend = function(members) {
+	var superClass = this;
+	var subClass;
+
+	members = members || {};
+
+	// ensure a constructor for the subclass, forwarding all arguments to the super-constructor if it doesn't exist
+	if (hasOwnProp(members, 'constructor')) {
+		subClass = members.constructor;
+	}
+	if (typeof subClass !== 'function') {
+		subClass = members.constructor = function() {
+			superClass.apply(this, arguments);
+		};
+	}
+
+	// build the base prototype for the subclass, which is an new object chained to the superclass's prototype
+	subClass.prototype = createObject(superClass.prototype);
+
+	// copy each member variable/method onto the the subclass's prototype
+	copyOwnProps(members, subClass.prototype);
+
+	// copy over all class variables/methods to the subclass, such as `extend` and `mixin`
+	copyOwnProps(superClass, subClass);
+
+	return subClass;
+};
+
+// adds new member variables/methods to the class's prototype.
+// can be called with another class, or a plain object hash containing new members.
+Class.mixin = function(members) {
+	copyOwnProps(members.prototype || members, this.prototype);
+};

+ 13 - 13
src/common/CoordMap.js

@@ -13,12 +13,7 @@ Common interface:
 /* Coordinate map for a grid component
 ----------------------------------------------------------------------------------------------------------------------*/
 
-function GridCoordMap(grid) {
-	this.grid = grid;
-}
-
-
-GridCoordMap.prototype = {
+var GridCoordMap = Class.extend({
 
 	grid: null, // reference to the Grid
 	rowCoords: null, // array of {top,bottom} objects
@@ -31,6 +26,11 @@ GridCoordMap.prototype = {
 	maxY: null, // exclusive
 
 
+	constructor: function(grid) {
+		this.grid = grid;
+	},
+
+
 	// Queries the grid for the coordinates of all the cells
 	build: function() {
 		this.rowCoords = this.grid.computeRowCoords();
@@ -106,20 +106,20 @@ GridCoordMap.prototype = {
 		return true;
 	}
 
-};
+});
 
 
 /* Coordinate map that is a combination of multiple other coordinate maps
 ----------------------------------------------------------------------------------------------------------------------*/
 
-function ComboCoordMap(coordMaps) {
-	this.coordMaps = coordMaps;
-}
+var ComboCoordMap = Class.extend({
 
+	coordMaps: null, // an array of CoordMaps
 
-ComboCoordMap.prototype = {
 
-	coordMaps: null, // an array of CoordMaps
+	constructor: function(coordMaps) {
+		this.coordMaps = coordMaps;
+	},
 
 
 	// Builds all coordMaps
@@ -157,4 +157,4 @@ ComboCoordMap.prototype = {
 		}
 	}
 
-};
+});

+ 1 - 1
src/common/DayGrid.events.js

@@ -2,7 +2,7 @@
 /* Event-rendering methods for the DayGrid class
 ----------------------------------------------------------------------------------------------------------------------*/
 
-$.extend(DayGrid.prototype, {
+DayGrid.mixin({
 
 	rowStructs: null, // an array of objects, each holding information about a row's foreground event-rendering
 

+ 1 - 7
src/common/DayGrid.js

@@ -2,13 +2,7 @@
 /* A component that renders a grid of whole-days that runs horizontally. There can be multiple rows, one per week.
 ----------------------------------------------------------------------------------------------------------------------*/
 
-function DayGrid() {
-	Grid.apply(this, arguments); // call the super-constructor
-}
-
-
-DayGrid.prototype = createObject(Grid.prototype); // declare the super-class
-$.extend(DayGrid.prototype, {
+var DayGrid = Grid.extend({
 
 	numbersVisible: false, // should render a row for day/week numbers? set by outside view. TODO: make internal
 	bottomCoordPadding: 0, // hack for extending the hit area for the last row of the coordinate grid

+ 1 - 2
src/common/DayGrid.limit.js

@@ -3,8 +3,7 @@
 ----------------------------------------------------------------------------------------------------------------------*/
 // NOTE: all the segs being passed around in here are foreground segs
 
-$.extend(DayGrid.prototype, {
-
+DayGrid.mixin({
 
 	segPopover: null, // the Popover that holds events that can't fit in a cell. null when not visible
 	popoverSegs: null, // an array of segment objects that the segPopover holds. null when not visible

+ 8 - 8
src/common/DragListener.js

@@ -3,13 +3,7 @@
 ----------------------------------------------------------------------------------------------------------------------*/
 // TODO: very useful to have a handler that gets called upon cellOut OR when dragging stops (for cleanup)
 
-function DragListener(coordMap, options) {
-	this.coordMap = coordMap;
-	this.options = options || {};
-}
-
-
-DragListener.prototype = {
+var DragListener = Class.extend({
 
 	coordMap: null,
 	options: null,
@@ -43,6 +37,12 @@ DragListener.prototype = {
 	scrollIntervalMs: 50, // millisecond wait between scroll increment
 
 
+	constructor: function(coordMap, options) {
+		this.coordMap = coordMap;
+		this.options = options || {};
+	},
+
+
 	// Call this when the user does a mousedown. Will probably lead to startListening
 	mousedown: function(ev) {
 		if (isPrimaryMouseButton(ev)) {
@@ -401,7 +401,7 @@ DragListener.prototype = {
 		}
 	}
 
-};
+});
 
 
 // Returns `true` if the cells are identically equal. `false` otherwise.

+ 1 - 1
src/common/Grid.events.js

@@ -2,7 +2,7 @@
 /* Event-rendering and event-interaction methods for the abstract Grid class
 ----------------------------------------------------------------------------------------------------------------------*/
 
-$.extend(Grid.prototype, {
+Grid.mixin({
 
 	mousedOverSeg: null, // the segment object the user's mouse is over. null if over nothing
 	isDraggingSeg: false, // is a segment being dragged? boolean

+ 10 - 13
src/common/Grid.js

@@ -1,20 +1,8 @@
 
-fc.Grid = Grid;
-
 /* An abstract class comprised of a "grid" of cells that each represent a specific datetime
 ----------------------------------------------------------------------------------------------------------------------*/
 
-function Grid(view) {
-	RowRenderer.call(this, view); // call the super-constructor
-
-	this.coordMap = new GridCoordMap(this);
-	this.elsByFill = {};
-	this.documentDragStartProxy = $.proxy(this, 'documentDragStart');
-}
-
-
-Grid.prototype = createObject(RowRenderer.prototype); // declare the super-class
-$.extend(Grid.prototype, {
+var Grid = fc.Grid = RowRenderer.extend({
 
 	start: null, // the date of the first cell
 	end: null, // the date after the last cell
@@ -36,6 +24,15 @@ $.extend(Grid.prototype, {
 	displayEventEnd: null,
 
 
+	constructor: function() {
+		RowRenderer.apply(this, arguments); // call the super-constructor
+
+		this.coordMap = new GridCoordMap(this);
+		this.elsByFill = {};
+		this.documentDragStartProxy = $.proxy(this, 'documentDragStart');
+	},
+
+
 	// Renders the grid into the `el` element.
 	// Subclasses should override and call this super-method when done.
 	render: function() {

+ 8 - 9
src/common/MouseFollower.js

@@ -2,14 +2,7 @@
 /* Creates a clone of an element and lets it track the mouse as it moves
 ----------------------------------------------------------------------------------------------------------------------*/
 
-function MouseFollower(sourceEl, options) {
-	this.options = options = options || {};
-	this.sourceEl = sourceEl;
-	this.parentEl = options.parentEl ? $(options.parentEl) : sourceEl.parent(); // default to sourceEl's parent
-}
-
-
-MouseFollower.prototype = {
+var MouseFollower = Class.extend({
 
 	options: null,
 
@@ -35,6 +28,12 @@ MouseFollower.prototype = {
 	isHidden: false,
 	isAnimating: false, // doing the revert animation?
 
+	constructor: function(sourceEl, options) {
+		this.options = options = options || {};
+		this.sourceEl = sourceEl;
+		this.parentEl = options.parentEl ? $(options.parentEl) : sourceEl.parent(); // default to sourceEl's parent
+	},
+
 
 	// Causes the element to start following the mouse
 	start: function(ev) {
@@ -183,4 +182,4 @@ MouseFollower.prototype = {
 		}
 	}
 
-};
+});

+ 7 - 7
src/common/Popover.js

@@ -13,12 +13,7 @@ Options:
 	- hide (callback)
 */
 
-function Popover(options) {
-	this.options = options || {};
-}
-
-
-Popover.prototype = {
+var Popover = Class.extend({
 
 	isHidden: true,
 	options: null,
@@ -27,6 +22,11 @@ Popover.prototype = {
 	margin: 10, // the space required between the popover and the edges of the scroll container
 
 
+	constructor: function(options) {
+		this.options = options || {};
+	},
+
+
 	// Shows the popover on the specified position. Renders it if not already
 	show: function() {
 		if (this.isHidden) {
@@ -164,4 +164,4 @@ Popover.prototype = {
 		}
 	}
 
-};
+});

+ 8 - 8
src/common/RowRenderer.js

@@ -4,19 +4,19 @@
 // It leverages methods of the subclass and the View to determine custom rendering behavior for each row "type"
 // (such as highlight rows, day rows, helper rows, etc).
 
-function RowRenderer(view) {
-	this.view = view;
-	this.isRTL = view.opt('isRTL');
-}
-
-
-RowRenderer.prototype = {
+var RowRenderer = Class.extend({
 
 	view: null, // a View object
 	isRTL: null, // shortcut to the view's isRTL option
 	cellHtml: '<td/>', // plain default HTML used for a cell when no other is available
 
 
+	constructor: function(view) {
+		this.view = view;
+		this.isRTL = view.opt('isRTL');
+	},
+
+
 	// Renders the HTML for a row, leveraging custom cell-HTML-renderers based on the `rowType`.
 	// Also applies the "intro" and "outro" cells, which are specified by the subclass and views.
 	// `row` is an optional row number.
@@ -99,4 +99,4 @@ RowRenderer.prototype = {
 		};
 	}
 
-};
+});

+ 1 - 1
src/common/TimeGrid.events.js

@@ -2,7 +2,7 @@
 /* Event-rendering methods for the TimeGrid class
 ----------------------------------------------------------------------------------------------------------------------*/
 
-$.extend(TimeGrid.prototype, {
+TimeGrid.mixin({
 
 	eventSkeletonEl: null, // has cells with event-containers, which contain absolutely positioned event elements
 

+ 7 - 8
src/common/TimeGrid.js

@@ -2,14 +2,7 @@
 /* A component that renders one or more columns of vertical time slots
 ----------------------------------------------------------------------------------------------------------------------*/
 
-function TimeGrid(view) {
-	Grid.call(this, view); // call the super-constructor
-	this.processOptions();
-}
-
-
-TimeGrid.prototype = createObject(Grid.prototype); // define the super-class
-$.extend(TimeGrid.prototype, {
+var TimeGrid = Grid.extend({
 
 	slotDuration: null, // duration of a "slot", a distinct time segment on given day, visualized by lines
 	snapDuration: null, // granularity of time for dragging and selecting
@@ -29,6 +22,12 @@ $.extend(TimeGrid.prototype, {
 	businessHourSegs: null,
 
 
+	constructor: function() {
+		Grid.apply(this, arguments); // call the super-constructor
+		this.processOptions();
+	},
+
+
 	// Renders the time grid into `this.el`, which should already be assigned.
 	// Relies on the view's colCnt. In the future, this component should probably be self-sufficient.
 	render: function() {

+ 6 - 5
src/common/View.js

@@ -1,11 +1,9 @@
 
-fc.View = View;
-
 /* An abstract class from which other views inherit from
 ----------------------------------------------------------------------------------------------------------------------*/
 // Newer methods should be written as prototype methods, not in the monster `View` function at the bottom.
 
-View.prototype = {
+var View = fc.View = Class.extend({
 
 	type: null, // subclass' view name (string)
 	name: null, // deprecated. use `type` instead
@@ -41,6 +39,9 @@ View.prototype = {
 	documentMousedownProxy: null,
 
 
+	constructor: View_constructor, // will call init
+
+
 	// Serves as a "constructor" to suppliment the monster `View` constructor below
 	init: function() {
 		var tm = this.opt('theme') ? 'ui' : 'fc';
@@ -515,12 +516,12 @@ View.prototype = {
 		}
 	}
 
-};
+});
 
 
 // We are mixing JavaScript OOP design patterns here by putting methods and member variables in the closed scope of the
 // constructor. Going forward, methods should be part of the prototype.
-function View(calendar, viewOptions, viewType) {
+function View_constructor(calendar, viewOptions, viewType) {
 	var t = this;
 	
 	// exports

+ 16 - 0
src/util.js

@@ -359,6 +359,8 @@ function isTimeString(str) {
 /* General Utilities
 ----------------------------------------------------------------------------------------------------------------------*/
 
+var hasOwnPropMethod = {}.hasOwnProperty;
+
 
 // Create an object that has the given prototype. Just like Object.create
 function createObject(proto) {
@@ -368,6 +370,20 @@ function createObject(proto) {
 }
 
 
+function copyOwnProps(src, dest) {
+	for (var name in src) {
+		if (hasOwnProp(src, name)) {
+			dest[name] = src[name];
+		}
+	}
+}
+
+
+function hasOwnProp(obj, name) {
+	return hasOwnPropMethod.call(obj, name);
+}
+
+
 // Is the given value a non-object non-function value?
 function isAtomic(val) {
 	return /undefined|null|boolean|number|string/.test($.type(val));