|
|
@@ -1,13 +1,35 @@
|
|
|
|
|
|
var ChronoComponent = Model.extend({
|
|
|
|
|
|
+ el: null, // the view's containing element. set by Calendar(?)
|
|
|
+
|
|
|
children: null,
|
|
|
+ isRTL: false, // frequently accessed options
|
|
|
+ nextDayThreshold: null, // "
|
|
|
|
|
|
- el: null, // the view's containing element. set by Calendar(?)
|
|
|
+ dateClickingClass: null,
|
|
|
+ dateSelectingClass: null,
|
|
|
+ eventPointingClass: null,
|
|
|
+ eventDraggingClass: null,
|
|
|
+ eventResizingClass: null,
|
|
|
+ externalDroppingClass: null,
|
|
|
+
|
|
|
+ dateClicking: null,
|
|
|
+ dateSelecting: null,
|
|
|
+ eventPointing: null,
|
|
|
+ eventDragging: null,
|
|
|
+ eventResizing: null,
|
|
|
+ externalDropping: null,
|
|
|
|
|
|
- // frequently accessed options
|
|
|
- isRTL: false,
|
|
|
- nextDayThreshold: null,
|
|
|
+ // self-config, overridable by subclasses
|
|
|
+ segSelector: '.fc-event-container > *', // what constitutes an event element?
|
|
|
+
|
|
|
+ // if defined, holds the unit identified (ex: "year" or "month") that determines the level of granularity
|
|
|
+ // of the date areas. if not defined, assumes to be day and time granularity.
|
|
|
+ // TODO: port isTimeScale into same system?
|
|
|
+ largeUnit: null,
|
|
|
+
|
|
|
+ hitsNeededDepth: 0, // necessary because multiple callers might need the same hits
|
|
|
|
|
|
|
|
|
constructor: function() {
|
|
|
@@ -56,7 +78,36 @@ var ChronoComponent = Model.extend({
|
|
|
// and renders all the non-date-related content inside.
|
|
|
setElement: function(el) {
|
|
|
this.el = el;
|
|
|
+
|
|
|
+ if (this.dateSelectingClass) {
|
|
|
+ (this.dateClicking = new this.dateClickingClass(this))
|
|
|
+ .bindToEl(this.el);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this.dateSelectingClass) {
|
|
|
+ (this.dateSelecting = new this.dateSelectingClass(this))
|
|
|
+ .bindToEl(this.el);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this.eventPointingClass) {
|
|
|
+ this.eventPointing = new this.eventPointingClass(this);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this.eventDraggingClass) {
|
|
|
+ this.eventDragging = new this.eventDraggingClass(this);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this.eventResizingClass) {
|
|
|
+ this.eventResizing = new this.eventResizingClass(this);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this.externalDroppingClass) {
|
|
|
+ this.externalDropping = new this.externalDroppingClass(this);
|
|
|
+ }
|
|
|
+
|
|
|
this.bindGlobalHandlers();
|
|
|
+ this.bindAllSegHandlersToEl(this.el);
|
|
|
+
|
|
|
this.renderSkeleton();
|
|
|
},
|
|
|
|
|
|
@@ -64,6 +115,7 @@ var ChronoComponent = Model.extend({
|
|
|
// Removes the view's container element from the DOM, clearing any content beforehand.
|
|
|
// Undoes any other DOM-related attachments.
|
|
|
removeElement: function() {
|
|
|
+ this.endInteractions();
|
|
|
this.unrenderSkeleton();
|
|
|
this.unbindGlobalHandlers();
|
|
|
|
|
|
@@ -75,10 +127,16 @@ var ChronoComponent = Model.extend({
|
|
|
|
|
|
|
|
|
bindGlobalHandlers: function() {
|
|
|
+ if (this.externalDropping) {
|
|
|
+ this.externalDropping.bindToDocument();
|
|
|
+ }
|
|
|
},
|
|
|
|
|
|
|
|
|
unbindGlobalHandlers: function() {
|
|
|
+ if (this.externalDropping) {
|
|
|
+ this.externalDropping.unbindFromDocument();
|
|
|
+ }
|
|
|
},
|
|
|
|
|
|
|
|
|
@@ -234,6 +292,180 @@ var ChronoComponent = Model.extend({
|
|
|
},
|
|
|
|
|
|
|
|
|
+ // Interaction
|
|
|
+ // ---------------------------------------------------------------------------------------------------------------
|
|
|
+
|
|
|
+
|
|
|
+ bindDateHandlerToEl: function(el, name, handler) {
|
|
|
+ var _this = this;
|
|
|
+
|
|
|
+ // attach a handler to the grid's root element.
|
|
|
+ // jQuery will take care of unregistering them when removeElement gets called.
|
|
|
+ this.el.on(name, function(ev) {
|
|
|
+ if (
|
|
|
+ !$(ev.target).is(
|
|
|
+ _this.segSelector + ',' + // directly on an event element
|
|
|
+ _this.segSelector + ' *,' + // within an event element
|
|
|
+ '.fc-more,' + // a "more.." link
|
|
|
+ 'a[data-goto]' // a clickable nav link
|
|
|
+ )
|
|
|
+ ) {
|
|
|
+ return handler.call(_this, ev);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+
|
|
|
+ bindSegHandlerToEl: function(el, name, handler) {
|
|
|
+ var _this = this;
|
|
|
+
|
|
|
+ el.on(name, this.segSelector, function(ev) {
|
|
|
+ var seg = $(this).data('fc-seg'); // grab segment data. put there by View::renderEventsPayload
|
|
|
+
|
|
|
+ if (seg && !_this.shouldIgnoreEventPointing()) {
|
|
|
+ return handler.call(_this, seg, ev); // context will be the Grid
|
|
|
+ }
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+
|
|
|
+ bindAllSegHandlersToEl: function(el) {
|
|
|
+ [
|
|
|
+ this.eventPointing,
|
|
|
+ this.eventDragging,
|
|
|
+ this.eventResizing
|
|
|
+ ].forEach(function(eventInteraction) {
|
|
|
+ if (eventInteraction) {
|
|
|
+ eventInteraction.bindToEl(el);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+
|
|
|
+ shouldIgnoreMouse: function() {
|
|
|
+ // HACK
|
|
|
+ // This will still work even though bindDateHandlerToEl doesn't use GlobalEmitter.
|
|
|
+ return GlobalEmitter.get().shouldIgnoreMouse();
|
|
|
+ },
|
|
|
+
|
|
|
+
|
|
|
+ shouldIgnoreTouch: function() {
|
|
|
+ var view = this._getView();
|
|
|
+
|
|
|
+ // On iOS (and Android?) when a new selection is initiated overtop another selection,
|
|
|
+ // the touchend never fires because the elements gets removed mid-touch-interaction (my theory).
|
|
|
+ // HACK: simply don't allow this to happen.
|
|
|
+ // ALSO: prevent selection when an *event* is already raised.
|
|
|
+ return view.isSelected || view.selectedEvent;
|
|
|
+ },
|
|
|
+
|
|
|
+
|
|
|
+ shouldIgnoreEventPointing: function() {
|
|
|
+ // only call the handlers if there is not a drag/resize in progress
|
|
|
+ return (this.eventDragging && this.eventDragging.isDragging) ||
|
|
|
+ (this.eventResizing && this.eventResizing.isResizing);
|
|
|
+ },
|
|
|
+
|
|
|
+
|
|
|
+ canStartSelection: function(seg, ev) {
|
|
|
+ return getEvIsTouch(ev) &&
|
|
|
+ !this.canStartResize(seg, ev) &&
|
|
|
+ (this.isEventDefDraggable(seg.footprint.eventDef) ||
|
|
|
+ this.isEventDefResizable(seg.footprint.eventDef));
|
|
|
+ },
|
|
|
+
|
|
|
+
|
|
|
+ canStartDrag: function(seg, ev) {
|
|
|
+ return !this.canStartResize(seg, ev) &&
|
|
|
+ this.isEventDefDraggable(seg.footprint.eventDef);
|
|
|
+ },
|
|
|
+
|
|
|
+
|
|
|
+ canStartResize: function(seg, ev) {
|
|
|
+ var view = this._getView();
|
|
|
+ var eventDef = seg.footprint.eventDef;
|
|
|
+
|
|
|
+ return (!getEvIsTouch(ev) || view.isEventDefSelected(eventDef)) &&
|
|
|
+ this.isEventDefResizable(eventDef) &&
|
|
|
+ $(ev.target).is('.fc-resizer');
|
|
|
+ },
|
|
|
+
|
|
|
+
|
|
|
+ // Kills all in-progress dragging.
|
|
|
+ // Useful for when public API methods that result in re-rendering are invoked during a drag.
|
|
|
+ // Also useful for when touch devices misbehave and don't fire their touchend.
|
|
|
+ endInteractions: function() {
|
|
|
+ [
|
|
|
+ this.dateClicking,
|
|
|
+ this.dateSelecting,
|
|
|
+ this.eventPointing,
|
|
|
+ this.eventDragging,
|
|
|
+ this.eventResizing
|
|
|
+ ].forEach(function(interaction) {
|
|
|
+ if (interaction) {
|
|
|
+ interaction.end();
|
|
|
+ }
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+
|
|
|
+ // Diffs the two dates, returning a duration, based on granularity of the grid
|
|
|
+ // TODO: port isTimeScale into this system?
|
|
|
+ diffDates: function(a, b) {
|
|
|
+ if (this.largeUnit) {
|
|
|
+ return diffByUnit(a, b, this.largeUnit);
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ return diffDayTime(a, b);
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+
|
|
|
+ // is it allowed, in relation to the view's validRange?
|
|
|
+ // NOTE: very similar to isExternalInstanceGroupAllowed
|
|
|
+ isEventInstanceGroupAllowed: function(eventInstanceGroup) {
|
|
|
+ var view = this._getView();
|
|
|
+ var eventFootprints = this.eventRangesToEventFootprints(eventInstanceGroup.getAllEventRanges());
|
|
|
+ var i;
|
|
|
+
|
|
|
+ for (i = 0; i < eventFootprints.length; i++) {
|
|
|
+ // TODO: just use getAllEventRanges directly
|
|
|
+ if (!view.validUnzonedRange.containsRange(eventFootprints[i].componentFootprint.unzonedRange)) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return view.calendar.isEventInstanceGroupAllowed(eventInstanceGroup);
|
|
|
+ },
|
|
|
+
|
|
|
+
|
|
|
+ // NOTE: very similar to isEventInstanceGroupAllowed
|
|
|
+ // when it's a completely anonymous external drag, no event.
|
|
|
+ isExternalInstanceGroupAllowed: function(eventInstanceGroup) {
|
|
|
+ var view = this._getView();
|
|
|
+ var eventFootprints = this.eventRangesToEventFootprints(eventInstanceGroup.getAllEventRanges());
|
|
|
+ var i;
|
|
|
+
|
|
|
+ for (i = 0; i < eventFootprints.length; i++) {
|
|
|
+ if (!view.validUnzonedRange.containsRange(eventFootprints[i].componentFootprint.unzonedRange)) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ for (i = 0; i < eventFootprints.length; i++) {
|
|
|
+ // treat it as a selection
|
|
|
+ // TODO: pass in eventInstanceGroup instead
|
|
|
+ // because we don't want calendar's constraint system to depend on a component's
|
|
|
+ // determination of footprints.
|
|
|
+ if (!view.calendar.isSelectionFootprintAllowed(eventFootprints[i].componentFootprint)) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return true;
|
|
|
+ },
|
|
|
+
|
|
|
+
|
|
|
// Drag-n-Drop Rendering (for both events and external elements)
|
|
|
// ---------------------------------------------------------------------------------------------------------------
|
|
|
|
|
|
@@ -270,6 +502,23 @@ var ChronoComponent = Model.extend({
|
|
|
},
|
|
|
|
|
|
|
|
|
+ // Event Resizing
|
|
|
+ // ---------------------------------------------------------------------------------------------------------------
|
|
|
+
|
|
|
+
|
|
|
+ // Renders a visual indication of an event being resized.
|
|
|
+ // Must return elements used for any mock events.
|
|
|
+ renderEventResize: function(eventFootprints, seg) {
|
|
|
+ // subclasses must implement
|
|
|
+ },
|
|
|
+
|
|
|
+
|
|
|
+ // Unrenders a visual indication of an event being resized.
|
|
|
+ unrenderEventResize: function() {
|
|
|
+ // subclasses must implement
|
|
|
+ },
|
|
|
+
|
|
|
+
|
|
|
// Selection
|
|
|
// ---------------------------------------------------------------------------------------------------------------
|
|
|
|
|
|
@@ -277,16 +526,30 @@ var ChronoComponent = Model.extend({
|
|
|
// Renders a visual indication of the selection
|
|
|
// TODO: rename to `renderSelection` after legacy is gone
|
|
|
renderSelectionFootprint: function(componentFootprint) {
|
|
|
+ this.renderHighlight(componentFootprint);
|
|
|
+
|
|
|
this.callChildren('renderSelectionFootprint', componentFootprint);
|
|
|
},
|
|
|
|
|
|
|
|
|
// Unrenders a visual indication of selection
|
|
|
unrenderSelection: function() {
|
|
|
+ this.unrenderHighlight();
|
|
|
+
|
|
|
this.callChildren('unrenderSelection');
|
|
|
},
|
|
|
|
|
|
|
|
|
+ // Renders an emphasis on the given date range. Given a span (unzoned start/end and other misc data)
|
|
|
+ renderHighlight: function(componentFootprint) {
|
|
|
+ },
|
|
|
+
|
|
|
+
|
|
|
+ // Unrenders the emphasis on a date range
|
|
|
+ unrenderHighlight: function() {
|
|
|
+ },
|
|
|
+
|
|
|
+
|
|
|
// Hit Areas
|
|
|
// ---------------------------------------------------------------------------------------------------------------
|
|
|
// just because all ChronoComponents support this interface
|
|
|
@@ -294,24 +557,30 @@ var ChronoComponent = Model.extend({
|
|
|
|
|
|
|
|
|
hitsNeeded: function() {
|
|
|
+ if (!(this.hitsNeededDepth++)) {
|
|
|
+ this.prepareHits();
|
|
|
+ }
|
|
|
+
|
|
|
this.callChildren('hitsNeeded');
|
|
|
},
|
|
|
|
|
|
|
|
|
hitsNotNeeded: function() {
|
|
|
+ if (this.hitsNeededDepth && !(--this.hitsNeededDepth)) {
|
|
|
+ this.releaseHits();
|
|
|
+ }
|
|
|
+
|
|
|
this.callChildren('hitsNotNeeded');
|
|
|
},
|
|
|
|
|
|
|
|
|
- // Called before one or more queryHit calls might happen. Should prepare any cached coordinates for queryHit
|
|
|
prepareHits: function() {
|
|
|
- this.callChildren('prepareHits');
|
|
|
+ // subclasses can implement
|
|
|
},
|
|
|
|
|
|
|
|
|
- // Called when queryHit calls have subsided. Good place to clear any coordinate caches.
|
|
|
releaseHits: function() {
|
|
|
- this.callChildren('releaseHits');
|
|
|
+ // subclasses can implement
|
|
|
},
|
|
|
|
|
|
|
|
|
@@ -336,6 +605,28 @@ var ChronoComponent = Model.extend({
|
|
|
},
|
|
|
|
|
|
|
|
|
+ getSafeHitFootprint: function(hit) {
|
|
|
+ var view = this._getView();
|
|
|
+ var footprint = this.getHitFootprint(hit);
|
|
|
+
|
|
|
+ if (!view.activeUnzonedRange.containsRange(footprint.unzonedRange)) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ return footprint;
|
|
|
+ },
|
|
|
+
|
|
|
+
|
|
|
+ getHitFootprint: function(hit) {
|
|
|
+ },
|
|
|
+
|
|
|
+
|
|
|
+ // Given position-level information about a date-related area within the grid,
|
|
|
+ // should return a jQuery element that best represents it. passed to dayClick callback.
|
|
|
+ getHitEl: function(hit) {
|
|
|
+ },
|
|
|
+
|
|
|
+
|
|
|
// Event Drag-n-Drop
|
|
|
// ---------------------------------------------------------------------------------------------------------------
|
|
|
|