fullcalendar-rails 2.6.1.0 → 2.8.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/fullcalendar-rails/version.rb +1 -1
- data/vendor/assets/javascripts/fullcalendar.js +1481 -522
- data/vendor/assets/javascripts/fullcalendar/gcal.js +4 -4
- data/vendor/assets/javascripts/fullcalendar/lang-all.js +4 -4
- data/vendor/assets/javascripts/fullcalendar/lang/ar-ma.js +1 -1
- data/vendor/assets/javascripts/fullcalendar/lang/ar-sa.js +1 -1
- data/vendor/assets/javascripts/fullcalendar/lang/ar-tn.js +1 -1
- data/vendor/assets/javascripts/fullcalendar/lang/ar.js +1 -1
- data/vendor/assets/javascripts/fullcalendar/lang/bg.js +0 -0
- data/vendor/assets/javascripts/fullcalendar/lang/ca.js +1 -1
- data/vendor/assets/javascripts/fullcalendar/lang/cs.js +0 -0
- data/vendor/assets/javascripts/fullcalendar/lang/da.js +0 -0
- data/vendor/assets/javascripts/fullcalendar/lang/de-at.js +1 -1
- data/vendor/assets/javascripts/fullcalendar/lang/de.js +1 -1
- data/vendor/assets/javascripts/fullcalendar/lang/el.js +0 -0
- data/vendor/assets/javascripts/fullcalendar/lang/en-au.js +1 -1
- data/vendor/assets/javascripts/fullcalendar/lang/en-ca.js +1 -1
- data/vendor/assets/javascripts/fullcalendar/lang/en-gb.js +1 -1
- data/vendor/assets/javascripts/fullcalendar/lang/en-ie.js +1 -1
- data/vendor/assets/javascripts/fullcalendar/lang/en-nz.js +1 -1
- data/vendor/assets/javascripts/fullcalendar/lang/es.js +1 -1
- data/vendor/assets/javascripts/fullcalendar/lang/eu.js +1 -0
- data/vendor/assets/javascripts/fullcalendar/lang/fa.js +1 -1
- data/vendor/assets/javascripts/fullcalendar/lang/fi.js +0 -0
- data/vendor/assets/javascripts/fullcalendar/lang/fr-ca.js +1 -1
- data/vendor/assets/javascripts/fullcalendar/lang/fr-ch.js +1 -1
- data/vendor/assets/javascripts/fullcalendar/lang/fr.js +1 -1
- data/vendor/assets/javascripts/fullcalendar/lang/gl.js +1 -0
- data/vendor/assets/javascripts/fullcalendar/lang/he.js +1 -1
- data/vendor/assets/javascripts/fullcalendar/lang/hi.js +1 -1
- data/vendor/assets/javascripts/fullcalendar/lang/hr.js +1 -1
- data/vendor/assets/javascripts/fullcalendar/lang/hu.js +0 -0
- data/vendor/assets/javascripts/fullcalendar/lang/id.js +0 -0
- data/vendor/assets/javascripts/fullcalendar/lang/is.js +1 -1
- data/vendor/assets/javascripts/fullcalendar/lang/it.js +0 -0
- data/vendor/assets/javascripts/fullcalendar/lang/ja.js +1 -1
- data/vendor/assets/javascripts/fullcalendar/lang/ko.js +1 -1
- data/vendor/assets/javascripts/fullcalendar/lang/lb.js +1 -0
- data/vendor/assets/javascripts/fullcalendar/lang/lt.js +1 -1
- data/vendor/assets/javascripts/fullcalendar/lang/lv.js +1 -1
- data/vendor/assets/javascripts/fullcalendar/lang/nb.js +1 -1
- data/vendor/assets/javascripts/fullcalendar/lang/nl.js +1 -1
- data/vendor/assets/javascripts/fullcalendar/lang/pl.js +0 -0
- data/vendor/assets/javascripts/fullcalendar/lang/pt-br.js +1 -1
- data/vendor/assets/javascripts/fullcalendar/lang/pt.js +1 -1
- data/vendor/assets/javascripts/fullcalendar/lang/ro.js +1 -1
- data/vendor/assets/javascripts/fullcalendar/lang/ru.js +1 -1
- data/vendor/assets/javascripts/fullcalendar/lang/sk.js +0 -0
- data/vendor/assets/javascripts/fullcalendar/lang/sl.js +1 -1
- data/vendor/assets/javascripts/fullcalendar/lang/sr-cyrl.js +1 -1
- data/vendor/assets/javascripts/fullcalendar/lang/sr.js +1 -1
- data/vendor/assets/javascripts/fullcalendar/lang/sv.js +1 -1
- data/vendor/assets/javascripts/fullcalendar/lang/th.js +1 -1
- data/vendor/assets/javascripts/fullcalendar/lang/tr.js +0 -0
- data/vendor/assets/javascripts/fullcalendar/lang/uk.js +0 -0
- data/vendor/assets/javascripts/fullcalendar/lang/vi.js +1 -1
- data/vendor/assets/javascripts/fullcalendar/lang/zh-cn.js +1 -1
- data/vendor/assets/javascripts/fullcalendar/lang/zh-tw.js +1 -1
- data/vendor/assets/stylesheets/fullcalendar.css +179 -42
- data/vendor/assets/stylesheets/fullcalendar.print.css +2 -2
- metadata +6 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bf346a46d7b099097273d5778484477c361d80d0
|
4
|
+
data.tar.gz: 1ef451e5d2a42a11c33174170e84394169ebf449
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 073212fe38be4290162ce05f714f9154979eafc35227132a311a8be3d260a2eecbb5e48987ef9cf3649e878f2b7c8e0c980963945f309b5e8602f78911930182
|
7
|
+
data.tar.gz: fc867f9f0a959c44749a5d2d0401725e235b379206537fc2cd2927b78c6d0a020a81b8cf6960b8229cd2c832f9bc16b60e309e1c501b498decc965e48602895a
|
@@ -1,7 +1,7 @@
|
|
1
1
|
/*!
|
2
|
-
* FullCalendar v2.
|
2
|
+
* FullCalendar v2.8.0
|
3
3
|
* Docs & License: http://fullcalendar.io/
|
4
|
-
* (c)
|
4
|
+
* (c) 2016 Adam Shaw
|
5
5
|
*/
|
6
6
|
|
7
7
|
(function(factory) {
|
@@ -19,8 +19,8 @@
|
|
19
19
|
;;
|
20
20
|
|
21
21
|
var FC = $.fullCalendar = {
|
22
|
-
version: "2.
|
23
|
-
internalApiVersion:
|
22
|
+
version: "2.8.0",
|
23
|
+
internalApiVersion: 4
|
24
24
|
};
|
25
25
|
var fcViews = FC.views = {};
|
26
26
|
|
@@ -262,29 +262,25 @@ function matchCellWidths(els) {
|
|
262
262
|
}
|
263
263
|
|
264
264
|
|
265
|
-
//
|
266
|
-
//
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
// are scrollbars needed?
|
272
|
-
if (containerEl[0].scrollHeight - 1 > containerEl[0].clientHeight) { // !!! -1 because IE is often off-by-one :(
|
273
|
-
return true;
|
274
|
-
}
|
275
|
-
|
276
|
-
unsetScroller(containerEl); // undo
|
277
|
-
return false;
|
278
|
-
}
|
265
|
+
// Given one element that resides inside another,
|
266
|
+
// Subtracts the height of the inner element from the outer element.
|
267
|
+
function subtractInnerElHeight(outerEl, innerEl) {
|
268
|
+
var both = outerEl.add(innerEl);
|
269
|
+
var diff;
|
279
270
|
|
271
|
+
// effin' IE8/9/10/11 sometimes returns 0 for dimensions. this weird hack was the only thing that worked
|
272
|
+
both.css({
|
273
|
+
position: 'relative', // cause a reflow, which will force fresh dimension recalculation
|
274
|
+
left: -1 // ensure reflow in case the el was already relative. negative is less likely to cause new scroll
|
275
|
+
});
|
276
|
+
diff = outerEl.outerHeight() - innerEl.outerHeight(); // grab the dimensions
|
277
|
+
both.css({ position: '', left: '' }); // undo hack
|
280
278
|
|
281
|
-
|
282
|
-
function unsetScroller(containerEl) {
|
283
|
-
containerEl.height('').removeClass('fc-scroller');
|
279
|
+
return diff;
|
284
280
|
}
|
285
281
|
|
286
282
|
|
287
|
-
/*
|
283
|
+
/* Element Geom Utilities
|
288
284
|
----------------------------------------------------------------------------------------------------------------------*/
|
289
285
|
|
290
286
|
FC.getOuterRect = getOuterRect;
|
@@ -309,26 +305,30 @@ function getScrollParent(el) {
|
|
309
305
|
|
310
306
|
// Queries the outer bounding area of a jQuery element.
|
311
307
|
// Returns a rectangle with absolute coordinates: left, right (exclusive), top, bottom (exclusive).
|
312
|
-
|
308
|
+
// Origin is optional.
|
309
|
+
function getOuterRect(el, origin) {
|
313
310
|
var offset = el.offset();
|
311
|
+
var left = offset.left - (origin ? origin.left : 0);
|
312
|
+
var top = offset.top - (origin ? origin.top : 0);
|
314
313
|
|
315
314
|
return {
|
316
|
-
left:
|
317
|
-
right:
|
318
|
-
top:
|
319
|
-
bottom:
|
315
|
+
left: left,
|
316
|
+
right: left + el.outerWidth(),
|
317
|
+
top: top,
|
318
|
+
bottom: top + el.outerHeight()
|
320
319
|
};
|
321
320
|
}
|
322
321
|
|
323
322
|
|
324
323
|
// Queries the area within the margin/border/scrollbars of a jQuery element. Does not go within the padding.
|
325
324
|
// Returns a rectangle with absolute coordinates: left, right (exclusive), top, bottom (exclusive).
|
325
|
+
// Origin is optional.
|
326
326
|
// NOTE: should use clientLeft/clientTop, but very unreliable cross-browser.
|
327
|
-
function getClientRect(el) {
|
327
|
+
function getClientRect(el, origin) {
|
328
328
|
var offset = el.offset();
|
329
329
|
var scrollbarWidths = getScrollbarWidths(el);
|
330
|
-
var left = offset.left + getCssFloat(el, 'border-left-width') + scrollbarWidths.left;
|
331
|
-
var top = offset.top + getCssFloat(el, 'border-top-width') + scrollbarWidths.top;
|
330
|
+
var left = offset.left + getCssFloat(el, 'border-left-width') + scrollbarWidths.left - (origin ? origin.left : 0);
|
331
|
+
var top = offset.top + getCssFloat(el, 'border-top-width') + scrollbarWidths.top - (origin ? origin.top : 0);
|
332
332
|
|
333
333
|
return {
|
334
334
|
left: left,
|
@@ -341,10 +341,13 @@ function getClientRect(el) {
|
|
341
341
|
|
342
342
|
// Queries the area within the margin/border/padding of a jQuery element. Assumed not to have scrollbars.
|
343
343
|
// Returns a rectangle with absolute coordinates: left, right (exclusive), top, bottom (exclusive).
|
344
|
-
|
344
|
+
// Origin is optional.
|
345
|
+
function getContentRect(el, origin) {
|
345
346
|
var offset = el.offset(); // just outside of border, margin not included
|
346
|
-
var left = offset.left + getCssFloat(el, 'border-left-width') + getCssFloat(el, 'padding-left')
|
347
|
-
|
347
|
+
var left = offset.left + getCssFloat(el, 'border-left-width') + getCssFloat(el, 'padding-left') -
|
348
|
+
(origin ? origin.left : 0);
|
349
|
+
var top = offset.top + getCssFloat(el, 'border-top-width') + getCssFloat(el, 'padding-top') -
|
350
|
+
(origin ? origin.top : 0);
|
348
351
|
|
349
352
|
return {
|
350
353
|
left: left,
|
@@ -414,13 +417,82 @@ function getCssFloat(el, prop) {
|
|
414
417
|
}
|
415
418
|
|
416
419
|
|
420
|
+
/* Mouse / Touch Utilities
|
421
|
+
----------------------------------------------------------------------------------------------------------------------*/
|
422
|
+
|
423
|
+
FC.preventDefault = preventDefault;
|
424
|
+
|
425
|
+
|
417
426
|
// Returns a boolean whether this was a left mouse click and no ctrl key (which means right click on Mac)
|
418
427
|
function isPrimaryMouseButton(ev) {
|
419
428
|
return ev.which == 1 && !ev.ctrlKey;
|
420
429
|
}
|
421
430
|
|
422
431
|
|
423
|
-
|
432
|
+
function getEvX(ev) {
|
433
|
+
if (ev.pageX !== undefined) {
|
434
|
+
return ev.pageX;
|
435
|
+
}
|
436
|
+
var touches = ev.originalEvent.touches;
|
437
|
+
if (touches) {
|
438
|
+
return touches[0].pageX;
|
439
|
+
}
|
440
|
+
}
|
441
|
+
|
442
|
+
|
443
|
+
function getEvY(ev) {
|
444
|
+
if (ev.pageY !== undefined) {
|
445
|
+
return ev.pageY;
|
446
|
+
}
|
447
|
+
var touches = ev.originalEvent.touches;
|
448
|
+
if (touches) {
|
449
|
+
return touches[0].pageY;
|
450
|
+
}
|
451
|
+
}
|
452
|
+
|
453
|
+
|
454
|
+
function getEvIsTouch(ev) {
|
455
|
+
return /^touch/.test(ev.type);
|
456
|
+
}
|
457
|
+
|
458
|
+
|
459
|
+
function preventSelection(el) {
|
460
|
+
el.addClass('fc-unselectable')
|
461
|
+
.on('selectstart', preventDefault);
|
462
|
+
}
|
463
|
+
|
464
|
+
|
465
|
+
// Stops a mouse/touch event from doing it's native browser action
|
466
|
+
function preventDefault(ev) {
|
467
|
+
ev.preventDefault();
|
468
|
+
}
|
469
|
+
|
470
|
+
|
471
|
+
// attach a handler to get called when ANY scroll action happens on the page.
|
472
|
+
// this was impossible to do with normal on/off because 'scroll' doesn't bubble.
|
473
|
+
// http://stackoverflow.com/a/32954565/96342
|
474
|
+
// returns `true` on success.
|
475
|
+
function bindAnyScroll(handler) {
|
476
|
+
if (window.addEventListener) {
|
477
|
+
window.addEventListener('scroll', handler, true); // useCapture=true
|
478
|
+
return true;
|
479
|
+
}
|
480
|
+
return false;
|
481
|
+
}
|
482
|
+
|
483
|
+
|
484
|
+
// undoes bindAnyScroll. must pass in the original function.
|
485
|
+
// returns `true` on success.
|
486
|
+
function unbindAnyScroll(handler) {
|
487
|
+
if (window.removeEventListener) {
|
488
|
+
window.removeEventListener('scroll', handler, true); // useCapture=true
|
489
|
+
return true;
|
490
|
+
}
|
491
|
+
return false;
|
492
|
+
}
|
493
|
+
|
494
|
+
|
495
|
+
/* General Geometry Utils
|
424
496
|
----------------------------------------------------------------------------------------------------------------------*/
|
425
497
|
|
426
498
|
FC.intersectRects = intersectRects;
|
@@ -946,22 +1018,21 @@ function proxy(obj, methodName) {
|
|
946
1018
|
|
947
1019
|
// Returns a function, that, as long as it continues to be invoked, will not
|
948
1020
|
// be triggered. The function will be called after it stops being called for
|
949
|
-
// N milliseconds.
|
1021
|
+
// N milliseconds. If `immediate` is passed, trigger the function on the
|
1022
|
+
// leading edge, instead of the trailing.
|
950
1023
|
// https://github.com/jashkenas/underscore/blob/1.6.0/underscore.js#L714
|
951
|
-
function debounce(func, wait) {
|
952
|
-
var
|
953
|
-
|
954
|
-
var context;
|
955
|
-
var timestamp; // of most recent call
|
1024
|
+
function debounce(func, wait, immediate) {
|
1025
|
+
var timeout, args, context, timestamp, result;
|
1026
|
+
|
956
1027
|
var later = function() {
|
957
1028
|
var last = +new Date() - timestamp;
|
958
|
-
if (last < wait
|
959
|
-
|
1029
|
+
if (last < wait) {
|
1030
|
+
timeout = setTimeout(later, wait - last);
|
960
1031
|
}
|
961
1032
|
else {
|
962
|
-
|
963
|
-
|
964
|
-
|
1033
|
+
timeout = null;
|
1034
|
+
if (!immediate) {
|
1035
|
+
result = func.apply(context, args);
|
965
1036
|
context = args = null;
|
966
1037
|
}
|
967
1038
|
}
|
@@ -971,12 +1042,32 @@ function debounce(func, wait) {
|
|
971
1042
|
context = this;
|
972
1043
|
args = arguments;
|
973
1044
|
timestamp = +new Date();
|
974
|
-
|
975
|
-
|
1045
|
+
var callNow = immediate && !timeout;
|
1046
|
+
if (!timeout) {
|
1047
|
+
timeout = setTimeout(later, wait);
|
1048
|
+
}
|
1049
|
+
if (callNow) {
|
1050
|
+
result = func.apply(context, args);
|
1051
|
+
context = args = null;
|
976
1052
|
}
|
1053
|
+
return result;
|
977
1054
|
};
|
978
1055
|
}
|
979
1056
|
|
1057
|
+
|
1058
|
+
// HACK around jQuery's now A+ promises: execute callback synchronously if already resolved.
|
1059
|
+
// thenFunc shouldn't accept args.
|
1060
|
+
// similar to whenResources in Scheduler plugin.
|
1061
|
+
function syncThen(promise, thenFunc) {
|
1062
|
+
// not a promise, or an already-resolved promise?
|
1063
|
+
if (!promise || !promise.then || promise.state() === 'resolved') {
|
1064
|
+
return $.when(thenFunc()); // resolve immediately
|
1065
|
+
}
|
1066
|
+
else if (thenFunc) {
|
1067
|
+
return promise.then(thenFunc);
|
1068
|
+
}
|
1069
|
+
}
|
1070
|
+
|
980
1071
|
;;
|
981
1072
|
|
982
1073
|
var ambigDateOfMonthRegex = /^\s*\d{4}-\d\d$/;
|
@@ -1777,61 +1868,162 @@ function extendClass(superClass, members) {
|
|
1777
1868
|
|
1778
1869
|
|
1779
1870
|
function mixIntoClass(theClass, members) {
|
1780
|
-
copyOwnProps(members
|
1871
|
+
copyOwnProps(members, theClass.prototype); // TODO: copyNativeMethods?
|
1781
1872
|
}
|
1782
1873
|
;;
|
1783
1874
|
|
1784
|
-
var
|
1875
|
+
var EmitterMixin = FC.EmitterMixin = {
|
1876
|
+
|
1877
|
+
// jQuery-ification via $(this) allows a non-DOM object to have
|
1878
|
+
// the same event handling capabilities (including namespaces).
|
1785
1879
|
|
1786
|
-
callbackHash: null,
|
1787
1880
|
|
1881
|
+
on: function(types, handler) {
|
1882
|
+
|
1883
|
+
// handlers are always called with an "event" object as their first param.
|
1884
|
+
// sneak the `this` context and arguments into the extra parameter object
|
1885
|
+
// and forward them on to the original handler.
|
1886
|
+
var intercept = function(ev, extra) {
|
1887
|
+
return handler.apply(
|
1888
|
+
extra.context || this,
|
1889
|
+
extra.args || []
|
1890
|
+
);
|
1891
|
+
};
|
1892
|
+
|
1893
|
+
// mimick jQuery's internal "proxy" system (risky, I know)
|
1894
|
+
// causing all functions with the same .guid to appear to be the same.
|
1895
|
+
// https://github.com/jquery/jquery/blob/2.2.4/src/core.js#L448
|
1896
|
+
// this is needed for calling .off with the original non-intercept handler.
|
1897
|
+
if (!handler.guid) {
|
1898
|
+
handler.guid = $.guid++;
|
1899
|
+
}
|
1900
|
+
intercept.guid = handler.guid;
|
1901
|
+
|
1902
|
+
$(this).on(types, intercept);
|
1788
1903
|
|
1789
|
-
on: function(name, callback) {
|
1790
|
-
this.getCallbacks(name).add(callback);
|
1791
1904
|
return this; // for chaining
|
1792
1905
|
},
|
1793
1906
|
|
1794
1907
|
|
1795
|
-
off: function(
|
1796
|
-
this
|
1908
|
+
off: function(types, handler) {
|
1909
|
+
$(this).off(types, handler);
|
1910
|
+
|
1797
1911
|
return this; // for chaining
|
1798
1912
|
},
|
1799
1913
|
|
1800
1914
|
|
1801
|
-
trigger: function(
|
1802
|
-
var args = Array.prototype.slice.call(arguments, 1);
|
1915
|
+
trigger: function(types) {
|
1916
|
+
var args = Array.prototype.slice.call(arguments, 1); // arguments after the first
|
1803
1917
|
|
1804
|
-
|
1918
|
+
// pass in "extra" info to the intercept
|
1919
|
+
$(this).triggerHandler(types, { args: args });
|
1805
1920
|
|
1806
1921
|
return this; // for chaining
|
1807
1922
|
},
|
1808
1923
|
|
1809
1924
|
|
1810
|
-
triggerWith: function(
|
1811
|
-
var callbacks = this.getCallbacks(name);
|
1925
|
+
triggerWith: function(types, context, args) {
|
1812
1926
|
|
1813
|
-
|
1927
|
+
// `triggerHandler` is less reliant on the DOM compared to `trigger`.
|
1928
|
+
// pass in "extra" info to the intercept.
|
1929
|
+
$(this).triggerHandler(types, { context: context, args: args });
|
1814
1930
|
|
1815
1931
|
return this; // for chaining
|
1816
|
-
}
|
1932
|
+
}
|
1817
1933
|
|
1934
|
+
};
|
1818
1935
|
|
1819
|
-
|
1820
|
-
var callbacks;
|
1936
|
+
;;
|
1821
1937
|
|
1822
|
-
|
1823
|
-
|
1824
|
-
|
1938
|
+
/*
|
1939
|
+
Utility methods for easily listening to events on another object,
|
1940
|
+
and more importantly, easily unlistening from them.
|
1941
|
+
*/
|
1942
|
+
var ListenerMixin = FC.ListenerMixin = (function() {
|
1943
|
+
var guid = 0;
|
1944
|
+
var ListenerMixin = {
|
1945
|
+
|
1946
|
+
listenerId: null,
|
1947
|
+
|
1948
|
+
/*
|
1949
|
+
Given an `other` object that has on/off methods, bind the given `callback` to an event by the given name.
|
1950
|
+
The `callback` will be called with the `this` context of the object that .listenTo is being called on.
|
1951
|
+
Can be called:
|
1952
|
+
.listenTo(other, eventName, callback)
|
1953
|
+
OR
|
1954
|
+
.listenTo(other, {
|
1955
|
+
eventName1: callback1,
|
1956
|
+
eventName2: callback2
|
1957
|
+
})
|
1958
|
+
*/
|
1959
|
+
listenTo: function(other, arg, callback) {
|
1960
|
+
if (typeof arg === 'object') { // given dictionary of callbacks
|
1961
|
+
for (var eventName in arg) {
|
1962
|
+
if (arg.hasOwnProperty(eventName)) {
|
1963
|
+
this.listenTo(other, eventName, arg[eventName]);
|
1964
|
+
}
|
1965
|
+
}
|
1966
|
+
}
|
1967
|
+
else if (typeof arg === 'string') {
|
1968
|
+
other.on(
|
1969
|
+
arg + '.' + this.getListenerNamespace(), // use event namespacing to identify this object
|
1970
|
+
$.proxy(callback, this) // always use `this` context
|
1971
|
+
// the usually-undesired jQuery guid behavior doesn't matter,
|
1972
|
+
// because we always unbind via namespace
|
1973
|
+
);
|
1974
|
+
}
|
1975
|
+
},
|
1825
1976
|
|
1826
|
-
|
1827
|
-
|
1828
|
-
|
1977
|
+
/*
|
1978
|
+
Causes the current object to stop listening to events on the `other` object.
|
1979
|
+
`eventName` is optional. If omitted, will stop listening to ALL events on `other`.
|
1980
|
+
*/
|
1981
|
+
stopListeningTo: function(other, eventName) {
|
1982
|
+
other.off((eventName || '') + '.' + this.getListenerNamespace());
|
1983
|
+
},
|
1984
|
+
|
1985
|
+
/*
|
1986
|
+
Returns a string, unique to this object, to be used for event namespacing
|
1987
|
+
*/
|
1988
|
+
getListenerNamespace: function() {
|
1989
|
+
if (this.listenerId == null) {
|
1990
|
+
this.listenerId = guid++;
|
1991
|
+
}
|
1992
|
+
return '_listener' + this.listenerId;
|
1829
1993
|
}
|
1830
1994
|
|
1831
|
-
|
1995
|
+
};
|
1996
|
+
return ListenerMixin;
|
1997
|
+
})();
|
1998
|
+
;;
|
1999
|
+
|
2000
|
+
// simple class for toggle a `isIgnoringMouse` flag on delay
|
2001
|
+
// initMouseIgnoring must first be called, with a millisecond delay setting.
|
2002
|
+
var MouseIgnorerMixin = {
|
2003
|
+
|
2004
|
+
isIgnoringMouse: false, // bool
|
2005
|
+
delayUnignoreMouse: null, // method
|
2006
|
+
|
2007
|
+
|
2008
|
+
initMouseIgnoring: function(delay) {
|
2009
|
+
this.delayUnignoreMouse = debounce(proxy(this, 'unignoreMouse'), delay || 1000);
|
2010
|
+
},
|
2011
|
+
|
2012
|
+
|
2013
|
+
// temporarily ignore mouse actions on segments
|
2014
|
+
tempIgnoreMouse: function() {
|
2015
|
+
this.isIgnoringMouse = true;
|
2016
|
+
this.delayUnignoreMouse();
|
2017
|
+
},
|
2018
|
+
|
2019
|
+
|
2020
|
+
// delayUnignoreMouse eventually calls this
|
2021
|
+
unignoreMouse: function() {
|
2022
|
+
this.isIgnoringMouse = false;
|
1832
2023
|
}
|
1833
2024
|
|
1834
|
-
}
|
2025
|
+
};
|
2026
|
+
|
1835
2027
|
;;
|
1836
2028
|
|
1837
2029
|
/* A rectangular panel that is absolutely positioned over other content
|
@@ -1848,12 +2040,11 @@ Options:
|
|
1848
2040
|
- hide (callback)
|
1849
2041
|
*/
|
1850
2042
|
|
1851
|
-
var Popover = Class.extend({
|
2043
|
+
var Popover = Class.extend(ListenerMixin, {
|
1852
2044
|
|
1853
2045
|
isHidden: true,
|
1854
2046
|
options: null,
|
1855
2047
|
el: null, // the container element for the popover. generated by this object
|
1856
|
-
documentMousedownProxy: null, // document mousedown handler bound to `this`
|
1857
2048
|
margin: 10, // the space required between the popover and the edges of the scroll container
|
1858
2049
|
|
1859
2050
|
|
@@ -1907,7 +2098,7 @@ var Popover = Class.extend({
|
|
1907
2098
|
});
|
1908
2099
|
|
1909
2100
|
if (options.autoHide) {
|
1910
|
-
$(document)
|
2101
|
+
this.listenTo($(document), 'mousedown', this.documentMousedown);
|
1911
2102
|
}
|
1912
2103
|
},
|
1913
2104
|
|
@@ -1930,7 +2121,7 @@ var Popover = Class.extend({
|
|
1930
2121
|
this.el = null;
|
1931
2122
|
}
|
1932
2123
|
|
1933
|
-
$(document)
|
2124
|
+
this.stopListeningTo($(document), 'mousedown');
|
1934
2125
|
},
|
1935
2126
|
|
1936
2127
|
|
@@ -2243,257 +2434,421 @@ var CoordCache = FC.CoordCache = Class.extend({
|
|
2243
2434
|
----------------------------------------------------------------------------------------------------------------------*/
|
2244
2435
|
// TODO: use Emitter
|
2245
2436
|
|
2246
|
-
var DragListener = FC.DragListener = Class.extend({
|
2437
|
+
var DragListener = FC.DragListener = Class.extend(ListenerMixin, MouseIgnorerMixin, {
|
2247
2438
|
|
2248
2439
|
options: null,
|
2249
2440
|
|
2250
|
-
|
2251
|
-
|
2441
|
+
// for IE8 bug-fighting behavior
|
2442
|
+
subjectEl: null,
|
2443
|
+
subjectHref: null,
|
2252
2444
|
|
2253
2445
|
// coordinates of the initial mousedown
|
2254
2446
|
originX: null,
|
2255
2447
|
originY: null,
|
2256
2448
|
|
2257
|
-
//
|
2258
|
-
|
2259
|
-
|
2449
|
+
// the wrapping element that scrolls, or MIGHT scroll if there's overflow.
|
2450
|
+
// TODO: do this for wrappers that have overflow:hidden as well.
|
2451
|
+
scrollEl: null,
|
2260
2452
|
|
2261
|
-
|
2262
|
-
|
2263
|
-
|
2453
|
+
isInteracting: false,
|
2454
|
+
isDistanceSurpassed: false,
|
2455
|
+
isDelayEnded: false,
|
2456
|
+
isDragging: false,
|
2457
|
+
isTouch: false,
|
2264
2458
|
|
2265
|
-
|
2266
|
-
|
2267
|
-
|
2268
|
-
scrollLeftVel: null, // pixels per second
|
2269
|
-
scrollIntervalId: null, // ID of setTimeout for scrolling animation loop
|
2270
|
-
scrollHandlerProxy: null, // this-scoped function for handling when scrollEl is scrolled
|
2459
|
+
delay: null,
|
2460
|
+
delayTimeoutId: null,
|
2461
|
+
minDistance: null,
|
2271
2462
|
|
2272
|
-
|
2273
|
-
scrollSpeed: 200, // pixels per second, at maximum speed
|
2274
|
-
scrollIntervalMs: 50, // millisecond wait between scroll increment
|
2463
|
+
handleTouchScrollProxy: null, // calls handleTouchScroll, always bound to `this`
|
2275
2464
|
|
2276
2465
|
|
2277
2466
|
constructor: function(options) {
|
2278
|
-
options = options || {};
|
2279
|
-
this.
|
2280
|
-
this.
|
2467
|
+
this.options = options || {};
|
2468
|
+
this.handleTouchScrollProxy = proxy(this, 'handleTouchScroll');
|
2469
|
+
this.initMouseIgnoring(500);
|
2281
2470
|
},
|
2282
2471
|
|
2283
2472
|
|
2284
|
-
//
|
2285
|
-
|
2286
|
-
|
2473
|
+
// Interaction (high-level)
|
2474
|
+
// -----------------------------------------------------------------------------------------------------------------
|
2475
|
+
|
2476
|
+
|
2477
|
+
startInteraction: function(ev, extraOptions) {
|
2478
|
+
var isTouch = getEvIsTouch(ev);
|
2479
|
+
|
2480
|
+
if (ev.type === 'mousedown') {
|
2481
|
+
if (this.isIgnoringMouse) {
|
2482
|
+
return;
|
2483
|
+
}
|
2484
|
+
else if (!isPrimaryMouseButton(ev)) {
|
2485
|
+
return;
|
2486
|
+
}
|
2487
|
+
else {
|
2488
|
+
ev.preventDefault(); // prevents native selection in most browsers
|
2489
|
+
}
|
2490
|
+
}
|
2287
2491
|
|
2288
|
-
|
2492
|
+
if (!this.isInteracting) {
|
2289
2493
|
|
2290
|
-
|
2494
|
+
// process options
|
2495
|
+
extraOptions = extraOptions || {};
|
2496
|
+
this.delay = firstDefined(extraOptions.delay, this.options.delay, 0);
|
2497
|
+
this.minDistance = firstDefined(extraOptions.distance, this.options.distance, 0);
|
2498
|
+
this.subjectEl = this.options.subjectEl;
|
2291
2499
|
|
2292
|
-
|
2293
|
-
|
2294
|
-
|
2500
|
+
this.isInteracting = true;
|
2501
|
+
this.isTouch = isTouch;
|
2502
|
+
this.isDelayEnded = false;
|
2503
|
+
this.isDistanceSurpassed = false;
|
2504
|
+
|
2505
|
+
this.originX = getEvX(ev);
|
2506
|
+
this.originY = getEvY(ev);
|
2507
|
+
this.scrollEl = getScrollParent($(ev.target));
|
2508
|
+
|
2509
|
+
this.bindHandlers();
|
2510
|
+
this.initAutoScroll();
|
2511
|
+
this.handleInteractionStart(ev);
|
2512
|
+
this.startDelay(ev);
|
2513
|
+
|
2514
|
+
if (!this.minDistance) {
|
2515
|
+
this.handleDistanceSurpassed(ev);
|
2295
2516
|
}
|
2296
2517
|
}
|
2297
2518
|
},
|
2298
2519
|
|
2299
2520
|
|
2300
|
-
|
2301
|
-
|
2302
|
-
|
2521
|
+
handleInteractionStart: function(ev) {
|
2522
|
+
this.trigger('interactionStart', ev);
|
2523
|
+
},
|
2303
2524
|
|
2304
|
-
if (!this.isListening) {
|
2305
2525
|
|
2306
|
-
|
2307
|
-
|
2308
|
-
|
2309
|
-
if (!scrollParent.is(window) && !scrollParent.is(document)) {
|
2310
|
-
this.scrollEl = scrollParent;
|
2526
|
+
endInteraction: function(ev, isCancelled) {
|
2527
|
+
if (this.isInteracting) {
|
2528
|
+
this.endDrag(ev);
|
2311
2529
|
|
2312
|
-
|
2313
|
-
|
2314
|
-
|
2315
|
-
}
|
2530
|
+
if (this.delayTimeoutId) {
|
2531
|
+
clearTimeout(this.delayTimeoutId);
|
2532
|
+
this.delayTimeoutId = null;
|
2316
2533
|
}
|
2317
2534
|
|
2318
|
-
|
2319
|
-
|
2320
|
-
|
2321
|
-
|
2535
|
+
this.destroyAutoScroll();
|
2536
|
+
this.unbindHandlers();
|
2537
|
+
|
2538
|
+
this.isInteracting = false;
|
2539
|
+
this.handleInteractionEnd(ev, isCancelled);
|
2322
2540
|
|
2323
|
-
|
2324
|
-
|
2325
|
-
|
2541
|
+
// a touchstart+touchend on the same element will result in the following addition simulated events:
|
2542
|
+
// mouseover + mouseout + click
|
2543
|
+
// let's ignore these bogus events
|
2544
|
+
if (this.isTouch) {
|
2545
|
+
this.tempIgnoreMouse();
|
2326
2546
|
}
|
2327
|
-
|
2328
|
-
|
2329
|
-
|
2330
|
-
|
2331
|
-
|
2547
|
+
}
|
2548
|
+
},
|
2549
|
+
|
2550
|
+
|
2551
|
+
handleInteractionEnd: function(ev, isCancelled) {
|
2552
|
+
this.trigger('interactionEnd', ev, isCancelled || false);
|
2553
|
+
},
|
2554
|
+
|
2555
|
+
|
2556
|
+
// Binding To DOM
|
2557
|
+
// -----------------------------------------------------------------------------------------------------------------
|
2558
|
+
|
2559
|
+
|
2560
|
+
bindHandlers: function() {
|
2561
|
+
var _this = this;
|
2562
|
+
var touchStartIgnores = 1;
|
2563
|
+
|
2564
|
+
if (this.isTouch) {
|
2565
|
+
this.listenTo($(document), {
|
2566
|
+
touchmove: this.handleTouchMove,
|
2567
|
+
touchend: this.endInteraction,
|
2568
|
+
touchcancel: this.endInteraction,
|
2569
|
+
|
2570
|
+
// Sometimes touchend doesn't fire
|
2571
|
+
// (can't figure out why. touchcancel doesn't fire either. has to do with scrolling?)
|
2572
|
+
// If another touchstart happens, we know it's bogus, so cancel the drag.
|
2573
|
+
// touchend will continue to be broken until user does a shorttap/scroll, but this is best we can do.
|
2574
|
+
touchstart: function(ev) {
|
2575
|
+
if (touchStartIgnores) { // bindHandlers is called from within a touchstart,
|
2576
|
+
touchStartIgnores--; // and we don't want this to fire immediately, so ignore.
|
2577
|
+
}
|
2578
|
+
else {
|
2579
|
+
_this.endInteraction(ev, true); // isCancelled=true
|
2580
|
+
}
|
2581
|
+
}
|
2582
|
+
});
|
2583
|
+
|
2584
|
+
// listen to ALL scroll actions on the page
|
2585
|
+
if (
|
2586
|
+
!bindAnyScroll(this.handleTouchScrollProxy) && // hopefully this works and short-circuits the rest
|
2587
|
+
this.scrollEl // otherwise, attach a single handler to this
|
2588
|
+
) {
|
2589
|
+
this.listenTo(this.scrollEl, 'scroll', this.handleTouchScroll);
|
2332
2590
|
}
|
2591
|
+
}
|
2592
|
+
else {
|
2593
|
+
this.listenTo($(document), {
|
2594
|
+
mousemove: this.handleMouseMove,
|
2595
|
+
mouseup: this.endInteraction
|
2596
|
+
});
|
2597
|
+
}
|
2598
|
+
|
2599
|
+
this.listenTo($(document), {
|
2600
|
+
selectstart: preventDefault, // don't allow selection while dragging
|
2601
|
+
contextmenu: preventDefault // long taps would open menu on Chrome dev tools
|
2602
|
+
});
|
2603
|
+
},
|
2604
|
+
|
2605
|
+
|
2606
|
+
unbindHandlers: function() {
|
2607
|
+
this.stopListeningTo($(document));
|
2608
|
+
|
2609
|
+
// unbind scroll listening
|
2610
|
+
unbindAnyScroll(this.handleTouchScrollProxy);
|
2611
|
+
if (this.scrollEl) {
|
2612
|
+
this.stopListeningTo(this.scrollEl, 'scroll');
|
2613
|
+
}
|
2614
|
+
},
|
2615
|
+
|
2333
2616
|
|
2334
|
-
|
2335
|
-
|
2617
|
+
// Drag (high-level)
|
2618
|
+
// -----------------------------------------------------------------------------------------------------------------
|
2619
|
+
|
2620
|
+
|
2621
|
+
// extraOptions ignored if drag already started
|
2622
|
+
startDrag: function(ev, extraOptions) {
|
2623
|
+
this.startInteraction(ev, extraOptions); // ensure interaction began
|
2624
|
+
|
2625
|
+
if (!this.isDragging) {
|
2626
|
+
this.isDragging = true;
|
2627
|
+
this.handleDragStart(ev);
|
2336
2628
|
}
|
2337
2629
|
},
|
2338
2630
|
|
2339
2631
|
|
2340
|
-
|
2341
|
-
|
2342
|
-
this.
|
2632
|
+
handleDragStart: function(ev) {
|
2633
|
+
this.trigger('dragStart', ev);
|
2634
|
+
this.initHrefHack();
|
2343
2635
|
},
|
2344
2636
|
|
2345
2637
|
|
2346
|
-
|
2347
|
-
|
2348
|
-
var
|
2349
|
-
var
|
2350
|
-
var minDistance;
|
2638
|
+
handleMove: function(ev) {
|
2639
|
+
var dx = getEvX(ev) - this.originX;
|
2640
|
+
var dy = getEvY(ev) - this.originY;
|
2641
|
+
var minDistance = this.minDistance;
|
2351
2642
|
var distanceSq; // current distance from the origin, squared
|
2352
2643
|
|
2353
|
-
if (!this.
|
2354
|
-
// then start the drag if the minimum distance criteria is met
|
2355
|
-
minDistance = this.options.distance || 1;
|
2644
|
+
if (!this.isDistanceSurpassed) {
|
2356
2645
|
distanceSq = dx * dx + dy * dy;
|
2357
2646
|
if (distanceSq >= minDistance * minDistance) { // use pythagorean theorem
|
2358
|
-
this.
|
2647
|
+
this.handleDistanceSurpassed(ev);
|
2359
2648
|
}
|
2360
2649
|
}
|
2361
2650
|
|
2362
2651
|
if (this.isDragging) {
|
2363
|
-
this.
|
2652
|
+
this.handleDrag(dx, dy, ev);
|
2364
2653
|
}
|
2365
2654
|
},
|
2366
2655
|
|
2367
2656
|
|
2368
|
-
//
|
2369
|
-
|
2370
|
-
|
2657
|
+
// Called while the mouse is being moved and when we know a legitimate drag is taking place
|
2658
|
+
handleDrag: function(dx, dy, ev) {
|
2659
|
+
this.trigger('drag', dx, dy, ev);
|
2660
|
+
this.updateAutoScroll(ev); // will possibly cause scrolling
|
2661
|
+
},
|
2371
2662
|
|
2372
|
-
if (!this.isListening) { // startDrag must have manually initiated
|
2373
|
-
this.startListening();
|
2374
|
-
}
|
2375
2663
|
|
2376
|
-
|
2377
|
-
|
2378
|
-
this.
|
2664
|
+
endDrag: function(ev) {
|
2665
|
+
if (this.isDragging) {
|
2666
|
+
this.isDragging = false;
|
2667
|
+
this.handleDragEnd(ev);
|
2379
2668
|
}
|
2380
2669
|
},
|
2381
2670
|
|
2382
2671
|
|
2383
|
-
|
2384
|
-
|
2385
|
-
|
2672
|
+
handleDragEnd: function(ev) {
|
2673
|
+
this.trigger('dragEnd', ev);
|
2674
|
+
this.destroyHrefHack();
|
2675
|
+
},
|
2386
2676
|
|
2387
|
-
this.trigger('dragStart', ev);
|
2388
2677
|
|
2389
|
-
|
2390
|
-
|
2391
|
-
|
2678
|
+
// Delay
|
2679
|
+
// -----------------------------------------------------------------------------------------------------------------
|
2680
|
+
|
2681
|
+
|
2682
|
+
startDelay: function(initialEv) {
|
2683
|
+
var _this = this;
|
2684
|
+
|
2685
|
+
if (this.delay) {
|
2686
|
+
this.delayTimeoutId = setTimeout(function() {
|
2687
|
+
_this.handleDelayEnd(initialEv);
|
2688
|
+
}, this.delay);
|
2689
|
+
}
|
2690
|
+
else {
|
2691
|
+
this.handleDelayEnd(initialEv);
|
2392
2692
|
}
|
2393
2693
|
},
|
2394
2694
|
|
2395
2695
|
|
2396
|
-
|
2397
|
-
|
2398
|
-
|
2399
|
-
this.
|
2696
|
+
handleDelayEnd: function(initialEv) {
|
2697
|
+
this.isDelayEnded = true;
|
2698
|
+
|
2699
|
+
if (this.isDistanceSurpassed) {
|
2700
|
+
this.startDrag(initialEv);
|
2701
|
+
}
|
2400
2702
|
},
|
2401
2703
|
|
2402
2704
|
|
2403
|
-
//
|
2404
|
-
|
2405
|
-
|
2705
|
+
// Distance
|
2706
|
+
// -----------------------------------------------------------------------------------------------------------------
|
2707
|
+
|
2708
|
+
|
2709
|
+
handleDistanceSurpassed: function(ev) {
|
2710
|
+
this.isDistanceSurpassed = true;
|
2711
|
+
|
2712
|
+
if (this.isDelayEnded) {
|
2713
|
+
this.startDrag(ev);
|
2714
|
+
}
|
2406
2715
|
},
|
2407
2716
|
|
2408
2717
|
|
2409
|
-
//
|
2410
|
-
//
|
2411
|
-
|
2718
|
+
// Mouse / Touch
|
2719
|
+
// -----------------------------------------------------------------------------------------------------------------
|
2720
|
+
|
2721
|
+
|
2722
|
+
handleTouchMove: function(ev) {
|
2723
|
+
// prevent inertia and touchmove-scrolling while dragging
|
2412
2724
|
if (this.isDragging) {
|
2413
|
-
|
2414
|
-
this.dragStop(ev);
|
2415
|
-
this.isDragging = false;
|
2725
|
+
ev.preventDefault();
|
2416
2726
|
}
|
2727
|
+
|
2728
|
+
this.handleMove(ev);
|
2417
2729
|
},
|
2418
2730
|
|
2419
2731
|
|
2420
|
-
|
2421
|
-
|
2422
|
-
|
2732
|
+
handleMouseMove: function(ev) {
|
2733
|
+
this.handleMove(ev);
|
2734
|
+
},
|
2423
2735
|
|
2424
|
-
this.trigger('dragStop', ev);
|
2425
2736
|
|
2426
|
-
|
2427
|
-
|
2428
|
-
if (_this.subjectHref) {
|
2429
|
-
_this.subjectEl.attr('href', _this.subjectHref);
|
2430
|
-
}
|
2431
|
-
}, 0);
|
2432
|
-
},
|
2737
|
+
// Scrolling (unrelated to auto-scroll)
|
2738
|
+
// -----------------------------------------------------------------------------------------------------------------
|
2433
2739
|
|
2434
2740
|
|
2435
|
-
|
2436
|
-
|
2437
|
-
|
2741
|
+
handleTouchScroll: function(ev) {
|
2742
|
+
// if the drag is being initiated by touch, but a scroll happens before
|
2743
|
+
// the drag-initiating delay is over, cancel the drag
|
2744
|
+
if (!this.isDragging) {
|
2745
|
+
this.endInteraction(ev, true); // isCancelled=true
|
2746
|
+
}
|
2747
|
+
},
|
2438
2748
|
|
2439
|
-
if (this.isListening) {
|
2440
2749
|
|
2441
|
-
|
2442
|
-
|
2443
|
-
this.scrollEl.off('scroll', this.scrollHandlerProxy);
|
2444
|
-
this.scrollHandlerProxy = null;
|
2445
|
-
}
|
2750
|
+
// <A> HREF Hack
|
2751
|
+
// -----------------------------------------------------------------------------------------------------------------
|
2446
2752
|
|
2447
|
-
$(document)
|
2448
|
-
.off('mousemove', this.mousemoveProxy)
|
2449
|
-
.off('mouseup', this.mouseupProxy)
|
2450
|
-
.off('selectstart', this.preventDefault);
|
2451
2753
|
|
2452
|
-
|
2453
|
-
|
2754
|
+
initHrefHack: function() {
|
2755
|
+
var subjectEl = this.subjectEl;
|
2454
2756
|
|
2455
|
-
|
2456
|
-
|
2757
|
+
// remove a mousedown'd <a>'s href so it is not visited (IE8 bug)
|
2758
|
+
if ((this.subjectHref = subjectEl ? subjectEl.attr('href') : null)) {
|
2759
|
+
subjectEl.removeAttr('href');
|
2457
2760
|
}
|
2458
2761
|
},
|
2459
2762
|
|
2460
2763
|
|
2461
|
-
|
2462
|
-
|
2463
|
-
this.
|
2764
|
+
destroyHrefHack: function() {
|
2765
|
+
var subjectEl = this.subjectEl;
|
2766
|
+
var subjectHref = this.subjectHref;
|
2767
|
+
|
2768
|
+
// restore a mousedown'd <a>'s href (for IE8 bug)
|
2769
|
+
setTimeout(function() { // must be outside of the click's execution
|
2770
|
+
if (subjectHref) {
|
2771
|
+
subjectEl.attr('href', subjectHref);
|
2772
|
+
}
|
2773
|
+
}, 0);
|
2464
2774
|
},
|
2465
2775
|
|
2466
2776
|
|
2777
|
+
// Utils
|
2778
|
+
// -----------------------------------------------------------------------------------------------------------------
|
2779
|
+
|
2780
|
+
|
2467
2781
|
// Triggers a callback. Calls a function in the option hash of the same name.
|
2468
2782
|
// Arguments beyond the first `name` are forwarded on.
|
2469
2783
|
trigger: function(name) {
|
2470
2784
|
if (this.options[name]) {
|
2471
2785
|
this.options[name].apply(this, Array.prototype.slice.call(arguments, 1));
|
2472
2786
|
}
|
2473
|
-
|
2787
|
+
// makes _methods callable by event name. TODO: kill this
|
2788
|
+
if (this['_' + name]) {
|
2789
|
+
this['_' + name].apply(this, Array.prototype.slice.call(arguments, 1));
|
2790
|
+
}
|
2791
|
+
}
|
2792
|
+
|
2793
|
+
|
2794
|
+
});
|
2795
|
+
|
2796
|
+
;;
|
2797
|
+
/*
|
2798
|
+
this.scrollEl is set in DragListener
|
2799
|
+
*/
|
2800
|
+
DragListener.mixin({
|
2801
|
+
|
2802
|
+
isAutoScroll: false,
|
2803
|
+
|
2804
|
+
scrollBounds: null, // { top, bottom, left, right }
|
2805
|
+
scrollTopVel: null, // pixels per second
|
2806
|
+
scrollLeftVel: null, // pixels per second
|
2807
|
+
scrollIntervalId: null, // ID of setTimeout for scrolling animation loop
|
2808
|
+
|
2809
|
+
// defaults
|
2810
|
+
scrollSensitivity: 30, // pixels from edge for scrolling to start
|
2811
|
+
scrollSpeed: 200, // pixels per second, at maximum speed
|
2812
|
+
scrollIntervalMs: 50, // millisecond wait between scroll increment
|
2813
|
+
|
2814
|
+
|
2815
|
+
initAutoScroll: function() {
|
2816
|
+
var scrollEl = this.scrollEl;
|
2474
2817
|
|
2818
|
+
this.isAutoScroll =
|
2819
|
+
this.options.scroll &&
|
2820
|
+
scrollEl &&
|
2821
|
+
!scrollEl.is(window) &&
|
2822
|
+
!scrollEl.is(document);
|
2475
2823
|
|
2476
|
-
|
2477
|
-
|
2478
|
-
|
2824
|
+
if (this.isAutoScroll) {
|
2825
|
+
// debounce makes sure rapid calls don't happen
|
2826
|
+
this.listenTo(scrollEl, 'scroll', debounce(this.handleDebouncedScroll, 100));
|
2827
|
+
}
|
2479
2828
|
},
|
2480
2829
|
|
2481
2830
|
|
2482
|
-
|
2483
|
-
|
2831
|
+
destroyAutoScroll: function() {
|
2832
|
+
this.endAutoScroll(); // kill any animation loop
|
2833
|
+
|
2834
|
+
// remove the scroll handler if there is a scrollEl
|
2835
|
+
if (this.isAutoScroll) {
|
2836
|
+
this.stopListeningTo(this.scrollEl, 'scroll'); // will probably get removed by unbindHandlers too :(
|
2837
|
+
}
|
2838
|
+
},
|
2484
2839
|
|
2485
2840
|
|
2486
2841
|
// Computes and stores the bounding rectangle of scrollEl
|
2487
2842
|
computeScrollBounds: function() {
|
2488
|
-
|
2489
|
-
|
2490
|
-
this.scrollBounds = el ? getOuterRect(el) : null;
|
2843
|
+
if (this.isAutoScroll) {
|
2844
|
+
this.scrollBounds = getOuterRect(this.scrollEl);
|
2491
2845
|
// TODO: use getClientRect in future. but prevents auto scrolling when on top of scrollbars
|
2846
|
+
}
|
2492
2847
|
},
|
2493
2848
|
|
2494
2849
|
|
2495
2850
|
// Called when the dragging is in progress and scrolling should be updated
|
2496
|
-
|
2851
|
+
updateAutoScroll: function(ev) {
|
2497
2852
|
var sensitivity = this.scrollSensitivity;
|
2498
2853
|
var bounds = this.scrollBounds;
|
2499
2854
|
var topCloseness, bottomCloseness;
|
@@ -2504,10 +2859,10 @@ var DragListener = FC.DragListener = Class.extend({
|
|
2504
2859
|
if (bounds) { // only scroll if scrollEl exists
|
2505
2860
|
|
2506
2861
|
// compute closeness to edges. valid range is from 0.0 - 1.0
|
2507
|
-
topCloseness = (sensitivity - (ev
|
2508
|
-
bottomCloseness = (sensitivity - (bounds.bottom - ev
|
2509
|
-
leftCloseness = (sensitivity - (ev
|
2510
|
-
rightCloseness = (sensitivity - (bounds.right - ev
|
2862
|
+
topCloseness = (sensitivity - (getEvY(ev) - bounds.top)) / sensitivity;
|
2863
|
+
bottomCloseness = (sensitivity - (bounds.bottom - getEvY(ev))) / sensitivity;
|
2864
|
+
leftCloseness = (sensitivity - (getEvX(ev) - bounds.left)) / sensitivity;
|
2865
|
+
rightCloseness = (sensitivity - (bounds.right - getEvX(ev))) / sensitivity;
|
2511
2866
|
|
2512
2867
|
// translate vertical closeness into velocity.
|
2513
2868
|
// mouse must be completely in bounds for velocity to happen.
|
@@ -2594,38 +2949,36 @@ var DragListener = FC.DragListener = Class.extend({
|
|
2594
2949
|
|
2595
2950
|
// if scrolled all the way, which causes the vels to be zero, stop the animation loop
|
2596
2951
|
if (!this.scrollTopVel && !this.scrollLeftVel) {
|
2597
|
-
this.
|
2952
|
+
this.endAutoScroll();
|
2598
2953
|
}
|
2599
2954
|
},
|
2600
2955
|
|
2601
2956
|
|
2602
2957
|
// Kills any existing scrolling animation loop
|
2603
|
-
|
2958
|
+
endAutoScroll: function() {
|
2604
2959
|
if (this.scrollIntervalId) {
|
2605
2960
|
clearInterval(this.scrollIntervalId);
|
2606
2961
|
this.scrollIntervalId = null;
|
2607
2962
|
|
2608
|
-
|
2609
|
-
this.scrollStop();
|
2963
|
+
this.handleScrollEnd();
|
2610
2964
|
}
|
2611
2965
|
},
|
2612
2966
|
|
2613
2967
|
|
2614
2968
|
// Get called when the scrollEl is scrolled (NOTE: this is delayed via debounce)
|
2615
|
-
|
2969
|
+
handleDebouncedScroll: function() {
|
2616
2970
|
// recompute all coordinates, but *only* if this is *not* part of our scrolling animation
|
2617
2971
|
if (!this.scrollIntervalId) {
|
2618
|
-
this.
|
2972
|
+
this.handleScrollEnd();
|
2619
2973
|
}
|
2620
2974
|
},
|
2621
2975
|
|
2622
2976
|
|
2623
2977
|
// Called when scrolling has stopped, whether through auto scroll, or the user scrolling
|
2624
|
-
|
2978
|
+
handleScrollEnd: function() {
|
2625
2979
|
}
|
2626
2980
|
|
2627
2981
|
});
|
2628
|
-
|
2629
2982
|
;;
|
2630
2983
|
|
2631
2984
|
/* Tracks mouse movements over a component and raises events about which hit the mouse is over.
|
@@ -2654,18 +3007,16 @@ var HitDragListener = DragListener.extend({
|
|
2654
3007
|
|
2655
3008
|
// Called when drag listening starts (but a real drag has not necessarily began).
|
2656
3009
|
// ev might be undefined if dragging was started manually.
|
2657
|
-
|
3010
|
+
handleInteractionStart: function(ev) {
|
2658
3011
|
var subjectEl = this.subjectEl;
|
2659
3012
|
var subjectRect;
|
2660
3013
|
var origPoint;
|
2661
3014
|
var point;
|
2662
3015
|
|
2663
|
-
DragListener.prototype.listenStart.apply(this, arguments); // call the super-method
|
2664
|
-
|
2665
3016
|
this.computeCoords();
|
2666
3017
|
|
2667
3018
|
if (ev) {
|
2668
|
-
origPoint = { left: ev
|
3019
|
+
origPoint = { left: getEvX(ev), top: getEvY(ev) };
|
2669
3020
|
point = origPoint;
|
2670
3021
|
|
2671
3022
|
// constrain the point to bounds of the element being dragged
|
@@ -2695,61 +3046,64 @@ var HitDragListener = DragListener.extend({
|
|
2695
3046
|
this.origHit = null;
|
2696
3047
|
this.coordAdjust = null;
|
2697
3048
|
}
|
3049
|
+
|
3050
|
+
// call the super-method. do it after origHit has been computed
|
3051
|
+
DragListener.prototype.handleInteractionStart.apply(this, arguments);
|
2698
3052
|
},
|
2699
3053
|
|
2700
3054
|
|
2701
3055
|
// Recomputes the drag-critical positions of elements
|
2702
3056
|
computeCoords: function() {
|
2703
3057
|
this.component.prepareHits();
|
2704
|
-
this.computeScrollBounds(); // why is this here
|
3058
|
+
this.computeScrollBounds(); // why is this here??????
|
2705
3059
|
},
|
2706
3060
|
|
2707
3061
|
|
2708
3062
|
// Called when the actual drag has started
|
2709
|
-
|
3063
|
+
handleDragStart: function(ev) {
|
2710
3064
|
var hit;
|
2711
3065
|
|
2712
|
-
DragListener.prototype.
|
3066
|
+
DragListener.prototype.handleDragStart.apply(this, arguments); // call the super-method
|
2713
3067
|
|
2714
3068
|
// might be different from this.origHit if the min-distance is large
|
2715
|
-
hit = this.queryHit(ev
|
3069
|
+
hit = this.queryHit(getEvX(ev), getEvY(ev));
|
2716
3070
|
|
2717
3071
|
// report the initial hit the mouse is over
|
2718
3072
|
// especially important if no min-distance and drag starts immediately
|
2719
3073
|
if (hit) {
|
2720
|
-
this.
|
3074
|
+
this.handleHitOver(hit);
|
2721
3075
|
}
|
2722
3076
|
},
|
2723
3077
|
|
2724
3078
|
|
2725
3079
|
// Called when the drag moves
|
2726
|
-
|
3080
|
+
handleDrag: function(dx, dy, ev) {
|
2727
3081
|
var hit;
|
2728
3082
|
|
2729
|
-
DragListener.prototype.
|
3083
|
+
DragListener.prototype.handleDrag.apply(this, arguments); // call the super-method
|
2730
3084
|
|
2731
|
-
hit = this.queryHit(ev
|
3085
|
+
hit = this.queryHit(getEvX(ev), getEvY(ev));
|
2732
3086
|
|
2733
3087
|
if (!isHitsEqual(hit, this.hit)) { // a different hit than before?
|
2734
3088
|
if (this.hit) {
|
2735
|
-
this.
|
3089
|
+
this.handleHitOut();
|
2736
3090
|
}
|
2737
3091
|
if (hit) {
|
2738
|
-
this.
|
3092
|
+
this.handleHitOver(hit);
|
2739
3093
|
}
|
2740
3094
|
}
|
2741
3095
|
},
|
2742
3096
|
|
2743
3097
|
|
2744
3098
|
// Called when dragging has been stopped
|
2745
|
-
|
2746
|
-
this.
|
2747
|
-
DragListener.prototype.
|
3099
|
+
handleDragEnd: function() {
|
3100
|
+
this.handleHitDone();
|
3101
|
+
DragListener.prototype.handleDragEnd.apply(this, arguments); // call the super-method
|
2748
3102
|
},
|
2749
3103
|
|
2750
3104
|
|
2751
3105
|
// Called when a the mouse has just moved over a new hit
|
2752
|
-
|
3106
|
+
handleHitOver: function(hit) {
|
2753
3107
|
var isOrig = isHitsEqual(hit, this.origHit);
|
2754
3108
|
|
2755
3109
|
this.hit = hit;
|
@@ -2759,26 +3113,26 @@ var HitDragListener = DragListener.extend({
|
|
2759
3113
|
|
2760
3114
|
|
2761
3115
|
// Called when the mouse has just moved out of a hit
|
2762
|
-
|
3116
|
+
handleHitOut: function() {
|
2763
3117
|
if (this.hit) {
|
2764
3118
|
this.trigger('hitOut', this.hit);
|
2765
|
-
this.
|
3119
|
+
this.handleHitDone();
|
2766
3120
|
this.hit = null;
|
2767
3121
|
}
|
2768
3122
|
},
|
2769
3123
|
|
2770
3124
|
|
2771
3125
|
// Called after a hitOut. Also called before a dragStop
|
2772
|
-
|
3126
|
+
handleHitDone: function() {
|
2773
3127
|
if (this.hit) {
|
2774
3128
|
this.trigger('hitDone', this.hit);
|
2775
3129
|
}
|
2776
3130
|
},
|
2777
3131
|
|
2778
3132
|
|
2779
|
-
// Called when drag
|
2780
|
-
|
2781
|
-
DragListener.prototype.
|
3133
|
+
// Called when the interaction ends, whether there was a real drag or not
|
3134
|
+
handleInteractionEnd: function() {
|
3135
|
+
DragListener.prototype.handleInteractionEnd.apply(this, arguments); // call the super-method
|
2782
3136
|
|
2783
3137
|
this.origHit = null;
|
2784
3138
|
this.hit = null;
|
@@ -2788,8 +3142,8 @@ var HitDragListener = DragListener.extend({
|
|
2788
3142
|
|
2789
3143
|
|
2790
3144
|
// Called when scrolling has stopped, whether through auto scroll, or the user scrolling
|
2791
|
-
|
2792
|
-
DragListener.prototype.
|
3145
|
+
handleScrollEnd: function() {
|
3146
|
+
DragListener.prototype.handleScrollEnd.apply(this, arguments); // call the super-method
|
2793
3147
|
|
2794
3148
|
this.computeCoords(); // hits' absolute positions will be in new places. recompute
|
2795
3149
|
},
|
@@ -2844,7 +3198,7 @@ function isHitPropsWithin(subHit, superHit) {
|
|
2844
3198
|
/* Creates a clone of an element and lets it track the mouse as it moves
|
2845
3199
|
----------------------------------------------------------------------------------------------------------------------*/
|
2846
3200
|
|
2847
|
-
var MouseFollower = Class.extend({
|
3201
|
+
var MouseFollower = Class.extend(ListenerMixin, {
|
2848
3202
|
|
2849
3203
|
options: null,
|
2850
3204
|
|
@@ -2856,16 +3210,14 @@ var MouseFollower = Class.extend({
|
|
2856
3210
|
top0: null,
|
2857
3211
|
left0: null,
|
2858
3212
|
|
2859
|
-
// the
|
2860
|
-
|
2861
|
-
|
3213
|
+
// the absolute coordinates of the initiating touch/mouse action
|
3214
|
+
y0: null,
|
3215
|
+
x0: null,
|
2862
3216
|
|
2863
3217
|
// the number of pixels the mouse has moved from its initial position
|
2864
3218
|
topDelta: null,
|
2865
3219
|
leftDelta: null,
|
2866
3220
|
|
2867
|
-
mousemoveProxy: null, // document mousemove handler, bound to the MouseFollower's `this`
|
2868
|
-
|
2869
3221
|
isFollowing: false,
|
2870
3222
|
isHidden: false,
|
2871
3223
|
isAnimating: false, // doing the revert animation?
|
@@ -2882,8 +3234,8 @@ var MouseFollower = Class.extend({
|
|
2882
3234
|
if (!this.isFollowing) {
|
2883
3235
|
this.isFollowing = true;
|
2884
3236
|
|
2885
|
-
this.
|
2886
|
-
this.
|
3237
|
+
this.y0 = getEvY(ev);
|
3238
|
+
this.x0 = getEvX(ev);
|
2887
3239
|
this.topDelta = 0;
|
2888
3240
|
this.leftDelta = 0;
|
2889
3241
|
|
@@ -2891,7 +3243,12 @@ var MouseFollower = Class.extend({
|
|
2891
3243
|
this.updatePosition();
|
2892
3244
|
}
|
2893
3245
|
|
2894
|
-
|
3246
|
+
if (getEvIsTouch(ev)) {
|
3247
|
+
this.listenTo($(document), 'touchmove', this.handleMove);
|
3248
|
+
}
|
3249
|
+
else {
|
3250
|
+
this.listenTo($(document), 'mousemove', this.handleMove);
|
3251
|
+
}
|
2895
3252
|
}
|
2896
3253
|
},
|
2897
3254
|
|
@@ -2916,7 +3273,7 @@ var MouseFollower = Class.extend({
|
|
2916
3273
|
if (this.isFollowing && !this.isAnimating) { // disallow more than one stop animation at a time
|
2917
3274
|
this.isFollowing = false;
|
2918
3275
|
|
2919
|
-
$(document)
|
3276
|
+
this.stopListeningTo($(document));
|
2920
3277
|
|
2921
3278
|
if (shouldRevert && revertDuration && !this.isHidden) { // do a revert animation?
|
2922
3279
|
this.isAnimating = true;
|
@@ -2942,6 +3299,7 @@ var MouseFollower = Class.extend({
|
|
2942
3299
|
if (!el) {
|
2943
3300
|
this.sourceEl.width(); // hack to force IE8 to compute correct bounding box
|
2944
3301
|
el = this.el = this.sourceEl.clone()
|
3302
|
+
.addClass(this.options.additionalClass || '')
|
2945
3303
|
.css({
|
2946
3304
|
position: 'absolute',
|
2947
3305
|
visibility: '', // in case original element was hidden (commonly through hideEvents())
|
@@ -2953,8 +3311,13 @@ var MouseFollower = Class.extend({
|
|
2953
3311
|
height: this.sourceEl.height(), // explicit width in case there was a 'bottom' value
|
2954
3312
|
opacity: this.options.opacity || '',
|
2955
3313
|
zIndex: this.options.zIndex
|
2956
|
-
})
|
2957
|
-
|
3314
|
+
});
|
3315
|
+
|
3316
|
+
// we don't want long taps or any mouse interaction causing selection/menus.
|
3317
|
+
// would use preventSelection(), but that prevents selectstart, causing problems.
|
3318
|
+
el.addClass('fc-unselectable');
|
3319
|
+
|
3320
|
+
el.appendTo(this.parentEl);
|
2958
3321
|
}
|
2959
3322
|
|
2960
3323
|
return el;
|
@@ -2994,9 +3357,9 @@ var MouseFollower = Class.extend({
|
|
2994
3357
|
|
2995
3358
|
|
2996
3359
|
// Gets called when the user moves the mouse
|
2997
|
-
|
2998
|
-
this.topDelta = ev
|
2999
|
-
this.leftDelta = ev
|
3360
|
+
handleMove: function(ev) {
|
3361
|
+
this.topDelta = getEvY(ev) - this.y0;
|
3362
|
+
this.leftDelta = getEvX(ev) - this.x0;
|
3000
3363
|
|
3001
3364
|
if (!this.isHidden) {
|
3002
3365
|
this.updatePosition();
|
@@ -3031,7 +3394,7 @@ var MouseFollower = Class.extend({
|
|
3031
3394
|
/* An abstract class comprised of a "grid" of areas that each represent a specific datetime
|
3032
3395
|
----------------------------------------------------------------------------------------------------------------------*/
|
3033
3396
|
|
3034
|
-
var Grid = FC.Grid = Class.extend({
|
3397
|
+
var Grid = FC.Grid = Class.extend(ListenerMixin, MouseIgnorerMixin, {
|
3035
3398
|
|
3036
3399
|
view: null, // a View object
|
3037
3400
|
isRTL: null, // shortcut to the view's isRTL option
|
@@ -3042,8 +3405,6 @@ var Grid = FC.Grid = Class.extend({
|
|
3042
3405
|
el: null, // the containing element
|
3043
3406
|
elsByFill: null, // a hash of jQuery element sets used for rendering each fill. Keyed by fill name.
|
3044
3407
|
|
3045
|
-
externalDragStartProxy: null, // binds the Grid's scope to externalDragStart (in DayGrid.events)
|
3046
|
-
|
3047
3408
|
// derived from options
|
3048
3409
|
eventTimeFormat: null,
|
3049
3410
|
displayEventTime: null,
|
@@ -3056,13 +3417,19 @@ var Grid = FC.Grid = Class.extend({
|
|
3056
3417
|
// TODO: port isTimeScale into same system?
|
3057
3418
|
largeUnit: null,
|
3058
3419
|
|
3420
|
+
dayDragListener: null,
|
3421
|
+
segDragListener: null,
|
3422
|
+
segResizeListener: null,
|
3423
|
+
externalDragListener: null,
|
3424
|
+
|
3059
3425
|
|
3060
3426
|
constructor: function(view) {
|
3061
3427
|
this.view = view;
|
3062
3428
|
this.isRTL = view.opt('isRTL');
|
3063
|
-
|
3064
3429
|
this.elsByFill = {};
|
3065
|
-
|
3430
|
+
|
3431
|
+
this.dayDragListener = this.buildDayDragListener();
|
3432
|
+
this.initMouseIgnoring();
|
3066
3433
|
},
|
3067
3434
|
|
3068
3435
|
|
@@ -3195,26 +3562,33 @@ var Grid = FC.Grid = Class.extend({
|
|
3195
3562
|
// Sets the container element that the grid should render inside of.
|
3196
3563
|
// Does other DOM-related initializations.
|
3197
3564
|
setElement: function(el) {
|
3198
|
-
var _this = this;
|
3199
|
-
|
3200
3565
|
this.el = el;
|
3566
|
+
preventSelection(el);
|
3567
|
+
|
3568
|
+
this.bindDayHandler('touchstart', this.dayTouchStart);
|
3569
|
+
this.bindDayHandler('mousedown', this.dayMousedown);
|
3570
|
+
|
3571
|
+
// attach event-element-related handlers. in Grid.events
|
3572
|
+
// same garbage collection note as above.
|
3573
|
+
this.bindSegHandlers();
|
3574
|
+
|
3575
|
+
this.bindGlobalHandlers();
|
3576
|
+
},
|
3577
|
+
|
3578
|
+
|
3579
|
+
bindDayHandler: function(name, handler) {
|
3580
|
+
var _this = this;
|
3201
3581
|
|
3202
3582
|
// attach a handler to the grid's root element.
|
3203
3583
|
// jQuery will take care of unregistering them when removeElement gets called.
|
3204
|
-
el.on(
|
3584
|
+
this.el.on(name, function(ev) {
|
3205
3585
|
if (
|
3206
3586
|
!$(ev.target).is('.fc-event-container *, .fc-more') && // not an an event element, or "more.." link
|
3207
3587
|
!$(ev.target).closest('.fc-popover').length // not on a popover (like the "more.." events one)
|
3208
3588
|
) {
|
3209
|
-
|
3589
|
+
return handler.call(_this, ev);
|
3210
3590
|
}
|
3211
3591
|
});
|
3212
|
-
|
3213
|
-
// attach event-element-related handlers. in Grid.events
|
3214
|
-
// same garbage collection note as above.
|
3215
|
-
this.bindSegHandlers();
|
3216
|
-
|
3217
|
-
this.bindGlobalHandlers();
|
3218
3592
|
},
|
3219
3593
|
|
3220
3594
|
|
@@ -3222,6 +3596,7 @@ var Grid = FC.Grid = Class.extend({
|
|
3222
3596
|
// DOES NOT remove any content beforehand (doesn't clear events or call unrenderDates), unlike View
|
3223
3597
|
removeElement: function() {
|
3224
3598
|
this.unbindGlobalHandlers();
|
3599
|
+
this.clearDragListeners();
|
3225
3600
|
|
3226
3601
|
this.el.remove();
|
3227
3602
|
|
@@ -3254,18 +3629,47 @@ var Grid = FC.Grid = Class.extend({
|
|
3254
3629
|
|
3255
3630
|
// Binds DOM handlers to elements that reside outside the grid, such as the document
|
3256
3631
|
bindGlobalHandlers: function() {
|
3257
|
-
$(document)
|
3632
|
+
this.listenTo($(document), {
|
3633
|
+
dragstart: this.externalDragStart, // jqui
|
3634
|
+
sortstart: this.externalDragStart // jqui
|
3635
|
+
});
|
3258
3636
|
},
|
3259
3637
|
|
3260
3638
|
|
3261
3639
|
// Unbinds DOM handlers from elements that reside outside the grid
|
3262
3640
|
unbindGlobalHandlers: function() {
|
3263
|
-
$(document)
|
3641
|
+
this.stopListeningTo($(document));
|
3264
3642
|
},
|
3265
3643
|
|
3266
3644
|
|
3267
3645
|
// Process a mousedown on an element that represents a day. For day clicking and selecting.
|
3268
3646
|
dayMousedown: function(ev) {
|
3647
|
+
if (!this.isIgnoringMouse) {
|
3648
|
+
this.dayDragListener.startInteraction(ev, {
|
3649
|
+
//distance: 5, // needs more work if we want dayClick to fire correctly
|
3650
|
+
});
|
3651
|
+
}
|
3652
|
+
},
|
3653
|
+
|
3654
|
+
|
3655
|
+
dayTouchStart: function(ev) {
|
3656
|
+
var view = this.view;
|
3657
|
+
|
3658
|
+
// HACK to prevent a user's clickaway for unselecting a range or an event
|
3659
|
+
// from causing a dayClick.
|
3660
|
+
if (view.isSelected || view.selectedEvent) {
|
3661
|
+
this.tempIgnoreMouse();
|
3662
|
+
}
|
3663
|
+
|
3664
|
+
this.dayDragListener.startInteraction(ev, {
|
3665
|
+
delay: this.view.opt('longPressDelay')
|
3666
|
+
});
|
3667
|
+
},
|
3668
|
+
|
3669
|
+
|
3670
|
+
// Creates a listener that tracks the user's drag across day elements.
|
3671
|
+
// For day clicking and selecting.
|
3672
|
+
buildDayDragListener: function() {
|
3269
3673
|
var _this = this;
|
3270
3674
|
var view = this.view;
|
3271
3675
|
var isSelectable = view.opt('selectable');
|
@@ -3276,14 +3680,21 @@ var Grid = FC.Grid = Class.extend({
|
|
3276
3680
|
// if the drag ends on the same day, it is a 'dayClick'.
|
3277
3681
|
// if 'selectable' is enabled, this listener also detects selections.
|
3278
3682
|
var dragListener = new HitDragListener(this, {
|
3279
|
-
//distance: 5, // needs more work if we want dayClick to fire correctly
|
3280
3683
|
scroll: view.opt('dragScroll'),
|
3684
|
+
interactionStart: function() {
|
3685
|
+
dayClickHit = dragListener.origHit; // for dayClick, where no dragging happens
|
3686
|
+
},
|
3281
3687
|
dragStart: function() {
|
3282
3688
|
view.unselect(); // since we could be rendering a new selection, we want to clear any old one
|
3283
3689
|
},
|
3284
3690
|
hitOver: function(hit, isOrig, origHit) {
|
3285
3691
|
if (origHit) { // click needs to have started on a hit
|
3286
|
-
|
3692
|
+
|
3693
|
+
// if user dragged to another cell at any point, it can no longer be a dayClick
|
3694
|
+
if (!isOrig) {
|
3695
|
+
dayClickHit = null;
|
3696
|
+
}
|
3697
|
+
|
3287
3698
|
if (isSelectable) {
|
3288
3699
|
selectionSpan = _this.computeSelection(
|
3289
3700
|
_this.getHitSpan(origHit),
|
@@ -3304,23 +3715,46 @@ var Grid = FC.Grid = Class.extend({
|
|
3304
3715
|
_this.unrenderSelection();
|
3305
3716
|
enableCursor();
|
3306
3717
|
},
|
3307
|
-
|
3308
|
-
if (
|
3309
|
-
|
3310
|
-
|
3311
|
-
_this.
|
3312
|
-
|
3313
|
-
|
3314
|
-
|
3315
|
-
|
3316
|
-
|
3317
|
-
|
3718
|
+
interactionEnd: function(ev, isCancelled) {
|
3719
|
+
if (!isCancelled) {
|
3720
|
+
if (
|
3721
|
+
dayClickHit &&
|
3722
|
+
!_this.isIgnoringMouse // see hack in dayTouchStart
|
3723
|
+
) {
|
3724
|
+
view.triggerDayClick(
|
3725
|
+
_this.getHitSpan(dayClickHit),
|
3726
|
+
_this.getHitEl(dayClickHit),
|
3727
|
+
ev
|
3728
|
+
);
|
3729
|
+
}
|
3730
|
+
if (selectionSpan) {
|
3731
|
+
// the selection will already have been rendered. just report it
|
3732
|
+
view.reportSelection(selectionSpan, ev);
|
3733
|
+
}
|
3734
|
+
enableCursor();
|
3318
3735
|
}
|
3319
|
-
enableCursor();
|
3320
3736
|
}
|
3321
3737
|
});
|
3322
3738
|
|
3323
|
-
dragListener
|
3739
|
+
return dragListener;
|
3740
|
+
},
|
3741
|
+
|
3742
|
+
|
3743
|
+
// Kills all in-progress dragging.
|
3744
|
+
// Useful for when public API methods that result in re-rendering are invoked during a drag.
|
3745
|
+
// Also useful for when touch devices misbehave and don't fire their touchend.
|
3746
|
+
clearDragListeners: function() {
|
3747
|
+
this.dayDragListener.endInteraction();
|
3748
|
+
|
3749
|
+
if (this.segDragListener) {
|
3750
|
+
this.segDragListener.endInteraction(); // will clear this.segDragListener
|
3751
|
+
}
|
3752
|
+
if (this.segResizeListener) {
|
3753
|
+
this.segResizeListener.endInteraction(); // will clear this.segResizeListener
|
3754
|
+
}
|
3755
|
+
if (this.externalDragListener) {
|
3756
|
+
this.externalDragListener.endInteraction(); // will clear this.externalDragListener
|
3757
|
+
}
|
3324
3758
|
},
|
3325
3759
|
|
3326
3760
|
|
@@ -3330,10 +3764,11 @@ var Grid = FC.Grid = Class.extend({
|
|
3330
3764
|
|
3331
3765
|
|
3332
3766
|
// Renders a mock event at the given event location, which contains zoned start/end properties.
|
3767
|
+
// Returns all mock event elements.
|
3333
3768
|
renderEventLocationHelper: function(eventLocation, sourceSeg) {
|
3334
3769
|
var fakeEvent = this.fabricateHelperEvent(eventLocation, sourceSeg);
|
3335
3770
|
|
3336
|
-
this.renderHelper(fakeEvent, sourceSeg); // do the actual rendering
|
3771
|
+
return this.renderHelper(fakeEvent, sourceSeg); // do the actual rendering
|
3337
3772
|
},
|
3338
3773
|
|
3339
3774
|
|
@@ -3361,6 +3796,7 @@ var Grid = FC.Grid = Class.extend({
|
|
3361
3796
|
|
3362
3797
|
|
3363
3798
|
// Renders a mock event. Given zoned event date properties.
|
3799
|
+
// Must return all mock event elements.
|
3364
3800
|
renderHelper: function(eventLocation, sourceSeg) {
|
3365
3801
|
// subclasses must implement
|
3366
3802
|
},
|
@@ -3538,7 +3974,7 @@ var Grid = FC.Grid = Class.extend({
|
|
3538
3974
|
fillSegTag: 'div', // subclasses can override
|
3539
3975
|
|
3540
3976
|
|
3541
|
-
// Builds the HTML needed for one fill segment. Generic
|
3977
|
+
// Builds the HTML needed for one fill segment. Generic enough to work with different types.
|
3542
3978
|
fillSegHtml: function(type, seg) {
|
3543
3979
|
|
3544
3980
|
// custom hooks per-type
|
@@ -3640,7 +4076,8 @@ Grid.mixin({
|
|
3640
4076
|
|
3641
4077
|
// Unrenders all events currently rendered on the grid
|
3642
4078
|
unrenderEvents: function() {
|
3643
|
-
this.
|
4079
|
+
this.handleSegMouseout(); // trigger an eventMouseout if user's mouse is over an event
|
4080
|
+
this.clearDragListeners();
|
3644
4081
|
|
3645
4082
|
this.unrenderFgSegs();
|
3646
4083
|
this.unrenderBgSegs();
|
@@ -3768,48 +4205,44 @@ Grid.mixin({
|
|
3768
4205
|
|
3769
4206
|
// Attaches event-element-related handlers to the container element and leverage bubbling
|
3770
4207
|
bindSegHandlers: function() {
|
4208
|
+
this.bindSegHandler('touchstart', this.handleSegTouchStart);
|
4209
|
+
this.bindSegHandler('touchend', this.handleSegTouchEnd);
|
4210
|
+
this.bindSegHandler('mouseenter', this.handleSegMouseover);
|
4211
|
+
this.bindSegHandler('mouseleave', this.handleSegMouseout);
|
4212
|
+
this.bindSegHandler('mousedown', this.handleSegMousedown);
|
4213
|
+
this.bindSegHandler('click', this.handleSegClick);
|
4214
|
+
},
|
4215
|
+
|
4216
|
+
|
4217
|
+
// Executes a handler for any a user-interaction on a segment.
|
4218
|
+
// Handler gets called with (seg, ev), and with the `this` context of the Grid
|
4219
|
+
bindSegHandler: function(name, handler) {
|
3771
4220
|
var _this = this;
|
3772
|
-
var view = this.view;
|
3773
4221
|
|
3774
|
-
|
3775
|
-
|
3776
|
-
|
3777
|
-
|
3778
|
-
|
3779
|
-
|
3780
|
-
_this.triggerSegMouseout(seg, ev);
|
3781
|
-
},
|
3782
|
-
click: function(seg, ev) {
|
3783
|
-
return view.trigger('eventClick', this, seg.event, ev); // can return `false` to cancel
|
3784
|
-
},
|
3785
|
-
mousedown: function(seg, ev) {
|
3786
|
-
if ($(ev.target).is('.fc-resizer') && view.isEventResizable(seg.event)) {
|
3787
|
-
_this.segResizeMousedown(seg, ev, $(ev.target).is('.fc-start-resizer'));
|
3788
|
-
}
|
3789
|
-
else if (view.isEventDraggable(seg.event)) {
|
3790
|
-
_this.segDragMousedown(seg, ev);
|
3791
|
-
}
|
3792
|
-
}
|
3793
|
-
},
|
3794
|
-
function(name, func) {
|
3795
|
-
// attach the handler to the container element and only listen for real event elements via bubbling
|
3796
|
-
_this.el.on(name, '.fc-event-container > *', function(ev) {
|
3797
|
-
var seg = $(this).data('fc-seg'); // grab segment data. put there by View::renderEvents
|
3798
|
-
|
3799
|
-
// only call the handlers if there is not a drag/resize in progress
|
3800
|
-
if (seg && !_this.isDraggingSeg && !_this.isResizingSeg) {
|
3801
|
-
return func.call(this, seg, ev); // `this` will be the event element
|
3802
|
-
}
|
3803
|
-
});
|
4222
|
+
this.el.on(name, '.fc-event-container > *', function(ev) {
|
4223
|
+
var seg = $(this).data('fc-seg'); // grab segment data. put there by View::renderEvents
|
4224
|
+
|
4225
|
+
// only call the handlers if there is not a drag/resize in progress
|
4226
|
+
if (seg && !_this.isDraggingSeg && !_this.isResizingSeg) {
|
4227
|
+
return handler.call(_this, seg, ev); // context will be the Grid
|
3804
4228
|
}
|
3805
|
-
);
|
4229
|
+
});
|
4230
|
+
},
|
4231
|
+
|
4232
|
+
|
4233
|
+
handleSegClick: function(seg, ev) {
|
4234
|
+
return this.view.trigger('eventClick', seg.el[0], seg.event, ev); // can return `false` to cancel
|
3806
4235
|
},
|
3807
4236
|
|
3808
4237
|
|
3809
4238
|
// Updates internal state and triggers handlers for when an event element is moused over
|
3810
|
-
|
3811
|
-
if (
|
4239
|
+
handleSegMouseover: function(seg, ev) {
|
4240
|
+
if (
|
4241
|
+
!this.isIgnoringMouse &&
|
4242
|
+
!this.mousedOverSeg
|
4243
|
+
) {
|
3812
4244
|
this.mousedOverSeg = seg;
|
4245
|
+
seg.el.addClass('fc-allow-mouse-resize');
|
3813
4246
|
this.view.trigger('eventMouseover', seg.el[0], seg.event, ev);
|
3814
4247
|
}
|
3815
4248
|
},
|
@@ -3817,56 +4250,132 @@ Grid.mixin({
|
|
3817
4250
|
|
3818
4251
|
// Updates internal state and triggers handlers for when an event element is moused out.
|
3819
4252
|
// Can be given no arguments, in which case it will mouseout the segment that was previously moused over.
|
3820
|
-
|
4253
|
+
handleSegMouseout: function(seg, ev) {
|
3821
4254
|
ev = ev || {}; // if given no args, make a mock mouse event
|
3822
4255
|
|
3823
4256
|
if (this.mousedOverSeg) {
|
3824
4257
|
seg = seg || this.mousedOverSeg; // if given no args, use the currently moused-over segment
|
3825
4258
|
this.mousedOverSeg = null;
|
4259
|
+
seg.el.removeClass('fc-allow-mouse-resize');
|
3826
4260
|
this.view.trigger('eventMouseout', seg.el[0], seg.event, ev);
|
3827
4261
|
}
|
3828
4262
|
},
|
3829
4263
|
|
3830
4264
|
|
4265
|
+
handleSegMousedown: function(seg, ev) {
|
4266
|
+
var isResizing = this.startSegResize(seg, ev, { distance: 5 });
|
4267
|
+
|
4268
|
+
if (!isResizing && this.view.isEventDraggable(seg.event)) {
|
4269
|
+
this.buildSegDragListener(seg)
|
4270
|
+
.startInteraction(ev, {
|
4271
|
+
distance: 5
|
4272
|
+
});
|
4273
|
+
}
|
4274
|
+
},
|
4275
|
+
|
4276
|
+
|
4277
|
+
handleSegTouchStart: function(seg, ev) {
|
4278
|
+
var view = this.view;
|
4279
|
+
var event = seg.event;
|
4280
|
+
var isSelected = view.isEventSelected(event);
|
4281
|
+
var isDraggable = view.isEventDraggable(event);
|
4282
|
+
var isResizable = view.isEventResizable(event);
|
4283
|
+
var isResizing = false;
|
4284
|
+
var dragListener;
|
4285
|
+
|
4286
|
+
if (isSelected && isResizable) {
|
4287
|
+
// only allow resizing of the event is selected
|
4288
|
+
isResizing = this.startSegResize(seg, ev);
|
4289
|
+
}
|
4290
|
+
|
4291
|
+
if (!isResizing && (isDraggable || isResizable)) { // allowed to be selected?
|
4292
|
+
|
4293
|
+
dragListener = isDraggable ?
|
4294
|
+
this.buildSegDragListener(seg) :
|
4295
|
+
this.buildSegSelectListener(seg); // seg isn't draggable, but still needs to be selected
|
4296
|
+
|
4297
|
+
dragListener.startInteraction(ev, { // won't start if already started
|
4298
|
+
delay: isSelected ? 0 : this.view.opt('longPressDelay') // do delay if not already selected
|
4299
|
+
});
|
4300
|
+
}
|
4301
|
+
|
4302
|
+
// a long tap simulates a mouseover. ignore this bogus mouseover.
|
4303
|
+
this.tempIgnoreMouse();
|
4304
|
+
},
|
4305
|
+
|
4306
|
+
|
4307
|
+
handleSegTouchEnd: function(seg, ev) {
|
4308
|
+
// touchstart+touchend = click, which simulates a mouseover.
|
4309
|
+
// ignore this bogus mouseover.
|
4310
|
+
this.tempIgnoreMouse();
|
4311
|
+
},
|
4312
|
+
|
4313
|
+
|
4314
|
+
// returns boolean whether resizing actually started or not.
|
4315
|
+
// assumes the seg allows resizing.
|
4316
|
+
// `dragOptions` are optional.
|
4317
|
+
startSegResize: function(seg, ev, dragOptions) {
|
4318
|
+
if ($(ev.target).is('.fc-resizer')) {
|
4319
|
+
this.buildSegResizeListener(seg, $(ev.target).is('.fc-start-resizer'))
|
4320
|
+
.startInteraction(ev, dragOptions);
|
4321
|
+
return true;
|
4322
|
+
}
|
4323
|
+
return false;
|
4324
|
+
},
|
4325
|
+
|
4326
|
+
|
4327
|
+
|
3831
4328
|
/* Event Dragging
|
3832
4329
|
------------------------------------------------------------------------------------------------------------------*/
|
3833
4330
|
|
3834
4331
|
|
3835
|
-
//
|
4332
|
+
// Builds a listener that will track user-dragging on an event segment.
|
3836
4333
|
// Generic enough to work with any type of Grid.
|
3837
|
-
|
4334
|
+
// Has side effect of setting/unsetting `segDragListener`
|
4335
|
+
buildSegDragListener: function(seg) {
|
3838
4336
|
var _this = this;
|
3839
4337
|
var view = this.view;
|
3840
4338
|
var calendar = view.calendar;
|
3841
4339
|
var el = seg.el;
|
3842
4340
|
var event = seg.event;
|
4341
|
+
var isDragging;
|
4342
|
+
var mouseFollower; // A clone of the original element that will move with the mouse
|
3843
4343
|
var dropLocation; // zoned event date properties
|
3844
4344
|
|
3845
|
-
|
3846
|
-
|
3847
|
-
|
3848
|
-
opacity: view.opt('dragOpacity'),
|
3849
|
-
revertDuration: view.opt('dragRevertDuration'),
|
3850
|
-
zIndex: 2 // one above the .fc-view
|
3851
|
-
});
|
4345
|
+
if (this.segDragListener) {
|
4346
|
+
return this.segDragListener;
|
4347
|
+
}
|
3852
4348
|
|
3853
4349
|
// Tracks mouse movement over the *view's* coordinate map. Allows dragging and dropping between subcomponents
|
3854
4350
|
// of the view.
|
3855
|
-
var dragListener = new HitDragListener(view, {
|
3856
|
-
distance: 5,
|
4351
|
+
var dragListener = this.segDragListener = new HitDragListener(view, {
|
3857
4352
|
scroll: view.opt('dragScroll'),
|
3858
4353
|
subjectEl: el,
|
3859
4354
|
subjectCenter: true,
|
3860
|
-
|
4355
|
+
interactionStart: function(ev) {
|
4356
|
+
isDragging = false;
|
4357
|
+
mouseFollower = new MouseFollower(seg.el, {
|
4358
|
+
additionalClass: 'fc-dragging',
|
4359
|
+
parentEl: view.el,
|
4360
|
+
opacity: dragListener.isTouch ? null : view.opt('dragOpacity'),
|
4361
|
+
revertDuration: view.opt('dragRevertDuration'),
|
4362
|
+
zIndex: 2 // one above the .fc-view
|
4363
|
+
});
|
3861
4364
|
mouseFollower.hide(); // don't show until we know this is a real drag
|
3862
4365
|
mouseFollower.start(ev);
|
3863
4366
|
},
|
3864
4367
|
dragStart: function(ev) {
|
3865
|
-
|
4368
|
+
if (dragListener.isTouch && !view.isEventSelected(event)) {
|
4369
|
+
// if not previously selected, will fire after a delay. then, select the event
|
4370
|
+
view.selectEvent(event);
|
4371
|
+
}
|
4372
|
+
isDragging = true;
|
4373
|
+
_this.handleSegMouseout(seg, ev); // ensure a mouseout on the manipulated event has been reported
|
3866
4374
|
_this.segDragStart(seg, ev);
|
3867
4375
|
view.hideEvent(event); // hide all event segments. our mouseFollower will take over
|
3868
4376
|
},
|
3869
4377
|
hitOver: function(hit, isOrig, origHit) {
|
4378
|
+
var dragHelperEls;
|
3870
4379
|
|
3871
4380
|
// starting hit could be forced (DayGrid.limit)
|
3872
4381
|
if (seg.hit) {
|
@@ -3886,7 +4395,13 @@ Grid.mixin({
|
|
3886
4395
|
}
|
3887
4396
|
|
3888
4397
|
// if a valid drop location, have the subclass render a visual indication
|
3889
|
-
if (dropLocation && view.renderDrag(dropLocation, seg)) {
|
4398
|
+
if (dropLocation && (dragHelperEls = view.renderDrag(dropLocation, seg))) {
|
4399
|
+
|
4400
|
+
dragHelperEls.addClass('fc-dragging');
|
4401
|
+
if (!dragListener.isTouch) {
|
4402
|
+
_this.applyDragOpacity(dragHelperEls);
|
4403
|
+
}
|
4404
|
+
|
3890
4405
|
mouseFollower.hide(); // if the subclass is already using a mock event "helper", hide our own
|
3891
4406
|
}
|
3892
4407
|
else {
|
@@ -3902,27 +4417,54 @@ Grid.mixin({
|
|
3902
4417
|
mouseFollower.show(); // show in case we are moving out of all hits
|
3903
4418
|
dropLocation = null;
|
3904
4419
|
},
|
3905
|
-
hitDone: function() { // Called after a hitOut OR before a
|
4420
|
+
hitDone: function() { // Called after a hitOut OR before a dragEnd
|
3906
4421
|
enableCursor();
|
3907
4422
|
},
|
3908
|
-
|
4423
|
+
interactionEnd: function(ev) {
|
3909
4424
|
// do revert animation if hasn't changed. calls a callback when finished (whether animation or not)
|
3910
4425
|
mouseFollower.stop(!dropLocation, function() {
|
3911
|
-
|
3912
|
-
|
3913
|
-
|
3914
|
-
|
4426
|
+
if (isDragging) {
|
4427
|
+
view.unrenderDrag();
|
4428
|
+
view.showEvent(event);
|
4429
|
+
_this.segDragStop(seg, ev);
|
4430
|
+
}
|
3915
4431
|
if (dropLocation) {
|
3916
4432
|
view.reportEventDrop(event, dropLocation, this.largeUnit, el, ev);
|
3917
4433
|
}
|
3918
4434
|
});
|
4435
|
+
_this.segDragListener = null;
|
4436
|
+
}
|
4437
|
+
});
|
4438
|
+
|
4439
|
+
return dragListener;
|
4440
|
+
},
|
4441
|
+
|
4442
|
+
|
4443
|
+
// seg isn't draggable, but let's use a generic DragListener
|
4444
|
+
// simply for the delay, so it can be selected.
|
4445
|
+
// Has side effect of setting/unsetting `segDragListener`
|
4446
|
+
buildSegSelectListener: function(seg) {
|
4447
|
+
var _this = this;
|
4448
|
+
var view = this.view;
|
4449
|
+
var event = seg.event;
|
4450
|
+
|
4451
|
+
if (this.segDragListener) {
|
4452
|
+
return this.segDragListener;
|
4453
|
+
}
|
4454
|
+
|
4455
|
+
var dragListener = this.segDragListener = new DragListener({
|
4456
|
+
dragStart: function(ev) {
|
4457
|
+
if (dragListener.isTouch && !view.isEventSelected(event)) {
|
4458
|
+
// if not previously selected, will fire after a delay. then, select the event
|
4459
|
+
view.selectEvent(event);
|
4460
|
+
}
|
3919
4461
|
},
|
3920
|
-
|
3921
|
-
|
4462
|
+
interactionEnd: function(ev) {
|
4463
|
+
_this.segDragListener = null;
|
3922
4464
|
}
|
3923
4465
|
});
|
3924
4466
|
|
3925
|
-
dragListener
|
4467
|
+
return dragListener;
|
3926
4468
|
},
|
3927
4469
|
|
3928
4470
|
|
@@ -4038,8 +4580,8 @@ Grid.mixin({
|
|
4038
4580
|
var dropLocation; // a null value signals an unsuccessful drag
|
4039
4581
|
|
4040
4582
|
// listener that tracks mouse movement over date-associated pixel regions
|
4041
|
-
var dragListener = new HitDragListener(this, {
|
4042
|
-
|
4583
|
+
var dragListener = _this.externalDragListener = new HitDragListener(this, {
|
4584
|
+
interactionStart: function() {
|
4043
4585
|
_this.isDraggingExternal = true;
|
4044
4586
|
},
|
4045
4587
|
hitOver: function(hit) {
|
@@ -4063,17 +4605,16 @@ Grid.mixin({
|
|
4063
4605
|
hitOut: function() {
|
4064
4606
|
dropLocation = null; // signal unsuccessful
|
4065
4607
|
},
|
4066
|
-
hitDone: function() { // Called after a hitOut OR before a
|
4608
|
+
hitDone: function() { // Called after a hitOut OR before a dragEnd
|
4067
4609
|
enableCursor();
|
4068
4610
|
_this.unrenderDrag();
|
4069
4611
|
},
|
4070
|
-
|
4612
|
+
interactionEnd: function(ev) {
|
4071
4613
|
if (dropLocation) { // element was dropped on a valid hit
|
4072
4614
|
_this.view.reportExternalDrop(meta, dropLocation, el, ev, ui);
|
4073
4615
|
}
|
4074
|
-
},
|
4075
|
-
listenStop: function() {
|
4076
4616
|
_this.isDraggingExternal = false;
|
4617
|
+
_this.externalDragListener = null;
|
4077
4618
|
}
|
4078
4619
|
});
|
4079
4620
|
|
@@ -4114,6 +4655,7 @@ Grid.mixin({
|
|
4114
4655
|
// `dropLocation` contains hypothetical start/end/allDay values the event would have if dropped. end can be null.
|
4115
4656
|
// `seg` is the internal segment object that is being dragged. If dragging an external element, `seg` is null.
|
4116
4657
|
// A truthy returned value indicates this method has rendered a helper element.
|
4658
|
+
// Must return elements used for any mock events.
|
4117
4659
|
renderDrag: function(dropLocation, seg) {
|
4118
4660
|
// subclasses must implement
|
4119
4661
|
},
|
@@ -4129,24 +4671,28 @@ Grid.mixin({
|
|
4129
4671
|
------------------------------------------------------------------------------------------------------------------*/
|
4130
4672
|
|
4131
4673
|
|
4132
|
-
//
|
4674
|
+
// Creates a listener that tracks the user as they resize an event segment.
|
4133
4675
|
// Generic enough to work with any type of Grid.
|
4134
|
-
|
4676
|
+
buildSegResizeListener: function(seg, isStart) {
|
4135
4677
|
var _this = this;
|
4136
4678
|
var view = this.view;
|
4137
4679
|
var calendar = view.calendar;
|
4138
4680
|
var el = seg.el;
|
4139
4681
|
var event = seg.event;
|
4140
4682
|
var eventEnd = calendar.getEventEnd(event);
|
4683
|
+
var isDragging;
|
4141
4684
|
var resizeLocation; // zoned event date properties. falsy if invalid resize
|
4142
4685
|
|
4143
4686
|
// Tracks mouse movement over the *grid's* coordinate map
|
4144
|
-
var dragListener = new HitDragListener(this, {
|
4145
|
-
distance: 5,
|
4687
|
+
var dragListener = this.segResizeListener = new HitDragListener(this, {
|
4146
4688
|
scroll: view.opt('dragScroll'),
|
4147
4689
|
subjectEl: el,
|
4690
|
+
interactionStart: function() {
|
4691
|
+
isDragging = false;
|
4692
|
+
},
|
4148
4693
|
dragStart: function(ev) {
|
4149
|
-
|
4694
|
+
isDragging = true;
|
4695
|
+
_this.handleSegMouseout(seg, ev); // ensure a mouseout on the manipulated event has been reported
|
4150
4696
|
_this.segResizeStart(seg, ev);
|
4151
4697
|
},
|
4152
4698
|
hitOver: function(hit, isOrig, origHit) {
|
@@ -4181,16 +4727,18 @@ Grid.mixin({
|
|
4181
4727
|
view.showEvent(event);
|
4182
4728
|
enableCursor();
|
4183
4729
|
},
|
4184
|
-
|
4185
|
-
|
4186
|
-
|
4730
|
+
interactionEnd: function(ev) {
|
4731
|
+
if (isDragging) {
|
4732
|
+
_this.segResizeStop(seg, ev);
|
4733
|
+
}
|
4187
4734
|
if (resizeLocation) { // valid date to resize to?
|
4188
4735
|
view.reportEventResize(event, resizeLocation, this.largeUnit, el, ev);
|
4189
4736
|
}
|
4737
|
+
_this.segResizeListener = null;
|
4190
4738
|
}
|
4191
4739
|
});
|
4192
4740
|
|
4193
|
-
dragListener
|
4741
|
+
return dragListener;
|
4194
4742
|
},
|
4195
4743
|
|
4196
4744
|
|
@@ -4267,6 +4815,7 @@ Grid.mixin({
|
|
4267
4815
|
|
4268
4816
|
// Renders a visual indication of an event being resized.
|
4269
4817
|
// `range` has the updated dates of the event. `seg` is the original segment object involved in the drag.
|
4818
|
+
// Must return elements used for any mock events.
|
4270
4819
|
renderEventResize: function(range, seg) {
|
4271
4820
|
// subclasses must implement
|
4272
4821
|
},
|
@@ -4312,6 +4861,7 @@ Grid.mixin({
|
|
4312
4861
|
|
4313
4862
|
// Generic utility for generating the HTML classNames for an event segment's element
|
4314
4863
|
getSegClasses: function(seg, isDraggable, isResizable) {
|
4864
|
+
var view = this.view;
|
4315
4865
|
var event = seg.event;
|
4316
4866
|
var classes = [
|
4317
4867
|
'fc-event',
|
@@ -4329,6 +4879,11 @@ Grid.mixin({
|
|
4329
4879
|
classes.push('fc-resizable');
|
4330
4880
|
}
|
4331
4881
|
|
4882
|
+
// event is currently selected? attach a className.
|
4883
|
+
if (view.isEventSelected(event)) {
|
4884
|
+
classes.push('fc-selected');
|
4885
|
+
}
|
4886
|
+
|
4332
4887
|
return classes;
|
4333
4888
|
},
|
4334
4889
|
|
@@ -5311,10 +5866,7 @@ var DayGrid = FC.DayGrid = Grid.extend(DayTableMixin, {
|
|
5311
5866
|
// if a segment from the same calendar but another component is being dragged, render a helper event
|
5312
5867
|
if (seg && !seg.el.closest(this.el).length) {
|
5313
5868
|
|
5314
|
-
this.renderEventLocationHelper(eventLocation, seg);
|
5315
|
-
this.applyDragOpacity(this.helperEls);
|
5316
|
-
|
5317
|
-
return true; // a helper has been rendered
|
5869
|
+
return this.renderEventLocationHelper(eventLocation, seg); // returns mock event elements
|
5318
5870
|
}
|
5319
5871
|
},
|
5320
5872
|
|
@@ -5333,7 +5885,7 @@ var DayGrid = FC.DayGrid = Grid.extend(DayTableMixin, {
|
|
5333
5885
|
// Renders a visual indication of an event being resized
|
5334
5886
|
renderEventResize: function(eventLocation, seg) {
|
5335
5887
|
this.renderHighlight(this.eventToSpan(eventLocation));
|
5336
|
-
this.renderEventLocationHelper(eventLocation, seg);
|
5888
|
+
return this.renderEventLocationHelper(eventLocation, seg); // returns mock event elements
|
5337
5889
|
},
|
5338
5890
|
|
5339
5891
|
|
@@ -5379,7 +5931,9 @@ var DayGrid = FC.DayGrid = Grid.extend(DayTableMixin, {
|
|
5379
5931
|
helperNodes.push(skeletonEl[0]);
|
5380
5932
|
});
|
5381
5933
|
|
5382
|
-
|
5934
|
+
return ( // must return the elements rendered
|
5935
|
+
this.helperEls = $(helperNodes) // array -> jQuery set
|
5936
|
+
);
|
5383
5937
|
},
|
5384
5938
|
|
5385
5939
|
|
@@ -6165,6 +6719,7 @@ var TimeGrid = FC.TimeGrid = Grid.extend(DayTableMixin, {
|
|
6165
6719
|
labelInterval: null, // duration of how often a label should be displayed for a slot
|
6166
6720
|
|
6167
6721
|
colEls: null, // cells elements in the day-row background
|
6722
|
+
slatContainerEl: null, // div that wraps all the slat rows
|
6168
6723
|
slatEls: null, // elements running horizontally across all columns
|
6169
6724
|
nowIndicatorEls: null,
|
6170
6725
|
|
@@ -6184,7 +6739,8 @@ var TimeGrid = FC.TimeGrid = Grid.extend(DayTableMixin, {
|
|
6184
6739
|
renderDates: function() {
|
6185
6740
|
this.el.html(this.renderHtml());
|
6186
6741
|
this.colEls = this.el.find('.fc-day');
|
6187
|
-
this.
|
6742
|
+
this.slatContainerEl = this.el.find('.fc-slats');
|
6743
|
+
this.slatEls = this.slatContainerEl.find('tr');
|
6188
6744
|
|
6189
6745
|
this.colCoordCache = new CoordCache({
|
6190
6746
|
els: this.colEls,
|
@@ -6463,6 +7019,11 @@ var TimeGrid = FC.TimeGrid = Grid.extend(DayTableMixin, {
|
|
6463
7019
|
},
|
6464
7020
|
|
6465
7021
|
|
7022
|
+
getTotalSlatHeight: function() {
|
7023
|
+
return this.slatContainerEl.outerHeight();
|
7024
|
+
},
|
7025
|
+
|
7026
|
+
|
6466
7027
|
// Computes the top coordinate, relative to the bounds of the grid, of the given date.
|
6467
7028
|
// A `startOfDayDate` must be given for avoiding ambiguity over how to treat midnight.
|
6468
7029
|
computeDateTop: function(date, startOfDayDate) {
|
@@ -6511,13 +7072,10 @@ var TimeGrid = FC.TimeGrid = Grid.extend(DayTableMixin, {
|
|
6511
7072
|
renderDrag: function(eventLocation, seg) {
|
6512
7073
|
|
6513
7074
|
if (seg) { // if there is event information for this drag, render a helper event
|
6514
|
-
this.renderEventLocationHelper(eventLocation, seg);
|
6515
7075
|
|
6516
|
-
|
6517
|
-
|
6518
|
-
|
6519
|
-
|
6520
|
-
return true; // signal that a helper has been rendered
|
7076
|
+
// returns mock event elements
|
7077
|
+
// signal that a helper has been rendered
|
7078
|
+
return this.renderEventLocationHelper(eventLocation, seg);
|
6521
7079
|
}
|
6522
7080
|
else {
|
6523
7081
|
// otherwise, just render a highlight
|
@@ -6539,7 +7097,7 @@ var TimeGrid = FC.TimeGrid = Grid.extend(DayTableMixin, {
|
|
6539
7097
|
|
6540
7098
|
// Renders a visual indication of an event being resized
|
6541
7099
|
renderEventResize: function(eventLocation, seg) {
|
6542
|
-
this.renderEventLocationHelper(eventLocation, seg);
|
7100
|
+
return this.renderEventLocationHelper(eventLocation, seg); // returns mock event elements
|
6543
7101
|
},
|
6544
7102
|
|
6545
7103
|
|
@@ -6555,7 +7113,7 @@ var TimeGrid = FC.TimeGrid = Grid.extend(DayTableMixin, {
|
|
6555
7113
|
|
6556
7114
|
// Renders a mock "helper" event. `sourceSeg` is the original segment object and might be null (an external drag)
|
6557
7115
|
renderHelper: function(event, sourceSeg) {
|
6558
|
-
this.renderHelperSegs(this.eventToSegs(event), sourceSeg);
|
7116
|
+
return this.renderHelperSegs(this.eventToSegs(event), sourceSeg); // returns mock event elements
|
6559
7117
|
},
|
6560
7118
|
|
6561
7119
|
|
@@ -6749,6 +7307,7 @@ TimeGrid.mixin({
|
|
6749
7307
|
|
6750
7308
|
|
6751
7309
|
renderHelperSegs: function(segs, sourceSeg) {
|
7310
|
+
var helperEls = [];
|
6752
7311
|
var i, seg;
|
6753
7312
|
var sourceEl;
|
6754
7313
|
|
@@ -6766,9 +7325,12 @@ TimeGrid.mixin({
|
|
6766
7325
|
'margin-right': sourceEl.css('margin-right')
|
6767
7326
|
});
|
6768
7327
|
}
|
7328
|
+
helperEls.push(seg.el[0]);
|
6769
7329
|
}
|
6770
7330
|
|
6771
7331
|
this.helperSegs = segs;
|
7332
|
+
|
7333
|
+
return $(helperEls); // must return rendered helpers
|
6772
7334
|
},
|
6773
7335
|
|
6774
7336
|
|
@@ -7279,7 +7841,7 @@ function isSlotSegCollision(seg1, seg2) {
|
|
7279
7841
|
/* An abstract class from which other views inherit from
|
7280
7842
|
----------------------------------------------------------------------------------------------------------------------*/
|
7281
7843
|
|
7282
|
-
var View = FC.View = Class.extend({
|
7844
|
+
var View = FC.View = Class.extend(EmitterMixin, ListenerMixin, {
|
7283
7845
|
|
7284
7846
|
type: null, // subclass' view name (string)
|
7285
7847
|
name: null, // deprecated. use `type` instead
|
@@ -7306,13 +7868,10 @@ var View = FC.View = Class.extend({
|
|
7306
7868
|
|
7307
7869
|
isRTL: false,
|
7308
7870
|
isSelected: false, // boolean whether a range of time is user-selected or not
|
7871
|
+
selectedEvent: null,
|
7309
7872
|
|
7310
7873
|
eventOrderSpecs: null, // criteria for ordering events when they have same date/time
|
7311
7874
|
|
7312
|
-
// subclasses can optionally use a scroll container
|
7313
|
-
scrollerEl: null, // the element that will most likely scroll when content is too tall
|
7314
|
-
scrollTop: null, // cached vertical scroll value
|
7315
|
-
|
7316
7875
|
// classNames styled by jqui themes
|
7317
7876
|
widgetHeaderClass: null,
|
7318
7877
|
widgetContentClass: null,
|
@@ -7322,9 +7881,6 @@ var View = FC.View = Class.extend({
|
|
7322
7881
|
nextDayThreshold: null,
|
7323
7882
|
isHiddenDayHash: null,
|
7324
7883
|
|
7325
|
-
// document handlers, bound to `this` object
|
7326
|
-
documentMousedownProxy: null, // TODO: doesn't work with touch
|
7327
|
-
|
7328
7884
|
// now indicator
|
7329
7885
|
isNowIndicatorRendered: null,
|
7330
7886
|
initialNowDate: null, // result first getNow call
|
@@ -7347,8 +7903,6 @@ var View = FC.View = Class.extend({
|
|
7347
7903
|
|
7348
7904
|
this.eventOrderSpecs = parseFieldSpecs(this.opt('eventOrder'));
|
7349
7905
|
|
7350
|
-
this.documentMousedownProxy = proxy(this, 'documentMousedown');
|
7351
|
-
|
7352
7906
|
this.initialize();
|
7353
7907
|
},
|
7354
7908
|
|
@@ -7566,15 +8120,14 @@ var View = FC.View = Class.extend({
|
|
7566
8120
|
|
7567
8121
|
this.calendar.freezeContentHeight();
|
7568
8122
|
|
7569
|
-
return this.clear()
|
8123
|
+
return syncThen(this.clear(), function() { // clear the content first
|
7570
8124
|
return (
|
7571
8125
|
_this.displaying =
|
7572
|
-
|
7573
|
-
.
|
7574
|
-
|
7575
|
-
|
7576
|
-
|
7577
|
-
})
|
8126
|
+
syncThen(_this.displayView(date), function() { // displayView might return a promise
|
8127
|
+
_this.forceScroll(_this.computeInitialScroll(scrollState));
|
8128
|
+
_this.calendar.unfreezeContentHeight();
|
8129
|
+
_this.triggerRender();
|
8130
|
+
})
|
7578
8131
|
);
|
7579
8132
|
});
|
7580
8133
|
},
|
@@ -7588,7 +8141,7 @@ var View = FC.View = Class.extend({
|
|
7588
8141
|
var displaying = this.displaying;
|
7589
8142
|
|
7590
8143
|
if (displaying) { // previously displayed, or in the process of being displayed?
|
7591
|
-
return displaying
|
8144
|
+
return syncThen(displaying, function() { // wait for the display to finish
|
7592
8145
|
_this.displaying = null;
|
7593
8146
|
_this.clearEvents();
|
7594
8147
|
return _this.clearView(); // might return a promise. chain it
|
@@ -7674,13 +8227,14 @@ var View = FC.View = Class.extend({
|
|
7674
8227
|
|
7675
8228
|
// Binds DOM handlers to elements that reside outside the view container, such as the document
|
7676
8229
|
bindGlobalHandlers: function() {
|
7677
|
-
$(document)
|
8230
|
+
this.listenTo($(document), 'mousedown', this.handleDocumentMousedown);
|
8231
|
+
this.listenTo($(document), 'touchstart', this.processUnselect);
|
7678
8232
|
},
|
7679
8233
|
|
7680
8234
|
|
7681
8235
|
// Unbinds DOM handlers from elements that reside outside the view container
|
7682
8236
|
unbindGlobalHandlers: function() {
|
7683
|
-
$(document)
|
8237
|
+
this.stopListeningTo($(document));
|
7684
8238
|
},
|
7685
8239
|
|
7686
8240
|
|
@@ -7845,28 +8399,7 @@ var View = FC.View = Class.extend({
|
|
7845
8399
|
|
7846
8400
|
|
7847
8401
|
/* Scroller
|
7848
|
-
------------------------------------------------------------------------------------------------------------------*/
|
7849
|
-
|
7850
|
-
|
7851
|
-
// Given the total height of the view, return the number of pixels that should be used for the scroller.
|
7852
|
-
// Utility for subclasses.
|
7853
|
-
computeScrollerHeight: function(totalHeight) {
|
7854
|
-
var scrollerEl = this.scrollerEl;
|
7855
|
-
var both;
|
7856
|
-
var otherHeight; // cumulative height of everything that is not the scrollerEl in the view (header+borders)
|
7857
|
-
|
7858
|
-
both = this.el.add(scrollerEl);
|
7859
|
-
|
7860
|
-
// fuckin IE8/9/10/11 sometimes returns 0 for dimensions. this weird hack was the only thing that worked
|
7861
|
-
both.css({
|
7862
|
-
position: 'relative', // cause a reflow, which will force fresh dimension recalculation
|
7863
|
-
left: -1 // ensure reflow in case the el was already relative. negative is less likely to cause new scroll
|
7864
|
-
});
|
7865
|
-
otherHeight = this.el.outerHeight() - scrollerEl.height(); // grab the dimensions
|
7866
|
-
both.css({ position: '', left: '' }); // undo hack
|
7867
|
-
|
7868
|
-
return totalHeight - otherHeight;
|
7869
|
-
},
|
8402
|
+
------------------------------------------------------------------------------------------------------------------*/
|
7870
8403
|
|
7871
8404
|
|
7872
8405
|
// Computes the initial pre-configured scroll state prior to allowing the user to change it.
|
@@ -7878,17 +8411,13 @@ var View = FC.View = Class.extend({
|
|
7878
8411
|
|
7879
8412
|
// Retrieves the view's current natural scroll state. Can return an arbitrary format.
|
7880
8413
|
queryScroll: function() {
|
7881
|
-
|
7882
|
-
return this.scrollerEl.scrollTop(); // operates on scrollerEl by default
|
7883
|
-
}
|
8414
|
+
// subclasses must implement
|
7884
8415
|
},
|
7885
8416
|
|
7886
8417
|
|
7887
8418
|
// Sets the view's scroll state. Will accept the same format computeInitialScroll and queryScroll produce.
|
7888
8419
|
setScroll: function(scrollState) {
|
7889
|
-
|
7890
|
-
return this.scrollerEl.scrollTop(scrollState); // operates on scrollerEl by default
|
7891
|
-
}
|
8420
|
+
// subclasses must implement
|
7892
8421
|
},
|
7893
8422
|
|
7894
8423
|
|
@@ -8103,7 +8632,8 @@ var View = FC.View = Class.extend({
|
|
8103
8632
|
|
8104
8633
|
|
8105
8634
|
// Renders a visual indication of a event or external-element drag over the given drop zone.
|
8106
|
-
// If an external-element, seg will be `null
|
8635
|
+
// If an external-element, seg will be `null`.
|
8636
|
+
// Must return elements used for any mock events.
|
8107
8637
|
renderDrag: function(dropLocation, seg) {
|
8108
8638
|
// subclasses must implement
|
8109
8639
|
},
|
@@ -8166,7 +8696,7 @@ var View = FC.View = Class.extend({
|
|
8166
8696
|
},
|
8167
8697
|
|
8168
8698
|
|
8169
|
-
/* Selection
|
8699
|
+
/* Selection (time range)
|
8170
8700
|
------------------------------------------------------------------------------------------------------------------*/
|
8171
8701
|
|
8172
8702
|
|
@@ -8224,13 +8754,62 @@ var View = FC.View = Class.extend({
|
|
8224
8754
|
},
|
8225
8755
|
|
8226
8756
|
|
8227
|
-
|
8228
|
-
|
8229
|
-
|
8757
|
+
/* Event Selection
|
8758
|
+
------------------------------------------------------------------------------------------------------------------*/
|
8759
|
+
|
8760
|
+
|
8761
|
+
selectEvent: function(event) {
|
8762
|
+
if (!this.selectedEvent || this.selectedEvent !== event) {
|
8763
|
+
this.unselectEvent();
|
8764
|
+
this.renderedEventSegEach(function(seg) {
|
8765
|
+
seg.el.addClass('fc-selected');
|
8766
|
+
}, event);
|
8767
|
+
this.selectedEvent = event;
|
8768
|
+
}
|
8769
|
+
},
|
8770
|
+
|
8771
|
+
|
8772
|
+
unselectEvent: function() {
|
8773
|
+
if (this.selectedEvent) {
|
8774
|
+
this.renderedEventSegEach(function(seg) {
|
8775
|
+
seg.el.removeClass('fc-selected');
|
8776
|
+
}, this.selectedEvent);
|
8777
|
+
this.selectedEvent = null;
|
8778
|
+
}
|
8779
|
+
},
|
8780
|
+
|
8781
|
+
|
8782
|
+
isEventSelected: function(event) {
|
8783
|
+
// event references might change on refetchEvents(), while selectedEvent doesn't,
|
8784
|
+
// so compare IDs
|
8785
|
+
return this.selectedEvent && this.selectedEvent._id === event._id;
|
8786
|
+
},
|
8787
|
+
|
8788
|
+
|
8789
|
+
/* Mouse / Touch Unselecting (time range & event unselection)
|
8790
|
+
------------------------------------------------------------------------------------------------------------------*/
|
8791
|
+
// TODO: move consistently to down/start or up/end?
|
8792
|
+
// TODO: don't kill previous selection if touch scrolling
|
8793
|
+
|
8794
|
+
|
8795
|
+
handleDocumentMousedown: function(ev) {
|
8796
|
+
if (isPrimaryMouseButton(ev)) {
|
8797
|
+
this.processUnselect(ev);
|
8798
|
+
}
|
8799
|
+
},
|
8800
|
+
|
8801
|
+
|
8802
|
+
processUnselect: function(ev) {
|
8803
|
+
this.processRangeUnselect(ev);
|
8804
|
+
this.processEventUnselect(ev);
|
8805
|
+
},
|
8806
|
+
|
8230
8807
|
|
8231
|
-
|
8232
|
-
|
8808
|
+
processRangeUnselect: function(ev) {
|
8809
|
+
var ignore;
|
8233
8810
|
|
8811
|
+
// is there a time-range selection?
|
8812
|
+
if (this.isSelected && this.opt('unselectAuto')) {
|
8234
8813
|
// only unselect if the clicked element is not identical to or inside of an 'unselectCancel' element
|
8235
8814
|
ignore = this.opt('unselectCancel');
|
8236
8815
|
if (!ignore || !$(ev.target).closest(ignore).length) {
|
@@ -8240,6 +8819,15 @@ var View = FC.View = Class.extend({
|
|
8240
8819
|
},
|
8241
8820
|
|
8242
8821
|
|
8822
|
+
processEventUnselect: function(ev) {
|
8823
|
+
if (this.selectedEvent) {
|
8824
|
+
if (!$(ev.target).closest('.fc-selected').length) {
|
8825
|
+
this.unselectEvent();
|
8826
|
+
}
|
8827
|
+
}
|
8828
|
+
},
|
8829
|
+
|
8830
|
+
|
8243
8831
|
/* Day Click
|
8244
8832
|
------------------------------------------------------------------------------------------------------------------*/
|
8245
8833
|
|
@@ -8354,6 +8942,127 @@ var View = FC.View = Class.extend({
|
|
8354
8942
|
|
8355
8943
|
;;
|
8356
8944
|
|
8945
|
+
/*
|
8946
|
+
Embodies a div that has potential scrollbars
|
8947
|
+
*/
|
8948
|
+
var Scroller = FC.Scroller = Class.extend({
|
8949
|
+
|
8950
|
+
el: null, // the guaranteed outer element
|
8951
|
+
scrollEl: null, // the element with the scrollbars
|
8952
|
+
overflowX: null,
|
8953
|
+
overflowY: null,
|
8954
|
+
|
8955
|
+
|
8956
|
+
constructor: function(options) {
|
8957
|
+
options = options || {};
|
8958
|
+
this.overflowX = options.overflowX || options.overflow || 'auto';
|
8959
|
+
this.overflowY = options.overflowY || options.overflow || 'auto';
|
8960
|
+
},
|
8961
|
+
|
8962
|
+
|
8963
|
+
render: function() {
|
8964
|
+
this.el = this.renderEl();
|
8965
|
+
this.applyOverflow();
|
8966
|
+
},
|
8967
|
+
|
8968
|
+
|
8969
|
+
renderEl: function() {
|
8970
|
+
return (this.scrollEl = $('<div class="fc-scroller"></div>'));
|
8971
|
+
},
|
8972
|
+
|
8973
|
+
|
8974
|
+
// sets to natural height, unlocks overflow
|
8975
|
+
clear: function() {
|
8976
|
+
this.setHeight('auto');
|
8977
|
+
this.applyOverflow();
|
8978
|
+
},
|
8979
|
+
|
8980
|
+
|
8981
|
+
destroy: function() {
|
8982
|
+
this.el.remove();
|
8983
|
+
},
|
8984
|
+
|
8985
|
+
|
8986
|
+
// Overflow
|
8987
|
+
// -----------------------------------------------------------------------------------------------------------------
|
8988
|
+
|
8989
|
+
|
8990
|
+
applyOverflow: function() {
|
8991
|
+
this.scrollEl.css({
|
8992
|
+
'overflow-x': this.overflowX,
|
8993
|
+
'overflow-y': this.overflowY
|
8994
|
+
});
|
8995
|
+
},
|
8996
|
+
|
8997
|
+
|
8998
|
+
// Causes any 'auto' overflow values to resolves to 'scroll' or 'hidden'.
|
8999
|
+
// Useful for preserving scrollbar widths regardless of future resizes.
|
9000
|
+
// Can pass in scrollbarWidths for optimization.
|
9001
|
+
lockOverflow: function(scrollbarWidths) {
|
9002
|
+
var overflowX = this.overflowX;
|
9003
|
+
var overflowY = this.overflowY;
|
9004
|
+
|
9005
|
+
scrollbarWidths = scrollbarWidths || this.getScrollbarWidths();
|
9006
|
+
|
9007
|
+
if (overflowX === 'auto') {
|
9008
|
+
overflowX = (
|
9009
|
+
scrollbarWidths.top || scrollbarWidths.bottom || // horizontal scrollbars?
|
9010
|
+
// OR scrolling pane with massless scrollbars?
|
9011
|
+
this.scrollEl[0].scrollWidth - 1 > this.scrollEl[0].clientWidth
|
9012
|
+
// subtract 1 because of IE off-by-one issue
|
9013
|
+
) ? 'scroll' : 'hidden';
|
9014
|
+
}
|
9015
|
+
|
9016
|
+
if (overflowY === 'auto') {
|
9017
|
+
overflowY = (
|
9018
|
+
scrollbarWidths.left || scrollbarWidths.right || // vertical scrollbars?
|
9019
|
+
// OR scrolling pane with massless scrollbars?
|
9020
|
+
this.scrollEl[0].scrollHeight - 1 > this.scrollEl[0].clientHeight
|
9021
|
+
// subtract 1 because of IE off-by-one issue
|
9022
|
+
) ? 'scroll' : 'hidden';
|
9023
|
+
}
|
9024
|
+
|
9025
|
+
this.scrollEl.css({ 'overflow-x': overflowX, 'overflow-y': overflowY });
|
9026
|
+
},
|
9027
|
+
|
9028
|
+
|
9029
|
+
// Getters / Setters
|
9030
|
+
// -----------------------------------------------------------------------------------------------------------------
|
9031
|
+
|
9032
|
+
|
9033
|
+
setHeight: function(height) {
|
9034
|
+
this.scrollEl.height(height);
|
9035
|
+
},
|
9036
|
+
|
9037
|
+
|
9038
|
+
getScrollTop: function() {
|
9039
|
+
return this.scrollEl.scrollTop();
|
9040
|
+
},
|
9041
|
+
|
9042
|
+
|
9043
|
+
setScrollTop: function(top) {
|
9044
|
+
this.scrollEl.scrollTop(top);
|
9045
|
+
},
|
9046
|
+
|
9047
|
+
|
9048
|
+
getClientWidth: function() {
|
9049
|
+
return this.scrollEl[0].clientWidth;
|
9050
|
+
},
|
9051
|
+
|
9052
|
+
|
9053
|
+
getClientHeight: function() {
|
9054
|
+
return this.scrollEl[0].clientHeight;
|
9055
|
+
},
|
9056
|
+
|
9057
|
+
|
9058
|
+
getScrollbarWidths: function() {
|
9059
|
+
return getScrollbarWidths(this.scrollEl);
|
9060
|
+
}
|
9061
|
+
|
9062
|
+
});
|
9063
|
+
|
9064
|
+
;;
|
9065
|
+
|
8357
9066
|
var Calendar = FC.Calendar = Class.extend({
|
8358
9067
|
|
8359
9068
|
dirDefaults: null, // option defaults related to LTR or RTL
|
@@ -8608,7 +9317,7 @@ var Calendar = FC.Calendar = Class.extend({
|
|
8608
9317
|
});
|
8609
9318
|
|
8610
9319
|
|
8611
|
-
Calendar.mixin(
|
9320
|
+
Calendar.mixin(EmitterMixin);
|
8612
9321
|
|
8613
9322
|
|
8614
9323
|
function Calendar_constructor(element, overrides) {
|
@@ -8625,6 +9334,7 @@ function Calendar_constructor(element, overrides) {
|
|
8625
9334
|
t.render = render;
|
8626
9335
|
t.destroy = destroy;
|
8627
9336
|
t.refetchEvents = refetchEvents;
|
9337
|
+
t.refetchEventSources = refetchEventSources;
|
8628
9338
|
t.reportEvents = reportEvents;
|
8629
9339
|
t.reportEventChange = reportEventChange;
|
8630
9340
|
t.rerenderEvents = renderEvents; // `renderEvents` serves as a rerender. an API method
|
@@ -8815,6 +9525,7 @@ function Calendar_constructor(element, overrides) {
|
|
8815
9525
|
EventManager.call(t, options);
|
8816
9526
|
var isFetchNeeded = t.isFetchNeeded;
|
8817
9527
|
var fetchEvents = t.fetchEvents;
|
9528
|
+
var fetchEventSources = t.fetchEventSources;
|
8818
9529
|
|
8819
9530
|
|
8820
9531
|
|
@@ -9054,11 +9765,16 @@ function Calendar_constructor(element, overrides) {
|
|
9054
9765
|
|
9055
9766
|
|
9056
9767
|
function refetchEvents() { // can be called as an API method
|
9057
|
-
destroyEvents(); // so that events are cleared before user starts waiting for AJAX
|
9058
9768
|
fetchAndRenderEvents();
|
9059
9769
|
}
|
9060
9770
|
|
9061
9771
|
|
9772
|
+
// TODO: move this into EventManager?
|
9773
|
+
function refetchEventSources(matchInputs) {
|
9774
|
+
fetchEventSources(t.getEventSourcesByMatchArray(matchInputs));
|
9775
|
+
}
|
9776
|
+
|
9777
|
+
|
9062
9778
|
function renderEvents() { // destroys old events if previously rendered
|
9063
9779
|
if (elementVisible()) {
|
9064
9780
|
freezeContentHeight();
|
@@ -9066,13 +9782,6 @@ function Calendar_constructor(element, overrides) {
|
|
9066
9782
|
unfreezeContentHeight();
|
9067
9783
|
}
|
9068
9784
|
}
|
9069
|
-
|
9070
|
-
|
9071
|
-
function destroyEvents() {
|
9072
|
-
freezeContentHeight();
|
9073
|
-
currentView.clearEvents();
|
9074
|
-
unfreezeContentHeight();
|
9075
|
-
}
|
9076
9785
|
|
9077
9786
|
|
9078
9787
|
function getAndRenderEvents() {
|
@@ -9283,7 +9992,7 @@ function Calendar_constructor(element, overrides) {
|
|
9283
9992
|
|
9284
9993
|
Calendar.defaults = {
|
9285
9994
|
|
9286
|
-
titleRangeSeparator: ' \
|
9995
|
+
titleRangeSeparator: ' \u2013 ', // en dash
|
9287
9996
|
monthYearFormat: 'MMMM YYYY', // required for en. other languages rely on datepicker computable option
|
9288
9997
|
|
9289
9998
|
defaultTimedEventDuration: '02:00:00',
|
@@ -9369,7 +10078,9 @@ Calendar.defaults = {
|
|
9369
10078
|
dayPopoverFormat: 'LL',
|
9370
10079
|
|
9371
10080
|
handleWindowResize: true,
|
9372
|
-
windowResizeDelay: 200 // milliseconds before an updateSize happens
|
10081
|
+
windowResizeDelay: 200, // milliseconds before an updateSize happens
|
10082
|
+
|
10083
|
+
longPressDelay: 1000
|
9373
10084
|
|
9374
10085
|
};
|
9375
10086
|
|
@@ -9830,14 +10541,14 @@ function Header(calendar, options) {
|
|
9830
10541
|
|
9831
10542
|
function disableButton(buttonName) {
|
9832
10543
|
el.find('.fc-' + buttonName + '-button')
|
9833
|
-
.
|
10544
|
+
.prop('disabled', true)
|
9834
10545
|
.addClass(tm + '-state-disabled');
|
9835
10546
|
}
|
9836
10547
|
|
9837
10548
|
|
9838
10549
|
function enableButton(buttonName) {
|
9839
10550
|
el.find('.fc-' + buttonName + '-button')
|
9840
|
-
.
|
10551
|
+
.prop('disabled', false)
|
9841
10552
|
.removeClass(tm + '-state-disabled');
|
9842
10553
|
}
|
9843
10554
|
|
@@ -9868,8 +10579,14 @@ function EventManager(options) { // assumed to be a calendar
|
|
9868
10579
|
// exports
|
9869
10580
|
t.isFetchNeeded = isFetchNeeded;
|
9870
10581
|
t.fetchEvents = fetchEvents;
|
10582
|
+
t.fetchEventSources = fetchEventSources;
|
10583
|
+
t.getEventSources = getEventSources;
|
10584
|
+
t.getEventSourceById = getEventSourceById;
|
10585
|
+
t.getEventSourcesByMatchArray = getEventSourcesByMatchArray;
|
10586
|
+
t.getEventSourcesByMatch = getEventSourcesByMatch;
|
9871
10587
|
t.addEventSource = addEventSource;
|
9872
10588
|
t.removeEventSource = removeEventSource;
|
10589
|
+
t.removeEventSources = removeEventSources;
|
9873
10590
|
t.updateEvent = updateEvent;
|
9874
10591
|
t.renderEvent = renderEvent;
|
9875
10592
|
t.removeEvents = removeEvents;
|
@@ -9887,8 +10604,7 @@ function EventManager(options) { // assumed to be a calendar
|
|
9887
10604
|
var stickySource = { events: [] };
|
9888
10605
|
var sources = [ stickySource ];
|
9889
10606
|
var rangeStart, rangeEnd;
|
9890
|
-
var
|
9891
|
-
var pendingSourceCnt = 0;
|
10607
|
+
var pendingSourceCnt = 0; // outstanding fetch requests, max one per source
|
9892
10608
|
var cache = []; // holds events that have already been expanded
|
9893
10609
|
|
9894
10610
|
|
@@ -9918,23 +10634,58 @@ function EventManager(options) { // assumed to be a calendar
|
|
9918
10634
|
function fetchEvents(start, end) {
|
9919
10635
|
rangeStart = start;
|
9920
10636
|
rangeEnd = end;
|
9921
|
-
|
9922
|
-
|
9923
|
-
|
9924
|
-
|
9925
|
-
|
9926
|
-
|
10637
|
+
fetchEventSources(sources, 'reset');
|
10638
|
+
}
|
10639
|
+
|
10640
|
+
|
10641
|
+
// expects an array of event source objects (the originals, not copies)
|
10642
|
+
// `specialFetchType` is an optimization parameter that affects purging of the event cache.
|
10643
|
+
function fetchEventSources(specificSources, specialFetchType) {
|
10644
|
+
var i, source;
|
10645
|
+
|
10646
|
+
if (specialFetchType === 'reset') {
|
10647
|
+
cache = [];
|
10648
|
+
}
|
10649
|
+
else if (specialFetchType !== 'add') {
|
10650
|
+
cache = excludeEventsBySources(cache, specificSources);
|
10651
|
+
}
|
10652
|
+
|
10653
|
+
for (i = 0; i < specificSources.length; i++) {
|
10654
|
+
source = specificSources[i];
|
10655
|
+
|
10656
|
+
// already-pending sources have already been accounted for in pendingSourceCnt
|
10657
|
+
if (source._status !== 'pending') {
|
10658
|
+
pendingSourceCnt++;
|
10659
|
+
}
|
10660
|
+
|
10661
|
+
source._fetchId = (source._fetchId || 0) + 1;
|
10662
|
+
source._status = 'pending';
|
10663
|
+
}
|
10664
|
+
|
10665
|
+
for (i = 0; i < specificSources.length; i++) {
|
10666
|
+
source = specificSources[i];
|
10667
|
+
|
10668
|
+
tryFetchEventSource(source, source._fetchId);
|
9927
10669
|
}
|
9928
10670
|
}
|
9929
|
-
|
9930
|
-
|
9931
|
-
|
10671
|
+
|
10672
|
+
|
10673
|
+
// fetches an event source and processes its result ONLY if it is still the current fetch.
|
10674
|
+
// caller is responsible for incrementing pendingSourceCnt first.
|
10675
|
+
function tryFetchEventSource(source, fetchId) {
|
9932
10676
|
_fetchEventSource(source, function(eventInputs) {
|
9933
10677
|
var isArraySource = $.isArray(source.events);
|
9934
10678
|
var i, eventInput;
|
9935
10679
|
var abstractEvent;
|
9936
10680
|
|
9937
|
-
if (
|
10681
|
+
if (
|
10682
|
+
// is this the source's most recent fetch?
|
10683
|
+
// if not, rely on an upcoming fetch of this source to decrement pendingSourceCnt
|
10684
|
+
fetchId === source._fetchId &&
|
10685
|
+
// event source no longer valid?
|
10686
|
+
source._status !== 'rejected'
|
10687
|
+
) {
|
10688
|
+
source._status = 'resolved';
|
9938
10689
|
|
9939
10690
|
if (eventInputs) {
|
9940
10691
|
for (i = 0; i < eventInputs.length; i++) {
|
@@ -9956,13 +10707,29 @@ function EventManager(options) { // assumed to be a calendar
|
|
9956
10707
|
}
|
9957
10708
|
}
|
9958
10709
|
|
9959
|
-
|
9960
|
-
if (!pendingSourceCnt) {
|
9961
|
-
reportEvents(cache);
|
9962
|
-
}
|
10710
|
+
decrementPendingSourceCnt();
|
9963
10711
|
}
|
9964
10712
|
});
|
9965
10713
|
}
|
10714
|
+
|
10715
|
+
|
10716
|
+
function rejectEventSource(source) {
|
10717
|
+
var wasPending = source._status === 'pending';
|
10718
|
+
|
10719
|
+
source._status = 'rejected';
|
10720
|
+
|
10721
|
+
if (wasPending) {
|
10722
|
+
decrementPendingSourceCnt();
|
10723
|
+
}
|
10724
|
+
}
|
10725
|
+
|
10726
|
+
|
10727
|
+
function decrementPendingSourceCnt() {
|
10728
|
+
pendingSourceCnt--;
|
10729
|
+
if (!pendingSourceCnt) {
|
10730
|
+
reportEvents(cache);
|
10731
|
+
}
|
10732
|
+
}
|
9966
10733
|
|
9967
10734
|
|
9968
10735
|
function _fetchEventSource(source, callback) {
|
@@ -10078,14 +10845,13 @@ function EventManager(options) { // assumed to be a calendar
|
|
10078
10845
|
|
10079
10846
|
/* Sources
|
10080
10847
|
-----------------------------------------------------------------------------*/
|
10081
|
-
|
10848
|
+
|
10082
10849
|
|
10083
10850
|
function addEventSource(sourceInput) {
|
10084
10851
|
var source = buildEventSource(sourceInput);
|
10085
10852
|
if (source) {
|
10086
10853
|
sources.push(source);
|
10087
|
-
|
10088
|
-
fetchEventSource(source, currentFetchID); // will eventually call reportEvents
|
10854
|
+
fetchEventSources([ source ], 'add'); // will eventually call reportEvents
|
10089
10855
|
}
|
10090
10856
|
}
|
10091
10857
|
|
@@ -10135,19 +10901,120 @@ function EventManager(options) { // assumed to be a calendar
|
|
10135
10901
|
}
|
10136
10902
|
|
10137
10903
|
|
10138
|
-
function removeEventSource(
|
10139
|
-
|
10140
|
-
|
10141
|
-
|
10142
|
-
|
10143
|
-
|
10144
|
-
|
10145
|
-
|
10904
|
+
function removeEventSource(matchInput) {
|
10905
|
+
removeSpecificEventSources(
|
10906
|
+
getEventSourcesByMatch(matchInput)
|
10907
|
+
);
|
10908
|
+
}
|
10909
|
+
|
10910
|
+
|
10911
|
+
// if called with no arguments, removes all.
|
10912
|
+
function removeEventSources(matchInputs) {
|
10913
|
+
if (matchInputs == null) {
|
10914
|
+
removeSpecificEventSources(sources, true); // isAll=true
|
10915
|
+
}
|
10916
|
+
else {
|
10917
|
+
removeSpecificEventSources(
|
10918
|
+
getEventSourcesByMatchArray(matchInputs)
|
10919
|
+
);
|
10920
|
+
}
|
10921
|
+
}
|
10922
|
+
|
10923
|
+
|
10924
|
+
function removeSpecificEventSources(targetSources, isAll) {
|
10925
|
+
var i;
|
10926
|
+
|
10927
|
+
// cancel pending requests
|
10928
|
+
for (i = 0; i < targetSources.length; i++) {
|
10929
|
+
rejectEventSource(targetSources[i]);
|
10930
|
+
}
|
10931
|
+
|
10932
|
+
if (isAll) { // an optimization
|
10933
|
+
sources = [];
|
10934
|
+
cache = [];
|
10935
|
+
}
|
10936
|
+
else {
|
10937
|
+
// remove from persisted source list
|
10938
|
+
sources = $.grep(sources, function(source) {
|
10939
|
+
for (i = 0; i < targetSources.length; i++) {
|
10940
|
+
if (source === targetSources[i]) {
|
10941
|
+
return false; // exclude
|
10942
|
+
}
|
10943
|
+
}
|
10944
|
+
return true; // include
|
10945
|
+
});
|
10946
|
+
|
10947
|
+
cache = excludeEventsBySources(cache, targetSources);
|
10948
|
+
}
|
10949
|
+
|
10146
10950
|
reportEvents(cache);
|
10147
10951
|
}
|
10148
10952
|
|
10149
10953
|
|
10150
|
-
function
|
10954
|
+
function getEventSources() {
|
10955
|
+
return sources.slice(1); // returns a shallow copy of sources with stickySource removed
|
10956
|
+
}
|
10957
|
+
|
10958
|
+
|
10959
|
+
function getEventSourceById(id) {
|
10960
|
+
return $.grep(sources, function(source) {
|
10961
|
+
return source.id && source.id === id;
|
10962
|
+
})[0];
|
10963
|
+
}
|
10964
|
+
|
10965
|
+
|
10966
|
+
// like getEventSourcesByMatch, but accepts multple match criteria (like multiple IDs)
|
10967
|
+
function getEventSourcesByMatchArray(matchInputs) {
|
10968
|
+
|
10969
|
+
// coerce into an array
|
10970
|
+
if (!matchInputs) {
|
10971
|
+
matchInputs = [];
|
10972
|
+
}
|
10973
|
+
else if (!$.isArray(matchInputs)) {
|
10974
|
+
matchInputs = [ matchInputs ];
|
10975
|
+
}
|
10976
|
+
|
10977
|
+
var matchingSources = [];
|
10978
|
+
var i;
|
10979
|
+
|
10980
|
+
// resolve raw inputs to real event source objects
|
10981
|
+
for (i = 0; i < matchInputs.length; i++) {
|
10982
|
+
matchingSources.push.apply( // append
|
10983
|
+
matchingSources,
|
10984
|
+
getEventSourcesByMatch(matchInputs[i])
|
10985
|
+
);
|
10986
|
+
}
|
10987
|
+
|
10988
|
+
return matchingSources;
|
10989
|
+
}
|
10990
|
+
|
10991
|
+
|
10992
|
+
// matchInput can either by a real event source object, an ID, or the function/URL for the source.
|
10993
|
+
// returns an array of matching source objects.
|
10994
|
+
function getEventSourcesByMatch(matchInput) {
|
10995
|
+
var i, source;
|
10996
|
+
|
10997
|
+
// given an proper event source object
|
10998
|
+
for (i = 0; i < sources.length; i++) {
|
10999
|
+
source = sources[i];
|
11000
|
+
if (source === matchInput) {
|
11001
|
+
return [ source ];
|
11002
|
+
}
|
11003
|
+
}
|
11004
|
+
|
11005
|
+
// an ID match
|
11006
|
+
source = getEventSourceById(matchInput);
|
11007
|
+
if (source) {
|
11008
|
+
return [ source ];
|
11009
|
+
}
|
11010
|
+
|
11011
|
+
return $.grep(sources, function(source) {
|
11012
|
+
return isSourcesEquivalent(matchInput, source);
|
11013
|
+
});
|
11014
|
+
}
|
11015
|
+
|
11016
|
+
|
11017
|
+
function isSourcesEquivalent(source1, source2) {
|
10151
11018
|
return source1 && source2 && getSourcePrimitive(source1) == getSourcePrimitive(source2);
|
10152
11019
|
}
|
10153
11020
|
|
@@ -10160,6 +11027,20 @@ function EventManager(options) { // assumed to be a calendar
|
|
10160
11027
|
) ||
|
10161
11028
|
source; // the given argument *is* the primitive
|
10162
11029
|
}
|
11030
|
+
|
11031
|
+
|
11032
|
+
// util
|
11033
|
+
// returns a filtered array without events that are part of any of the given sources
|
11034
|
+
function excludeEventsBySources(specificEvents, specificSources) {
|
11035
|
+
return $.grep(specificEvents, function(event) {
|
11036
|
+
for (var i = 0; i < specificSources.length; i++) {
|
11037
|
+
if (event.source === specificSources[i]) {
|
11038
|
+
return false; // exclude
|
11039
|
+
}
|
11040
|
+
}
|
11041
|
+
return true; // keep
|
11042
|
+
});
|
11043
|
+
}
|
10163
11044
|
|
10164
11045
|
|
10165
11046
|
|
@@ -10368,6 +11249,8 @@ function EventManager(options) { // assumed to be a calendar
|
|
10368
11249
|
assignDatesToEvent(start, end, allDay, out);
|
10369
11250
|
}
|
10370
11251
|
|
11252
|
+
t.normalizeEvent(out); // hook for external use. a prototype method
|
11253
|
+
|
10371
11254
|
return out;
|
10372
11255
|
}
|
10373
11256
|
|
@@ -10900,6 +11783,12 @@ function EventManager(options) { // assumed to be a calendar
|
|
10900
11783
|
}
|
10901
11784
|
|
10902
11785
|
|
11786
|
+
// hook for external libs to manipulate event properties upon creation.
|
11787
|
+
// should manipulate the event in-place.
|
11788
|
+
Calendar.prototype.normalizeEvent = function(event) {
|
11789
|
+
};
|
11790
|
+
|
11791
|
+
|
10903
11792
|
// Returns a list of events that the given event should be compared against when being considered for a move to
|
10904
11793
|
// the specified span. Attached to the Calendar's prototype because EventManager is a mixin for a Calendar.
|
10905
11794
|
Calendar.prototype.getPeerEvents = function(span, event) {
|
@@ -10937,6 +11826,8 @@ function backupEventDates(event) {
|
|
10937
11826
|
|
10938
11827
|
var BasicView = FC.BasicView = View.extend({
|
10939
11828
|
|
11829
|
+
scroller: null,
|
11830
|
+
|
10940
11831
|
dayGridClass: DayGrid, // class the dayGrid will be instantiated from (overridable by subclasses)
|
10941
11832
|
dayGrid: null, // the main subcomponent that does most of the heavy lifting
|
10942
11833
|
|
@@ -10951,6 +11842,11 @@ var BasicView = FC.BasicView = View.extend({
|
|
10951
11842
|
|
10952
11843
|
initialize: function() {
|
10953
11844
|
this.dayGrid = this.instantiateDayGrid();
|
11845
|
+
|
11846
|
+
this.scroller = new Scroller({
|
11847
|
+
overflowX: 'hidden',
|
11848
|
+
overflowY: 'auto'
|
11849
|
+
});
|
10954
11850
|
},
|
10955
11851
|
|
10956
11852
|
|
@@ -11003,9 +11899,12 @@ var BasicView = FC.BasicView = View.extend({
|
|
11003
11899
|
this.el.addClass('fc-basic-view').html(this.renderSkeletonHtml());
|
11004
11900
|
this.renderHead();
|
11005
11901
|
|
11006
|
-
this.
|
11902
|
+
this.scroller.render();
|
11903
|
+
var dayGridContainerEl = this.scroller.el.addClass('fc-day-grid-container');
|
11904
|
+
var dayGridEl = $('<div class="fc-day-grid" />').appendTo(dayGridContainerEl);
|
11905
|
+
this.el.find('.fc-body > tr > td').append(dayGridContainerEl);
|
11007
11906
|
|
11008
|
-
this.dayGrid.setElement(
|
11907
|
+
this.dayGrid.setElement(dayGridEl);
|
11009
11908
|
this.dayGrid.renderDates(this.hasRigidRows());
|
11010
11909
|
},
|
11011
11910
|
|
@@ -11024,6 +11923,7 @@ var BasicView = FC.BasicView = View.extend({
|
|
11024
11923
|
unrenderDates: function() {
|
11025
11924
|
this.dayGrid.unrenderDates();
|
11026
11925
|
this.dayGrid.removeElement();
|
11926
|
+
this.scroller.destroy();
|
11027
11927
|
},
|
11028
11928
|
|
11029
11929
|
|
@@ -11044,11 +11944,7 @@ var BasicView = FC.BasicView = View.extend({
|
|
11044
11944
|
'</thead>' +
|
11045
11945
|
'<tbody class="fc-body">' +
|
11046
11946
|
'<tr>' +
|
11047
|
-
'<td class="' + this.widgetContentClass + '">' +
|
11048
|
-
'<div class="fc-day-grid-container">' +
|
11049
|
-
'<div class="fc-day-grid"/>' +
|
11050
|
-
'</div>' +
|
11051
|
-
'</td>' +
|
11947
|
+
'<td class="' + this.widgetContentClass + '"></td>' +
|
11052
11948
|
'</tr>' +
|
11053
11949
|
'</tbody>' +
|
11054
11950
|
'</table>';
|
@@ -11091,9 +11987,10 @@ var BasicView = FC.BasicView = View.extend({
|
|
11091
11987
|
setHeight: function(totalHeight, isAuto) {
|
11092
11988
|
var eventLimit = this.opt('eventLimit');
|
11093
11989
|
var scrollerHeight;
|
11990
|
+
var scrollbarWidths;
|
11094
11991
|
|
11095
11992
|
// reset all heights to be natural
|
11096
|
-
|
11993
|
+
this.scroller.clear();
|
11097
11994
|
uncompensateScroll(this.headRowEl);
|
11098
11995
|
|
11099
11996
|
this.dayGrid.removeSegPopover(); // kill the "more" popover if displayed
|
@@ -11103,6 +12000,8 @@ var BasicView = FC.BasicView = View.extend({
|
|
11103
12000
|
this.dayGrid.limitRows(eventLimit); // limit the levels first so the height can redistribute after
|
11104
12001
|
}
|
11105
12002
|
|
12003
|
+
// distribute the height to the rows
|
12004
|
+
// (totalHeight is a "recommended" value if isAuto)
|
11106
12005
|
scrollerHeight = this.computeScrollerHeight(totalHeight);
|
11107
12006
|
this.setGridHeight(scrollerHeight, isAuto);
|
11108
12007
|
|
@@ -11111,17 +12010,33 @@ var BasicView = FC.BasicView = View.extend({
|
|
11111
12010
|
this.dayGrid.limitRows(eventLimit); // limit the levels after the grid's row heights have been set
|
11112
12011
|
}
|
11113
12012
|
|
11114
|
-
if (!isAuto
|
12013
|
+
if (!isAuto) { // should we force dimensions of the scroll container?
|
11115
12014
|
|
11116
|
-
|
12015
|
+
this.scroller.setHeight(scrollerHeight);
|
12016
|
+
scrollbarWidths = this.scroller.getScrollbarWidths();
|
11117
12017
|
|
11118
|
-
|
11119
|
-
|
11120
|
-
|
12018
|
+
if (scrollbarWidths.left || scrollbarWidths.right) { // using scrollbars?
|
12019
|
+
|
12020
|
+
compensateScroll(this.headRowEl, scrollbarWidths);
|
12021
|
+
|
12022
|
+
// doing the scrollbar compensation might have created text overflow which created more height. redo
|
12023
|
+
scrollerHeight = this.computeScrollerHeight(totalHeight);
|
12024
|
+
this.scroller.setHeight(scrollerHeight);
|
12025
|
+
}
|
12026
|
+
|
12027
|
+
// guarantees the same scrollbar widths
|
12028
|
+
this.scroller.lockOverflow(scrollbarWidths);
|
11121
12029
|
}
|
11122
12030
|
},
|
11123
12031
|
|
11124
12032
|
|
12033
|
+
// given a desired total height of the view, returns what the height of the scroller should be
|
12034
|
+
computeScrollerHeight: function(totalHeight) {
|
12035
|
+
return totalHeight -
|
12036
|
+
subtractInnerElHeight(this.el, this.scroller.el); // everything that's NOT the scroller
|
12037
|
+
},
|
12038
|
+
|
12039
|
+
|
11125
12040
|
// Sets the height of just the DayGrid component in this view
|
11126
12041
|
setGridHeight: function(height, isAuto) {
|
11127
12042
|
if (isAuto) {
|
@@ -11133,6 +12048,20 @@ var BasicView = FC.BasicView = View.extend({
|
|
11133
12048
|
},
|
11134
12049
|
|
11135
12050
|
|
12051
|
+
/* Scroll
|
12052
|
+
------------------------------------------------------------------------------------------------------------------*/
|
12053
|
+
|
12054
|
+
|
12055
|
+
queryScroll: function() {
|
12056
|
+
return this.scroller.getScrollTop();
|
12057
|
+
},
|
12058
|
+
|
12059
|
+
|
12060
|
+
setScroll: function(top) {
|
12061
|
+
this.scroller.setScrollTop(top);
|
12062
|
+
},
|
12063
|
+
|
12064
|
+
|
11136
12065
|
/* Hit Areas
|
11137
12066
|
------------------------------------------------------------------------------------------------------------------*/
|
11138
12067
|
// forward all hit-related method calls to dayGrid
|
@@ -11368,6 +12297,8 @@ fcViews.month = {
|
|
11368
12297
|
|
11369
12298
|
var AgendaView = FC.AgendaView = View.extend({
|
11370
12299
|
|
12300
|
+
scroller: null,
|
12301
|
+
|
11371
12302
|
timeGridClass: TimeGrid, // class used to instantiate the timeGrid. subclasses can override
|
11372
12303
|
timeGrid: null, // the main time-grid subcomponent of this view
|
11373
12304
|
|
@@ -11377,11 +12308,10 @@ var AgendaView = FC.AgendaView = View.extend({
|
|
11377
12308
|
axisWidth: null, // the width of the time axis running down the side
|
11378
12309
|
|
11379
12310
|
headContainerEl: null, // div that hold's the timeGrid's rendered date header
|
11380
|
-
noScrollRowEls: null, // set of fake row elements that must compensate when
|
12311
|
+
noScrollRowEls: null, // set of fake row elements that must compensate when scroller has scrollbars
|
11381
12312
|
|
11382
12313
|
// when the time-grid isn't tall enough to occupy the given height, we render an <hr> underneath
|
11383
12314
|
bottomRuleEl: null,
|
11384
|
-
bottomRuleHeight: null,
|
11385
12315
|
|
11386
12316
|
|
11387
12317
|
initialize: function() {
|
@@ -11390,6 +12320,11 @@ var AgendaView = FC.AgendaView = View.extend({
|
|
11390
12320
|
if (this.opt('allDaySlot')) { // should we display the "all-day" area?
|
11391
12321
|
this.dayGrid = this.instantiateDayGrid(); // the all-day subcomponent of this view
|
11392
12322
|
}
|
12323
|
+
|
12324
|
+
this.scroller = new Scroller({
|
12325
|
+
overflowX: 'hidden',
|
12326
|
+
overflowY: 'auto'
|
12327
|
+
});
|
11393
12328
|
},
|
11394
12329
|
|
11395
12330
|
|
@@ -11430,10 +12365,12 @@ var AgendaView = FC.AgendaView = View.extend({
|
|
11430
12365
|
this.el.addClass('fc-agenda-view').html(this.renderSkeletonHtml());
|
11431
12366
|
this.renderHead();
|
11432
12367
|
|
11433
|
-
|
11434
|
-
|
12368
|
+
this.scroller.render();
|
12369
|
+
var timeGridWrapEl = this.scroller.el.addClass('fc-time-grid-container');
|
12370
|
+
var timeGridEl = $('<div class="fc-time-grid" />').appendTo(timeGridWrapEl);
|
12371
|
+
this.el.find('.fc-body > tr > td').append(timeGridWrapEl);
|
11435
12372
|
|
11436
|
-
this.timeGrid.setElement(
|
12373
|
+
this.timeGrid.setElement(timeGridEl);
|
11437
12374
|
this.timeGrid.renderDates();
|
11438
12375
|
|
11439
12376
|
// the <hr> that sometimes displays under the time-grid
|
@@ -11470,6 +12407,8 @@ var AgendaView = FC.AgendaView = View.extend({
|
|
11470
12407
|
this.dayGrid.unrenderDates();
|
11471
12408
|
this.dayGrid.removeElement();
|
11472
12409
|
}
|
12410
|
+
|
12411
|
+
this.scroller.destroy();
|
11473
12412
|
},
|
11474
12413
|
|
11475
12414
|
|
@@ -11491,9 +12430,6 @@ var AgendaView = FC.AgendaView = View.extend({
|
|
11491
12430
|
'<hr class="fc-divider ' + this.widgetHeaderClass + '"/>' :
|
11492
12431
|
''
|
11493
12432
|
) +
|
11494
|
-
'<div class="fc-time-grid-container">' +
|
11495
|
-
'<div class="fc-time-grid"/>' +
|
11496
|
-
'</div>' +
|
11497
12433
|
'</td>' +
|
11498
12434
|
'</tr>' +
|
11499
12435
|
'</tbody>' +
|
@@ -11573,16 +12509,11 @@ var AgendaView = FC.AgendaView = View.extend({
|
|
11573
12509
|
setHeight: function(totalHeight, isAuto) {
|
11574
12510
|
var eventLimit;
|
11575
12511
|
var scrollerHeight;
|
11576
|
-
|
11577
|
-
if (this.bottomRuleHeight === null) {
|
11578
|
-
// calculate the height of the rule the very first time
|
11579
|
-
this.bottomRuleHeight = this.bottomRuleEl.outerHeight();
|
11580
|
-
}
|
11581
|
-
this.bottomRuleEl.hide(); // .show() will be called later if this <hr> is necessary
|
12512
|
+
var scrollbarWidths;
|
11582
12513
|
|
11583
12514
|
// reset all dimensions back to the original state
|
11584
|
-
this.
|
11585
|
-
|
12515
|
+
this.bottomRuleEl.hide(); // .show() will be called later if this <hr> is necessary
|
12516
|
+
this.scroller.clear(); // sets height to 'auto' and clears overflow
|
11586
12517
|
uncompensateScroll(this.noScrollRowEls);
|
11587
12518
|
|
11588
12519
|
// limit number of events in the all-day area
|
@@ -11598,28 +12529,46 @@ var AgendaView = FC.AgendaView = View.extend({
|
|
11598
12529
|
}
|
11599
12530
|
}
|
11600
12531
|
|
11601
|
-
if (!isAuto) { // should we force dimensions of the scroll container
|
12532
|
+
if (!isAuto) { // should we force dimensions of the scroll container?
|
11602
12533
|
|
11603
12534
|
scrollerHeight = this.computeScrollerHeight(totalHeight);
|
11604
|
-
|
12535
|
+
this.scroller.setHeight(scrollerHeight);
|
12536
|
+
scrollbarWidths = this.scroller.getScrollbarWidths();
|
12537
|
+
|
12538
|
+
if (scrollbarWidths.left || scrollbarWidths.right) { // using scrollbars?
|
11605
12539
|
|
11606
12540
|
// make the all-day and header rows lines up
|
11607
|
-
compensateScroll(this.noScrollRowEls,
|
12541
|
+
compensateScroll(this.noScrollRowEls, scrollbarWidths);
|
11608
12542
|
|
11609
12543
|
// the scrollbar compensation might have changed text flow, which might affect height, so recalculate
|
11610
12544
|
// and reapply the desired height to the scroller.
|
11611
12545
|
scrollerHeight = this.computeScrollerHeight(totalHeight);
|
11612
|
-
this.
|
12546
|
+
this.scroller.setHeight(scrollerHeight);
|
11613
12547
|
}
|
11614
|
-
|
11615
|
-
|
11616
|
-
|
12548
|
+
|
12549
|
+
// guarantees the same scrollbar widths
|
12550
|
+
this.scroller.lockOverflow(scrollbarWidths);
|
12551
|
+
|
12552
|
+
// if there's any space below the slats, show the horizontal rule.
|
12553
|
+
// this won't cause any new overflow, because lockOverflow already called.
|
12554
|
+
if (this.timeGrid.getTotalSlatHeight() < scrollerHeight) {
|
11617
12555
|
this.bottomRuleEl.show();
|
11618
12556
|
}
|
11619
12557
|
}
|
11620
12558
|
},
|
11621
12559
|
|
11622
12560
|
|
12561
|
+
// given a desired total height of the view, returns what the height of the scroller should be
|
12562
|
+
computeScrollerHeight: function(totalHeight) {
|
12563
|
+
return totalHeight -
|
12564
|
+
subtractInnerElHeight(this.el, this.scroller.el); // everything that's NOT the scroller
|
12565
|
+
},
|
12566
|
+
|
12567
|
+
|
12568
|
+
/* Scroll
|
12569
|
+
------------------------------------------------------------------------------------------------------------------*/
|
12570
|
+
|
12571
|
+
|
11623
12572
|
// Computes the initial pre-configured scroll state prior to allowing the user to change it
|
11624
12573
|
computeInitialScroll: function() {
|
11625
12574
|
var scrollTime = moment.duration(this.opt('scrollTime'));
|
@@ -11636,6 +12585,16 @@ var AgendaView = FC.AgendaView = View.extend({
|
|
11636
12585
|
},
|
11637
12586
|
|
11638
12587
|
|
12588
|
+
queryScroll: function() {
|
12589
|
+
return this.scroller.getScrollTop();
|
12590
|
+
},
|
12591
|
+
|
12592
|
+
|
12593
|
+
setScroll: function(top) {
|
12594
|
+
this.scroller.setScrollTop(top);
|
12595
|
+
},
|
12596
|
+
|
12597
|
+
|
11639
12598
|
/* Hit Areas
|
11640
12599
|
------------------------------------------------------------------------------------------------------------------*/
|
11641
12600
|
// forward all hit-related method calls to the grids (dayGrid might not be defined)
|