fullcalendar-rails 3.1.0.0 → 3.2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +3 -3
- data/lib/fullcalendar-rails/version.rb +1 -1
- data/vendor/assets/javascripts/fullcalendar.js +751 -358
- data/vendor/assets/javascripts/fullcalendar/gcal.js +3 -3
- data/vendor/assets/javascripts/fullcalendar/lang/fi.js +1 -1
- data/vendor/assets/javascripts/fullcalendar/lang/hr.js +1 -1
- data/vendor/assets/javascripts/fullcalendar/lang/zh-tw.js +1 -1
- data/vendor/assets/javascripts/fullcalendar/locale-all.js +4 -4
- data/vendor/assets/stylesheets/fullcalendar.css +3 -3
- data/vendor/assets/stylesheets/fullcalendar.print.css +3 -3
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8a9e720e772af873af48653c6f8d09c45b214052
|
4
|
+
data.tar.gz: c74f48f1e08b16b4414b21f3bcca51e7a3895032
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cf3cd9399a0c8c6c33f7772d53200ca21650c7510ee8896c127a42df4e90c4a29f9773f41810acada3c9f136070eeb2cdb76adb533f5125864633da5c6ddc68b
|
7
|
+
data.tar.gz: 2c229f7e829db414536aca4ad36ab8ef82e79c30f7cd11cbcfb6661eab1a8dd0bc3f1d96ea6c41eff864eb1326c7c5a5282ac2777a660c74d33bb6a2adb60b57
|
data/README.md
CHANGED
@@ -34,7 +34,7 @@ In order to install the fullcalendar-rails gem and get FullCalendar working with
|
|
34
34
|
|
35
35
|
1. Reference the Using FullCalendar section for details on populating FullCalendar.
|
36
36
|
|
37
|
-
### Installing Google
|
37
|
+
### Installing Google Calendar support
|
38
38
|
FullCalendar comes with Google calendar support, which can be implemented within your application with the following step:
|
39
39
|
|
40
40
|
* Using `gem fullcalendar-rails >= 2.1.1`, add `//= require fullcalendar/gcal` to `application.js`
|
@@ -48,7 +48,7 @@ If you want a specific version of FullCalendar, use the following line in your G
|
|
48
48
|
where **X.Y.Z** is the specific version of FullCalendar you wish to install (**Note: the last number "0" in the line above indicates the version of the fullcalendar-rails gem and may be something other than "0", but will still provide the FullCalendar version specified by X.Y.Z**).
|
49
49
|
|
50
50
|
### Install for fullcalendar-print
|
51
|
-
After following the above instalations steps, you may choose to use the `fullcalendar-print` file within your application to better customize the appearance of
|
51
|
+
After following the above instalations steps, you may choose to use the `fullcalendar-print` file within your application to better customize the appearance of FullCalendar. To do so, follow these steps:
|
52
52
|
|
53
53
|
+ Option 1: Add to `application.css`
|
54
54
|
```css
|
@@ -75,7 +75,7 @@ After following the above instalations steps, you may choose to use the `fullcal
|
|
75
75
|
|
76
76
|
## Using FullCalendar
|
77
77
|
A step by step tutorial for creating events for FullCalendar in rails may be followed here:
|
78
|
-
http://blog.crowdint.com/2014/02/18/fancy-calendars-for-your-web-application-with-fullcalendar.html
|
78
|
+
https://web.archive.org/web/20160531044930/http://blog.crowdint.com/2014/02/18/fancy-calendars-for-your-web-application-with-fullcalendar.html
|
79
79
|
|
80
80
|
And general documentation for FullCalendar may be found here:
|
81
81
|
http://fullcalendar.io/docs/
|
@@ -1,7 +1,7 @@
|
|
1
1
|
/*!
|
2
|
-
* FullCalendar v3.
|
3
|
-
* Docs & License:
|
4
|
-
* (c)
|
2
|
+
* FullCalendar v3.2.0
|
3
|
+
* Docs & License: https://fullcalendar.io/
|
4
|
+
* (c) 2017 Adam Shaw
|
5
5
|
*/
|
6
6
|
|
7
7
|
(function(factory) {
|
@@ -19,8 +19,11 @@
|
|
19
19
|
;;
|
20
20
|
|
21
21
|
var FC = $.fullCalendar = {
|
22
|
-
version: "3.
|
23
|
-
|
22
|
+
version: "3.2.0",
|
23
|
+
// When introducing internal API incompatibilities (where fullcalendar plugins would break),
|
24
|
+
// the minor version of the calendar should be upped (ex: 2.7.2 -> 2.8.0)
|
25
|
+
// and the below integer should be incremented.
|
26
|
+
internalApiVersion: 8
|
24
27
|
};
|
25
28
|
var fcViews = FC.views = {};
|
26
29
|
|
@@ -313,12 +316,13 @@ function getContentRect(el, origin) {
|
|
313
316
|
// NOTE: should use clientLeft/clientTop, but very unreliable cross-browser.
|
314
317
|
function getScrollbarWidths(el) {
|
315
318
|
var leftRightWidth = el.innerWidth() - el[0].clientWidth; // the paddings cancel out, leaving the scrollbars
|
316
|
-
var
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
319
|
+
var bottomWidth = el.innerHeight() - el[0].clientHeight; // "
|
320
|
+
var widths;
|
321
|
+
|
322
|
+
leftRightWidth = sanitizeScrollbarWidth(leftRightWidth);
|
323
|
+
bottomWidth = sanitizeScrollbarWidth(bottomWidth);
|
324
|
+
|
325
|
+
widths = { left: 0, right: 0, top: 0, bottom: bottomWidth };
|
322
326
|
|
323
327
|
if (getIsLeftRtlScrollbars() && el.css('direction') == 'rtl') { // is the scrollbar on the left side?
|
324
328
|
widths.left = leftRightWidth;
|
@@ -331,6 +335,15 @@ function getScrollbarWidths(el) {
|
|
331
335
|
}
|
332
336
|
|
333
337
|
|
338
|
+
// The scrollbar width computations in getScrollbarWidths are sometimes flawed when it comes to
|
339
|
+
// retina displays, rounding, and IE11. Massage them into a usable value.
|
340
|
+
function sanitizeScrollbarWidth(width) {
|
341
|
+
width = Math.max(0, width); // no negatives
|
342
|
+
width = Math.round(width);
|
343
|
+
return width;
|
344
|
+
}
|
345
|
+
|
346
|
+
|
334
347
|
// Logic for determining if, when the element is right-to-left, the scrollbar appears on the left side
|
335
348
|
|
336
349
|
var _isLeftRtlScrollbars = null;
|
@@ -381,24 +394,28 @@ function isPrimaryMouseButton(ev) {
|
|
381
394
|
|
382
395
|
|
383
396
|
function getEvX(ev) {
|
384
|
-
if (ev.pageX !== undefined) {
|
385
|
-
return ev.pageX;
|
386
|
-
}
|
387
397
|
var touches = ev.originalEvent.touches;
|
388
|
-
|
398
|
+
|
399
|
+
// on mobile FF, pageX for touch events is present, but incorrect,
|
400
|
+
// so, look at touch coordinates first.
|
401
|
+
if (touches && touches.length) {
|
389
402
|
return touches[0].pageX;
|
390
403
|
}
|
404
|
+
|
405
|
+
return ev.pageX;
|
391
406
|
}
|
392
407
|
|
393
408
|
|
394
409
|
function getEvY(ev) {
|
395
|
-
if (ev.pageY !== undefined) {
|
396
|
-
return ev.pageY;
|
397
|
-
}
|
398
410
|
var touches = ev.originalEvent.touches;
|
399
|
-
|
411
|
+
|
412
|
+
// on mobile FF, pageX for touch events is present, but incorrect,
|
413
|
+
// so, look at touch coordinates first.
|
414
|
+
if (touches && touches.length) {
|
400
415
|
return touches[0].pageY;
|
401
416
|
}
|
417
|
+
|
418
|
+
return ev.pageY;
|
402
419
|
}
|
403
420
|
|
404
421
|
|
@@ -413,33 +430,15 @@ function preventSelection(el) {
|
|
413
430
|
}
|
414
431
|
|
415
432
|
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
}
|
420
|
-
|
421
|
-
|
422
|
-
// attach a handler to get called when ANY scroll action happens on the page.
|
423
|
-
// this was impossible to do with normal on/off because 'scroll' doesn't bubble.
|
424
|
-
// http://stackoverflow.com/a/32954565/96342
|
425
|
-
// returns `true` on success.
|
426
|
-
function bindAnyScroll(handler) {
|
427
|
-
if (window.addEventListener) {
|
428
|
-
window.addEventListener('scroll', handler, true); // useCapture=true
|
429
|
-
return true;
|
430
|
-
}
|
431
|
-
return false;
|
433
|
+
function allowSelection(el) {
|
434
|
+
el.removeClass('fc-unselectable')
|
435
|
+
.off('selectstart', preventDefault);
|
432
436
|
}
|
433
437
|
|
434
438
|
|
435
|
-
//
|
436
|
-
|
437
|
-
|
438
|
-
if (window.removeEventListener) {
|
439
|
-
window.removeEventListener('scroll', handler, true); // useCapture=true
|
440
|
-
return true;
|
441
|
-
}
|
442
|
-
return false;
|
439
|
+
// Stops a mouse/touch event from doing it's native browser action
|
440
|
+
function preventDefault(ev) {
|
441
|
+
ev.preventDefault();
|
443
442
|
}
|
444
443
|
|
445
444
|
|
@@ -1329,38 +1328,42 @@ newMomentProto.toISOString = function() {
|
|
1329
1328
|
};
|
1330
1329
|
|
1331
1330
|
;;
|
1331
|
+
(function() {
|
1332
1332
|
|
1333
|
-
//
|
1334
|
-
|
1335
|
-
|
1336
|
-
|
1337
|
-
|
1338
|
-
function oldMomentFormat(mom, formatStr) {
|
1339
|
-
return oldMomentProto.format.call(mom, formatStr); // oldMomentProto defined in moment-ext.js
|
1340
|
-
}
|
1341
|
-
|
1333
|
+
// exports
|
1334
|
+
FC.formatDate = formatDate;
|
1335
|
+
FC.formatRange = formatRange;
|
1336
|
+
FC.oldMomentFormat = oldMomentFormat;
|
1337
|
+
FC.queryMostGranularFormatUnit = queryMostGranularFormatUnit;
|
1342
1338
|
|
1343
|
-
// Formats `date` with a Moment formatting string, but allow our non-zero areas and
|
1344
|
-
// additional token.
|
1345
|
-
function formatDate(date, formatStr) {
|
1346
|
-
return formatDateWithChunks(date, getFormatStringChunks(formatStr));
|
1347
|
-
}
|
1348
1339
|
|
1340
|
+
// Config
|
1341
|
+
// ---------------------------------------------------------------------------------------------------------------------
|
1349
1342
|
|
1350
|
-
|
1351
|
-
|
1352
|
-
|
1353
|
-
|
1354
|
-
|
1355
|
-
|
1356
|
-
}
|
1343
|
+
/*
|
1344
|
+
Inserted between chunks in the fake ("intermediate") formatting string.
|
1345
|
+
Important that it passes as whitespace (\s) because moment often identifies non-standalone months
|
1346
|
+
via a regexp with an \s.
|
1347
|
+
*/
|
1348
|
+
var PART_SEPARATOR = '\u000b'; // vertical tab
|
1357
1349
|
|
1358
|
-
|
1359
|
-
|
1350
|
+
/*
|
1351
|
+
Inserted as the first character of a literal-text chunk to indicate that the literal text is not actually literal text,
|
1352
|
+
but rather, a "special" token that has custom rendering (see specialTokens map).
|
1353
|
+
*/
|
1354
|
+
var SPECIAL_TOKEN_MARKER = '\u001f'; // information separator 1
|
1360
1355
|
|
1356
|
+
/*
|
1357
|
+
Inserted at the beginning and end of a span of text that must have non-zero numeric characters.
|
1358
|
+
Handling of these markers is done in a post-processing step at the very end of text rendering.
|
1359
|
+
*/
|
1360
|
+
var MAYBE_MARKER = '\u001e'; // information separator 2
|
1361
|
+
var MAYBE_REGEXP = new RegExp(MAYBE_MARKER + '([^' + MAYBE_MARKER + ']*)' + MAYBE_MARKER, 'g'); // must be global
|
1361
1362
|
|
1362
|
-
|
1363
|
-
|
1363
|
+
/*
|
1364
|
+
Addition formatting tokens we want recognized
|
1365
|
+
*/
|
1366
|
+
var specialTokens = {
|
1364
1367
|
t: function(date) { // "a" or "p"
|
1365
1368
|
return oldMomentFormat(date, 'a').charAt(0);
|
1366
1369
|
},
|
@@ -1369,28 +1372,39 @@ var tokenOverrides = {
|
|
1369
1372
|
}
|
1370
1373
|
};
|
1371
1374
|
|
1375
|
+
/*
|
1376
|
+
The first characters of formatting tokens for units that are 1 day or larger.
|
1377
|
+
`value` is for ranking relative size (lower means bigger).
|
1378
|
+
`unit` is a normalized unit, used for comparing moments.
|
1379
|
+
*/
|
1380
|
+
var largeTokenMap = {
|
1381
|
+
Y: { value: 1, unit: 'year' },
|
1382
|
+
M: { value: 2, unit: 'month' },
|
1383
|
+
W: { value: 3, unit: 'week' }, // ISO week
|
1384
|
+
w: { value: 3, unit: 'week' }, // local week
|
1385
|
+
D: { value: 4, unit: 'day' }, // day of month
|
1386
|
+
d: { value: 4, unit: 'day' } // day of week
|
1387
|
+
};
|
1388
|
+
|
1372
1389
|
|
1373
|
-
|
1374
|
-
|
1375
|
-
var maybeStr;
|
1390
|
+
// Single Date Formatting
|
1391
|
+
// ---------------------------------------------------------------------------------------------------------------------
|
1376
1392
|
|
1377
|
-
|
1378
|
-
|
1379
|
-
|
1380
|
-
|
1381
|
-
|
1382
|
-
|
1383
|
-
|
1384
|
-
|
1385
|
-
|
1386
|
-
else if (chunk.maybe) { // a grouping of other chunks that must be non-zero
|
1387
|
-
maybeStr = formatDateWithChunks(date, chunk.maybe);
|
1388
|
-
if (maybeStr.match(/[1-9]/)) {
|
1389
|
-
return maybeStr;
|
1390
|
-
}
|
1391
|
-
}
|
1393
|
+
/*
|
1394
|
+
Formats `date` with a Moment formatting string, but allow our non-zero areas and special token
|
1395
|
+
*/
|
1396
|
+
function formatDate(date, formatStr) {
|
1397
|
+
return renderFakeFormatString(
|
1398
|
+
getParsedFormatString(formatStr).fakeFormatString,
|
1399
|
+
date
|
1400
|
+
);
|
1401
|
+
}
|
1392
1402
|
|
1393
|
-
|
1403
|
+
/*
|
1404
|
+
Call this if you want Moment's original format method to be used
|
1405
|
+
*/
|
1406
|
+
function oldMomentFormat(mom, formatStr) {
|
1407
|
+
return oldMomentProto.format.call(mom, formatStr); // oldMomentProto defined in moment-ext.js
|
1394
1408
|
}
|
1395
1409
|
|
1396
1410
|
|
@@ -1398,10 +1412,12 @@ function formatDateWithChunk(date, chunk) {
|
|
1398
1412
|
// -------------------------------------------------------------------------------------------------
|
1399
1413
|
// TODO: make it work with timezone offset
|
1400
1414
|
|
1401
|
-
|
1402
|
-
|
1403
|
-
|
1404
|
-
|
1415
|
+
/*
|
1416
|
+
Using a formatting string meant for a single date, generate a range string, like
|
1417
|
+
"Sep 2 - 9 2013", that intelligently inserts a separator where the dates differ.
|
1418
|
+
If the dates are the same as far as the format string is concerned, just return a single
|
1419
|
+
rendering of one date, without any separator.
|
1420
|
+
*/
|
1405
1421
|
function formatRange(date1, date2, formatStr, separator, isRTL) {
|
1406
1422
|
var localeData;
|
1407
1423
|
|
@@ -1410,28 +1426,31 @@ function formatRange(date1, date2, formatStr, separator, isRTL) {
|
|
1410
1426
|
|
1411
1427
|
localeData = date1.localeData();
|
1412
1428
|
|
1413
|
-
// Expand localized format strings, like "LL" -> "MMMM D YYYY"
|
1414
|
-
formatStr = localeData.longDateFormat(formatStr) || formatStr;
|
1429
|
+
// Expand localized format strings, like "LL" -> "MMMM D YYYY".
|
1415
1430
|
// BTW, this is not important for `formatDate` because it is impossible to put custom tokens
|
1416
1431
|
// or non-zero areas in Moment's localized format strings.
|
1432
|
+
formatStr = localeData.longDateFormat(formatStr) || formatStr;
|
1417
1433
|
|
1418
|
-
|
1419
|
-
|
1420
|
-
return formatRangeWithChunks(
|
1434
|
+
return renderParsedFormat(
|
1435
|
+
getParsedFormatString(formatStr),
|
1421
1436
|
date1,
|
1422
1437
|
date2,
|
1423
|
-
|
1424
|
-
separator,
|
1438
|
+
separator || ' - ',
|
1425
1439
|
isRTL
|
1426
1440
|
);
|
1427
1441
|
}
|
1428
|
-
FC.formatRange = formatRange; // expose
|
1429
1442
|
|
1430
|
-
|
1431
|
-
|
1432
|
-
|
1443
|
+
/*
|
1444
|
+
Renders a range with an already-parsed format string.
|
1445
|
+
*/
|
1446
|
+
function renderParsedFormat(parsedFormat, date1, date2, separator, isRTL) {
|
1447
|
+
var sameUnits = parsedFormat.sameUnits;
|
1448
|
+
var unzonedDate1 = date1.clone().stripZone(); // for same-unit comparisons
|
1433
1449
|
var unzonedDate2 = date2.clone().stripZone(); // "
|
1434
|
-
|
1450
|
+
|
1451
|
+
var renderedParts1 = renderFakeFormatStringParts(parsedFormat.fakeFormatString, date1);
|
1452
|
+
var renderedParts2 = renderFakeFormatStringParts(parsedFormat.fakeFormatString, date2);
|
1453
|
+
|
1435
1454
|
var leftI;
|
1436
1455
|
var leftStr = '';
|
1437
1456
|
var rightI;
|
@@ -1443,28 +1462,35 @@ function formatRangeWithChunks(date1, date2, chunks, separator, isRTL) {
|
|
1443
1462
|
|
1444
1463
|
// Start at the leftmost side of the formatting string and continue until you hit a token
|
1445
1464
|
// that is not the same between dates.
|
1446
|
-
for (
|
1447
|
-
|
1448
|
-
|
1449
|
-
|
1450
|
-
|
1451
|
-
leftStr +=
|
1465
|
+
for (
|
1466
|
+
leftI = 0;
|
1467
|
+
leftI < sameUnits.length && (!sameUnits[leftI] || unzonedDate1.isSame(unzonedDate2, sameUnits[leftI]));
|
1468
|
+
leftI++
|
1469
|
+
) {
|
1470
|
+
leftStr += renderedParts1[leftI];
|
1452
1471
|
}
|
1453
1472
|
|
1454
1473
|
// Similarly, start at the rightmost side of the formatting string and move left
|
1455
|
-
for (
|
1456
|
-
|
1457
|
-
|
1474
|
+
for (
|
1475
|
+
rightI = sameUnits.length - 1;
|
1476
|
+
rightI > leftI && (!sameUnits[rightI] || unzonedDate1.isSame(unzonedDate2, sameUnits[rightI]));
|
1477
|
+
rightI--
|
1478
|
+
) {
|
1479
|
+
// If current chunk is on the boundary of unique date-content, and is a special-case
|
1480
|
+
// date-formatting postfix character, then don't consume it. Consider it unique date-content.
|
1481
|
+
// TODO: make configurable
|
1482
|
+
if (rightI - 1 === leftI && renderedParts1[rightI] === '.') {
|
1458
1483
|
break;
|
1459
1484
|
}
|
1460
|
-
|
1485
|
+
|
1486
|
+
rightStr = renderedParts1[rightI] + rightStr;
|
1461
1487
|
}
|
1462
1488
|
|
1463
1489
|
// The area in the middle is different for both of the dates.
|
1464
1490
|
// Collect them distinctly so we can jam them together later.
|
1465
|
-
for (middleI=leftI; middleI<=rightI; middleI++) {
|
1466
|
-
middleStr1 +=
|
1467
|
-
middleStr2 +=
|
1491
|
+
for (middleI = leftI; middleI <= rightI; middleI++) {
|
1492
|
+
middleStr1 += renderedParts1[middleI];
|
1493
|
+
middleStr2 += renderedParts2[middleI];
|
1468
1494
|
}
|
1469
1495
|
|
1470
1496
|
if (middleStr1 || middleStr2) {
|
@@ -1476,77 +1502,59 @@ function formatRangeWithChunks(date1, date2, chunks, separator, isRTL) {
|
|
1476
1502
|
}
|
1477
1503
|
}
|
1478
1504
|
|
1479
|
-
return
|
1505
|
+
return processMaybeMarkers(
|
1506
|
+
leftStr + middleStr + rightStr
|
1507
|
+
);
|
1480
1508
|
}
|
1481
1509
|
|
1482
1510
|
|
1483
|
-
|
1484
|
-
|
1485
|
-
M: 'month',
|
1486
|
-
D: 'day', // day of month
|
1487
|
-
d: 'day', // day of week
|
1488
|
-
// prevents a separator between anything time-related...
|
1489
|
-
A: 'second', // AM/PM
|
1490
|
-
a: 'second', // am/pm
|
1491
|
-
T: 'second', // A/P
|
1492
|
-
t: 'second', // a/p
|
1493
|
-
H: 'second', // hour (24)
|
1494
|
-
h: 'second', // hour (12)
|
1495
|
-
m: 'second', // minute
|
1496
|
-
s: 'second' // second
|
1497
|
-
};
|
1498
|
-
// TODO: week maybe?
|
1499
|
-
|
1500
|
-
|
1501
|
-
// Given a formatting chunk, and given that both dates are similar in the regard the
|
1502
|
-
// formatting chunk is concerned, format date1 against `chunk`. Otherwise, return `false`.
|
1503
|
-
function formatSimilarChunk(date1, date2, unzonedDate1, unzonedDate2, chunk) {
|
1504
|
-
var token;
|
1505
|
-
var unit;
|
1511
|
+
// Format String Parsing
|
1512
|
+
// ---------------------------------------------------------------------------------------------------------------------
|
1506
1513
|
|
1507
|
-
|
1508
|
-
return chunk;
|
1509
|
-
}
|
1510
|
-
else if ((token = chunk.token)) {
|
1511
|
-
unit = similarUnitMap[token.charAt(0)];
|
1514
|
+
var parsedFormatStrCache = {};
|
1512
1515
|
|
1513
|
-
|
1514
|
-
|
1515
|
-
|
1516
|
-
|
1517
|
-
|
1518
|
-
|
1519
|
-
}
|
1520
|
-
|
1521
|
-
return false; // the chunk is NOT the same for the two dates
|
1522
|
-
// BTW, don't support splitting on non-zero areas
|
1516
|
+
/*
|
1517
|
+
Returns a parsed format string, leveraging a cache.
|
1518
|
+
*/
|
1519
|
+
function getParsedFormatString(formatStr) {
|
1520
|
+
return parsedFormatStrCache[formatStr] ||
|
1521
|
+
(parsedFormatStrCache[formatStr] = parseFormatString(formatStr));
|
1523
1522
|
}
|
1524
1523
|
|
1525
|
-
|
1526
|
-
|
1527
|
-
|
1528
|
-
|
1529
|
-
|
1530
|
-
|
1531
|
-
|
1532
|
-
|
1533
|
-
function
|
1534
|
-
|
1535
|
-
|
1536
|
-
|
1537
|
-
|
1524
|
+
/*
|
1525
|
+
Parses a format string into the following:
|
1526
|
+
- fakeFormatString: a momentJS formatting string, littered with special control characters that get post-processed.
|
1527
|
+
- sameUnits: for every part in fakeFormatString, if the part is a token, the value will be a unit string (like "day"),
|
1528
|
+
that indicates how similar a range's start & end must be in order to share the same formatted text.
|
1529
|
+
If not a token, then the value is null.
|
1530
|
+
Always a flat array (not nested liked "chunks").
|
1531
|
+
*/
|
1532
|
+
function parseFormatString(formatStr) {
|
1533
|
+
var chunks = chunkFormatString(formatStr);
|
1534
|
+
|
1535
|
+
return {
|
1536
|
+
fakeFormatString: buildFakeFormatString(chunks),
|
1537
|
+
sameUnits: buildSameUnits(chunks)
|
1538
|
+
};
|
1538
1539
|
}
|
1539
1540
|
|
1540
|
-
|
1541
|
-
|
1541
|
+
/*
|
1542
|
+
Break the formatting string into an array of chunks.
|
1543
|
+
A 'maybe' chunk will have nested chunks.
|
1544
|
+
*/
|
1542
1545
|
function chunkFormatString(formatStr) {
|
1543
1546
|
var chunks = [];
|
1544
|
-
var chunker = /\[([^\]]*)\]|\(([^\)]*)\)|(LTS|LT|(\w)\4*o?)|([^\w\[\(]+)/g; // TODO: more descrimination
|
1545
1547
|
var match;
|
1546
1548
|
|
1549
|
+
// TODO: more descrimination
|
1550
|
+
// \4 is a backreference to the first character of a multi-character set.
|
1551
|
+
var chunker = /\[([^\]]*)\]|\(([^\)]*)\)|(LTS|LT|(\w)\4*o?)|([^\w\[\(]+)/g;
|
1552
|
+
|
1547
1553
|
while ((match = chunker.exec(formatStr))) {
|
1548
1554
|
if (match[1]) { // a literal string inside [ ... ]
|
1549
|
-
chunks.push(
|
1555
|
+
chunks.push.apply(chunks, // append
|
1556
|
+
splitStringLiteral(match[1])
|
1557
|
+
);
|
1550
1558
|
}
|
1551
1559
|
else if (match[2]) { // non-zero formatting inside ( ... )
|
1552
1560
|
chunks.push({ maybe: chunkFormatString(match[2]) });
|
@@ -1555,41 +1563,166 @@ function chunkFormatString(formatStr) {
|
|
1555
1563
|
chunks.push({ token: match[3] });
|
1556
1564
|
}
|
1557
1565
|
else if (match[5]) { // an unenclosed literal string
|
1558
|
-
chunks.push(
|
1566
|
+
chunks.push.apply(chunks, // append
|
1567
|
+
splitStringLiteral(match[5])
|
1568
|
+
);
|
1559
1569
|
}
|
1560
1570
|
}
|
1561
1571
|
|
1562
1572
|
return chunks;
|
1563
1573
|
}
|
1564
1574
|
|
1575
|
+
/*
|
1576
|
+
Potentially splits a literal-text string into multiple parts. For special cases.
|
1577
|
+
*/
|
1578
|
+
function splitStringLiteral(s) {
|
1579
|
+
if (s === '. ') {
|
1580
|
+
return [ '.', ' ' ]; // for locales with periods bound to the end of each year/month/date
|
1581
|
+
}
|
1582
|
+
else {
|
1583
|
+
return [ s ];
|
1584
|
+
}
|
1585
|
+
}
|
1565
1586
|
|
1566
|
-
|
1567
|
-
|
1587
|
+
/*
|
1588
|
+
Given chunks parsed from a real format string, generate a fake (aka "intermediate") format string with special control
|
1589
|
+
characters that will eventually be given to moment for formatting, and then post-processed.
|
1590
|
+
*/
|
1591
|
+
function buildFakeFormatString(chunks) {
|
1592
|
+
var parts = [];
|
1593
|
+
var i, chunk;
|
1568
1594
|
|
1595
|
+
for (i = 0; i < chunks.length; i++) {
|
1596
|
+
chunk = chunks[i];
|
1569
1597
|
|
1570
|
-
|
1571
|
-
|
1572
|
-
|
1573
|
-
|
1574
|
-
|
1575
|
-
|
1576
|
-
|
1577
|
-
|
1578
|
-
|
1579
|
-
}
|
1598
|
+
if (typeof chunk === 'string') {
|
1599
|
+
parts.push('[' + chunk + ']');
|
1600
|
+
}
|
1601
|
+
else if (chunk.token) {
|
1602
|
+
if (chunk.token in specialTokens) {
|
1603
|
+
parts.push(
|
1604
|
+
SPECIAL_TOKEN_MARKER + // useful during post-processing
|
1605
|
+
'[' + chunk.token + ']' // preserve as literal text
|
1606
|
+
);
|
1607
|
+
}
|
1608
|
+
else {
|
1609
|
+
parts.push(chunk.token); // unprotected text implies a format string
|
1610
|
+
}
|
1611
|
+
}
|
1612
|
+
else if (chunk.maybe) {
|
1613
|
+
parts.push(
|
1614
|
+
MAYBE_MARKER + // useful during post-processing
|
1615
|
+
buildFakeFormatString(chunk.maybe) +
|
1616
|
+
MAYBE_MARKER
|
1617
|
+
);
|
1618
|
+
}
|
1619
|
+
}
|
1620
|
+
|
1621
|
+
return parts.join(PART_SEPARATOR);
|
1622
|
+
}
|
1580
1623
|
|
1581
|
-
|
1582
|
-
|
1583
|
-
|
1584
|
-
|
1624
|
+
/*
|
1625
|
+
Given parsed chunks from a real formatting string, generates an array of unit strings (like "day") that indicate
|
1626
|
+
in which regard two dates must be similar in order to share range formatting text.
|
1627
|
+
The `chunks` can be nested (because of "maybe" chunks), however, the returned array will be flat.
|
1628
|
+
*/
|
1629
|
+
function buildSameUnits(chunks) {
|
1630
|
+
var units = [];
|
1631
|
+
var i, chunk;
|
1632
|
+
var tokenInfo;
|
1633
|
+
|
1634
|
+
for (i = 0; i < chunks.length; i++) {
|
1635
|
+
chunk = chunks[i];
|
1636
|
+
|
1637
|
+
if (chunk.token) {
|
1638
|
+
tokenInfo = largeTokenMap[chunk.token.charAt(0)];
|
1639
|
+
units.push(tokenInfo ? tokenInfo.unit : 'second'); // default to a very strict same-second
|
1640
|
+
}
|
1641
|
+
else if (chunk.maybe) {
|
1642
|
+
units.push.apply(units, // append
|
1643
|
+
buildSameUnits(chunk.maybe)
|
1644
|
+
);
|
1645
|
+
}
|
1646
|
+
else {
|
1647
|
+
units.push(null);
|
1648
|
+
}
|
1649
|
+
}
|
1650
|
+
|
1651
|
+
return units;
|
1652
|
+
}
|
1653
|
+
|
1654
|
+
|
1655
|
+
// Rendering to text
|
1656
|
+
// ---------------------------------------------------------------------------------------------------------------------
|
1657
|
+
|
1658
|
+
/*
|
1659
|
+
Formats a date with a fake format string, post-processes the control characters, then returns.
|
1660
|
+
*/
|
1661
|
+
function renderFakeFormatString(fakeFormatString, date) {
|
1662
|
+
return processMaybeMarkers(
|
1663
|
+
renderFakeFormatStringParts(fakeFormatString, date).join('')
|
1664
|
+
);
|
1665
|
+
}
|
1666
|
+
|
1667
|
+
/*
|
1668
|
+
Formats a date into parts that will have been post-processed, EXCEPT for the "maybe" markers.
|
1669
|
+
*/
|
1670
|
+
function renderFakeFormatStringParts(fakeFormatString, date) {
|
1671
|
+
var parts = [];
|
1672
|
+
var fakeRender = oldMomentFormat(date, fakeFormatString);
|
1673
|
+
var fakeParts = fakeRender.split(PART_SEPARATOR);
|
1674
|
+
var i, fakePart;
|
1675
|
+
|
1676
|
+
for (i = 0; i < fakeParts.length; i++) {
|
1677
|
+
fakePart = fakeParts[i];
|
1678
|
+
|
1679
|
+
if (fakePart.charAt(0) === SPECIAL_TOKEN_MARKER) {
|
1680
|
+
parts.push(
|
1681
|
+
// the literal string IS the token's name.
|
1682
|
+
// call special token's registered function.
|
1683
|
+
specialTokens[fakePart.substring(1)](date)
|
1684
|
+
);
|
1685
|
+
}
|
1686
|
+
else {
|
1687
|
+
parts.push(fakePart);
|
1688
|
+
}
|
1689
|
+
}
|
1690
|
+
|
1691
|
+
return parts;
|
1692
|
+
}
|
1693
|
+
|
1694
|
+
/*
|
1695
|
+
Accepts an almost-finally-formatted string and processes the "maybe" control characters, returning a new string.
|
1696
|
+
*/
|
1697
|
+
function processMaybeMarkers(s) {
|
1698
|
+
return s.replace(MAYBE_REGEXP, function(m0, m1) { // regex assumed to have 'g' flag
|
1699
|
+
if (m1.match(/[1-9]/)) { // any non-zero numeric characters?
|
1700
|
+
return m1;
|
1701
|
+
}
|
1702
|
+
else {
|
1703
|
+
return '';
|
1704
|
+
}
|
1705
|
+
});
|
1706
|
+
}
|
1707
|
+
|
1708
|
+
|
1709
|
+
// Misc Utils
|
1710
|
+
// -------------------------------------------------------------------------------------------------
|
1711
|
+
|
1712
|
+
/*
|
1713
|
+
Returns a unit string, either 'year', 'month', 'day', or null for the most granular formatting token in the string.
|
1714
|
+
*/
|
1715
|
+
function queryMostGranularFormatUnit(formatStr) {
|
1716
|
+
var chunks = chunkFormatString(formatStr);
|
1585
1717
|
var i, chunk;
|
1586
1718
|
var candidate;
|
1587
1719
|
var best;
|
1588
1720
|
|
1589
1721
|
for (i = 0; i < chunks.length; i++) {
|
1590
1722
|
chunk = chunks[i];
|
1723
|
+
|
1591
1724
|
if (chunk.token) {
|
1592
|
-
candidate =
|
1725
|
+
candidate = largeTokenMap[chunk.token.charAt(0)];
|
1593
1726
|
if (candidate) {
|
1594
1727
|
if (!best || candidate.value > best.value) {
|
1595
1728
|
best = candidate;
|
@@ -1605,6 +1738,13 @@ FC.queryMostGranularFormatUnit = function(formatStr) {
|
|
1605
1738
|
return null;
|
1606
1739
|
};
|
1607
1740
|
|
1741
|
+
})();
|
1742
|
+
|
1743
|
+
// quick local references
|
1744
|
+
var formatDate = FC.formatDate;
|
1745
|
+
var formatRange = FC.formatRange;
|
1746
|
+
var oldMomentFormat = FC.oldMomentFormat;
|
1747
|
+
|
1608
1748
|
;;
|
1609
1749
|
|
1610
1750
|
FC.Class = Class; // export
|
@@ -1998,35 +2138,6 @@ var ListenerMixin = FC.ListenerMixin = (function() {
|
|
1998
2138
|
})();
|
1999
2139
|
;;
|
2000
2140
|
|
2001
|
-
// simple class for toggle a `isIgnoringMouse` flag on delay
|
2002
|
-
// initMouseIgnoring must first be called, with a millisecond delay setting.
|
2003
|
-
var MouseIgnorerMixin = {
|
2004
|
-
|
2005
|
-
isIgnoringMouse: false, // bool
|
2006
|
-
delayUnignoreMouse: null, // method
|
2007
|
-
|
2008
|
-
|
2009
|
-
initMouseIgnoring: function(delay) {
|
2010
|
-
this.delayUnignoreMouse = debounce(proxy(this, 'unignoreMouse'), delay || 1000);
|
2011
|
-
},
|
2012
|
-
|
2013
|
-
|
2014
|
-
// temporarily ignore mouse actions on segments
|
2015
|
-
tempIgnoreMouse: function() {
|
2016
|
-
this.isIgnoringMouse = true;
|
2017
|
-
this.delayUnignoreMouse();
|
2018
|
-
},
|
2019
|
-
|
2020
|
-
|
2021
|
-
// delayUnignoreMouse eventually calls this
|
2022
|
-
unignoreMouse: function() {
|
2023
|
-
this.isIgnoringMouse = false;
|
2024
|
-
}
|
2025
|
-
|
2026
|
-
};
|
2027
|
-
|
2028
|
-
;;
|
2029
|
-
|
2030
2141
|
/* A rectangular panel that is absolutely positioned over other content
|
2031
2142
|
------------------------------------------------------------------------------------------------------------------------
|
2032
2143
|
Options:
|
@@ -2457,7 +2568,7 @@ var CoordCache = FC.CoordCache = Class.extend({
|
|
2457
2568
|
----------------------------------------------------------------------------------------------------------------------*/
|
2458
2569
|
// TODO: use Emitter
|
2459
2570
|
|
2460
|
-
var DragListener = FC.DragListener = Class.extend(ListenerMixin,
|
2571
|
+
var DragListener = FC.DragListener = Class.extend(ListenerMixin, {
|
2461
2572
|
|
2462
2573
|
options: null,
|
2463
2574
|
subjectEl: null,
|
@@ -2480,13 +2591,12 @@ var DragListener = FC.DragListener = Class.extend(ListenerMixin, MouseIgnorerMix
|
|
2480
2591
|
delayTimeoutId: null,
|
2481
2592
|
minDistance: null,
|
2482
2593
|
|
2483
|
-
|
2594
|
+
shouldCancelTouchScroll: true,
|
2595
|
+
scrollAlwaysKills: false,
|
2484
2596
|
|
2485
2597
|
|
2486
2598
|
constructor: function(options) {
|
2487
2599
|
this.options = options || {};
|
2488
|
-
this.handleTouchScrollProxy = proxy(this, 'handleTouchScroll');
|
2489
|
-
this.initMouseIgnoring(500);
|
2490
2600
|
},
|
2491
2601
|
|
2492
2602
|
|
@@ -2498,7 +2608,7 @@ var DragListener = FC.DragListener = Class.extend(ListenerMixin, MouseIgnorerMix
|
|
2498
2608
|
var isTouch = getEvIsTouch(ev);
|
2499
2609
|
|
2500
2610
|
if (ev.type === 'mousedown') {
|
2501
|
-
if (
|
2611
|
+
if (GlobalEmitter.get().shouldIgnoreMouse()) {
|
2502
2612
|
return;
|
2503
2613
|
}
|
2504
2614
|
else if (!isPrimaryMouseButton(ev)) {
|
@@ -2517,6 +2627,8 @@ var DragListener = FC.DragListener = Class.extend(ListenerMixin, MouseIgnorerMix
|
|
2517
2627
|
this.minDistance = firstDefined(extraOptions.distance, this.options.distance, 0);
|
2518
2628
|
this.subjectEl = this.options.subjectEl;
|
2519
2629
|
|
2630
|
+
preventSelection($('body'));
|
2631
|
+
|
2520
2632
|
this.isInteracting = true;
|
2521
2633
|
this.isTouch = isTouch;
|
2522
2634
|
this.isDelayEnded = false;
|
@@ -2558,12 +2670,7 @@ var DragListener = FC.DragListener = Class.extend(ListenerMixin, MouseIgnorerMix
|
|
2558
2670
|
this.isInteracting = false;
|
2559
2671
|
this.handleInteractionEnd(ev, isCancelled);
|
2560
2672
|
|
2561
|
-
|
2562
|
-
// mouseover + mouseout + click
|
2563
|
-
// let's ignore these bogus events
|
2564
|
-
if (this.isTouch) {
|
2565
|
-
this.tempIgnoreMouse();
|
2566
|
-
}
|
2673
|
+
allowSelection($('body'));
|
2567
2674
|
}
|
2568
2675
|
},
|
2569
2676
|
|
@@ -2578,45 +2685,25 @@ var DragListener = FC.DragListener = Class.extend(ListenerMixin, MouseIgnorerMix
|
|
2578
2685
|
|
2579
2686
|
|
2580
2687
|
bindHandlers: function() {
|
2581
|
-
|
2582
|
-
|
2688
|
+
// some browsers (Safari in iOS 10) don't allow preventDefault on touch events that are bound after touchstart,
|
2689
|
+
// so listen to the GlobalEmitter singleton, which is always bound, instead of the document directly.
|
2690
|
+
var globalEmitter = GlobalEmitter.get();
|
2583
2691
|
|
2584
2692
|
if (this.isTouch) {
|
2585
|
-
this.listenTo(
|
2693
|
+
this.listenTo(globalEmitter, {
|
2586
2694
|
touchmove: this.handleTouchMove,
|
2587
2695
|
touchend: this.endInteraction,
|
2588
|
-
|
2589
|
-
|
2590
|
-
// Sometimes touchend doesn't fire
|
2591
|
-
// (can't figure out why. touchcancel doesn't fire either. has to do with scrolling?)
|
2592
|
-
// If another touchstart happens, we know it's bogus, so cancel the drag.
|
2593
|
-
// touchend will continue to be broken until user does a shorttap/scroll, but this is best we can do.
|
2594
|
-
touchstart: function(ev) {
|
2595
|
-
if (touchStartIgnores) { // bindHandlers is called from within a touchstart,
|
2596
|
-
touchStartIgnores--; // and we don't want this to fire immediately, so ignore.
|
2597
|
-
}
|
2598
|
-
else {
|
2599
|
-
_this.endInteraction(ev, true); // isCancelled=true
|
2600
|
-
}
|
2601
|
-
}
|
2696
|
+
scroll: this.handleTouchScroll
|
2602
2697
|
});
|
2603
|
-
|
2604
|
-
// listen to ALL scroll actions on the page
|
2605
|
-
if (
|
2606
|
-
!bindAnyScroll(this.handleTouchScrollProxy) && // hopefully this works and short-circuits the rest
|
2607
|
-
this.scrollEl // otherwise, attach a single handler to this
|
2608
|
-
) {
|
2609
|
-
this.listenTo(this.scrollEl, 'scroll', this.handleTouchScroll);
|
2610
|
-
}
|
2611
2698
|
}
|
2612
2699
|
else {
|
2613
|
-
this.listenTo(
|
2700
|
+
this.listenTo(globalEmitter, {
|
2614
2701
|
mousemove: this.handleMouseMove,
|
2615
2702
|
mouseup: this.endInteraction
|
2616
2703
|
});
|
2617
2704
|
}
|
2618
2705
|
|
2619
|
-
this.listenTo(
|
2706
|
+
this.listenTo(globalEmitter, {
|
2620
2707
|
selectstart: preventDefault, // don't allow selection while dragging
|
2621
2708
|
contextmenu: preventDefault // long taps would open menu on Chrome dev tools
|
2622
2709
|
});
|
@@ -2624,13 +2711,7 @@ var DragListener = FC.DragListener = Class.extend(ListenerMixin, MouseIgnorerMix
|
|
2624
2711
|
|
2625
2712
|
|
2626
2713
|
unbindHandlers: function() {
|
2627
|
-
this.stopListeningTo(
|
2628
|
-
|
2629
|
-
// unbind scroll listening
|
2630
|
-
unbindAnyScroll(this.handleTouchScrollProxy);
|
2631
|
-
if (this.scrollEl) {
|
2632
|
-
this.stopListeningTo(this.scrollEl, 'scroll');
|
2633
|
-
}
|
2714
|
+
this.stopListeningTo(GlobalEmitter.get());
|
2634
2715
|
},
|
2635
2716
|
|
2636
2717
|
|
@@ -2738,8 +2819,9 @@ var DragListener = FC.DragListener = Class.extend(ListenerMixin, MouseIgnorerMix
|
|
2738
2819
|
|
2739
2820
|
|
2740
2821
|
handleTouchMove: function(ev) {
|
2822
|
+
|
2741
2823
|
// prevent inertia and touchmove-scrolling while dragging
|
2742
|
-
if (this.isDragging) {
|
2824
|
+
if (this.isDragging && this.shouldCancelTouchScroll) {
|
2743
2825
|
ev.preventDefault();
|
2744
2826
|
}
|
2745
2827
|
|
@@ -2759,7 +2841,7 @@ var DragListener = FC.DragListener = Class.extend(ListenerMixin, MouseIgnorerMix
|
|
2759
2841
|
handleTouchScroll: function(ev) {
|
2760
2842
|
// if the drag is being initiated by touch, but a scroll happens before
|
2761
2843
|
// the drag-initiating delay is over, cancel the drag
|
2762
|
-
if (!this.isDragging) {
|
2844
|
+
if (!this.isDragging || this.scrollAlwaysKills) {
|
2763
2845
|
this.endInteraction(ev, true); // isCancelled=true
|
2764
2846
|
}
|
2765
2847
|
},
|
@@ -2982,7 +3064,7 @@ options:
|
|
2982
3064
|
var HitDragListener = DragListener.extend({
|
2983
3065
|
|
2984
3066
|
component: null, // converts coordinates to hits
|
2985
|
-
// methods:
|
3067
|
+
// methods: hitsNeeded, hitsNotNeeded, queryHit
|
2986
3068
|
|
2987
3069
|
origHit: null, // the hit the mouse was over when listening started
|
2988
3070
|
hit: null, // the hit the mouse is over
|
@@ -3004,7 +3086,8 @@ var HitDragListener = DragListener.extend({
|
|
3004
3086
|
var origPoint;
|
3005
3087
|
var point;
|
3006
3088
|
|
3007
|
-
this.
|
3089
|
+
this.component.hitsNeeded();
|
3090
|
+
this.computeScrollBounds(); // for autoscroll
|
3008
3091
|
|
3009
3092
|
if (ev) {
|
3010
3093
|
origPoint = { left: getEvX(ev), top: getEvY(ev) };
|
@@ -3043,13 +3126,6 @@ var HitDragListener = DragListener.extend({
|
|
3043
3126
|
},
|
3044
3127
|
|
3045
3128
|
|
3046
|
-
// Recomputes the drag-critical positions of elements
|
3047
|
-
computeCoords: function() {
|
3048
|
-
this.component.prepareHits();
|
3049
|
-
this.computeScrollBounds(); // why is this here??????
|
3050
|
-
},
|
3051
|
-
|
3052
|
-
|
3053
3129
|
// Called when the actual drag has started
|
3054
3130
|
handleDragStart: function(ev) {
|
3055
3131
|
var hit;
|
@@ -3128,7 +3204,7 @@ var HitDragListener = DragListener.extend({
|
|
3128
3204
|
this.origHit = null;
|
3129
3205
|
this.hit = null;
|
3130
3206
|
|
3131
|
-
this.component.
|
3207
|
+
this.component.hitsNotNeeded();
|
3132
3208
|
},
|
3133
3209
|
|
3134
3210
|
|
@@ -3136,7 +3212,12 @@ var HitDragListener = DragListener.extend({
|
|
3136
3212
|
handleScrollEnd: function() {
|
3137
3213
|
DragListener.prototype.handleScrollEnd.apply(this, arguments); // call the super-method
|
3138
3214
|
|
3139
|
-
|
3215
|
+
// hits' absolute positions will be in new places after a user's scroll.
|
3216
|
+
// HACK for recomputing.
|
3217
|
+
if (this.isDragging) {
|
3218
|
+
this.component.releaseHits();
|
3219
|
+
this.component.prepareHits();
|
3220
|
+
}
|
3140
3221
|
},
|
3141
3222
|
|
3142
3223
|
|
@@ -3186,6 +3267,231 @@ function isHitPropsWithin(subHit, superHit) {
|
|
3186
3267
|
|
3187
3268
|
;;
|
3188
3269
|
|
3270
|
+
/*
|
3271
|
+
Listens to document and window-level user-interaction events, like touch events and mouse events,
|
3272
|
+
and fires these events as-is to whoever is observing a GlobalEmitter.
|
3273
|
+
Best when used as a singleton via GlobalEmitter.get()
|
3274
|
+
|
3275
|
+
Normalizes mouse/touch events. For examples:
|
3276
|
+
- ignores the the simulated mouse events that happen after a quick tap: mousemove+mousedown+mouseup+click
|
3277
|
+
- compensates for various buggy scenarios where a touchend does not fire
|
3278
|
+
*/
|
3279
|
+
|
3280
|
+
FC.touchMouseIgnoreWait = 500;
|
3281
|
+
|
3282
|
+
var GlobalEmitter = Class.extend(ListenerMixin, EmitterMixin, {
|
3283
|
+
|
3284
|
+
isTouching: false,
|
3285
|
+
mouseIgnoreDepth: 0,
|
3286
|
+
handleScrollProxy: null,
|
3287
|
+
|
3288
|
+
|
3289
|
+
bind: function() {
|
3290
|
+
var _this = this;
|
3291
|
+
|
3292
|
+
this.listenTo($(document), {
|
3293
|
+
touchstart: this.handleTouchStart,
|
3294
|
+
touchcancel: this.handleTouchCancel,
|
3295
|
+
touchend: this.handleTouchEnd,
|
3296
|
+
mousedown: this.handleMouseDown,
|
3297
|
+
mousemove: this.handleMouseMove,
|
3298
|
+
mouseup: this.handleMouseUp,
|
3299
|
+
click: this.handleClick,
|
3300
|
+
selectstart: this.handleSelectStart,
|
3301
|
+
contextmenu: this.handleContextMenu
|
3302
|
+
});
|
3303
|
+
|
3304
|
+
// because we need to call preventDefault
|
3305
|
+
// because https://www.chromestatus.com/features/5093566007214080
|
3306
|
+
// TODO: investigate performance because this is a global handler
|
3307
|
+
window.addEventListener(
|
3308
|
+
'touchmove',
|
3309
|
+
this.handleTouchMoveProxy = function(ev) {
|
3310
|
+
_this.handleTouchMove($.Event(ev));
|
3311
|
+
},
|
3312
|
+
{ passive: false } // allows preventDefault()
|
3313
|
+
);
|
3314
|
+
|
3315
|
+
// attach a handler to get called when ANY scroll action happens on the page.
|
3316
|
+
// this was impossible to do with normal on/off because 'scroll' doesn't bubble.
|
3317
|
+
// http://stackoverflow.com/a/32954565/96342
|
3318
|
+
window.addEventListener(
|
3319
|
+
'scroll',
|
3320
|
+
this.handleScrollProxy = function(ev) {
|
3321
|
+
_this.handleScroll($.Event(ev));
|
3322
|
+
},
|
3323
|
+
true // useCapture
|
3324
|
+
);
|
3325
|
+
},
|
3326
|
+
|
3327
|
+
unbind: function() {
|
3328
|
+
this.stopListeningTo($(document));
|
3329
|
+
|
3330
|
+
window.removeEventListener(
|
3331
|
+
'touchmove',
|
3332
|
+
this.handleTouchMoveProxy
|
3333
|
+
);
|
3334
|
+
|
3335
|
+
window.removeEventListener(
|
3336
|
+
'scroll',
|
3337
|
+
this.handleScrollProxy,
|
3338
|
+
true // useCapture
|
3339
|
+
);
|
3340
|
+
},
|
3341
|
+
|
3342
|
+
|
3343
|
+
// Touch Handlers
|
3344
|
+
// -----------------------------------------------------------------------------------------------------------------
|
3345
|
+
|
3346
|
+
handleTouchStart: function(ev) {
|
3347
|
+
|
3348
|
+
// if a previous touch interaction never ended with a touchend, then implicitly end it,
|
3349
|
+
// but since a new touch interaction is about to begin, don't start the mouse ignore period.
|
3350
|
+
this.stopTouch(ev, true); // skipMouseIgnore=true
|
3351
|
+
|
3352
|
+
this.isTouching = true;
|
3353
|
+
this.trigger('touchstart', ev);
|
3354
|
+
},
|
3355
|
+
|
3356
|
+
handleTouchMove: function(ev) {
|
3357
|
+
if (this.isTouching) {
|
3358
|
+
this.trigger('touchmove', ev);
|
3359
|
+
}
|
3360
|
+
},
|
3361
|
+
|
3362
|
+
handleTouchCancel: function(ev) {
|
3363
|
+
if (this.isTouching) {
|
3364
|
+
this.trigger('touchcancel', ev);
|
3365
|
+
|
3366
|
+
// Have touchcancel fire an artificial touchend. That way, handlers won't need to listen to both.
|
3367
|
+
// If touchend fires later, it won't have any effect b/c isTouching will be false.
|
3368
|
+
this.stopTouch(ev);
|
3369
|
+
}
|
3370
|
+
},
|
3371
|
+
|
3372
|
+
handleTouchEnd: function(ev) {
|
3373
|
+
this.stopTouch(ev);
|
3374
|
+
},
|
3375
|
+
|
3376
|
+
|
3377
|
+
// Mouse Handlers
|
3378
|
+
// -----------------------------------------------------------------------------------------------------------------
|
3379
|
+
|
3380
|
+
handleMouseDown: function(ev) {
|
3381
|
+
if (!this.shouldIgnoreMouse()) {
|
3382
|
+
this.trigger('mousedown', ev);
|
3383
|
+
}
|
3384
|
+
},
|
3385
|
+
|
3386
|
+
handleMouseMove: function(ev) {
|
3387
|
+
if (!this.shouldIgnoreMouse()) {
|
3388
|
+
this.trigger('mousemove', ev);
|
3389
|
+
}
|
3390
|
+
},
|
3391
|
+
|
3392
|
+
handleMouseUp: function(ev) {
|
3393
|
+
if (!this.shouldIgnoreMouse()) {
|
3394
|
+
this.trigger('mouseup', ev);
|
3395
|
+
}
|
3396
|
+
},
|
3397
|
+
|
3398
|
+
handleClick: function(ev) {
|
3399
|
+
if (!this.shouldIgnoreMouse()) {
|
3400
|
+
this.trigger('click', ev);
|
3401
|
+
}
|
3402
|
+
},
|
3403
|
+
|
3404
|
+
|
3405
|
+
// Misc Handlers
|
3406
|
+
// -----------------------------------------------------------------------------------------------------------------
|
3407
|
+
|
3408
|
+
handleSelectStart: function(ev) {
|
3409
|
+
this.trigger('selectstart', ev);
|
3410
|
+
},
|
3411
|
+
|
3412
|
+
handleContextMenu: function(ev) {
|
3413
|
+
this.trigger('contextmenu', ev);
|
3414
|
+
},
|
3415
|
+
|
3416
|
+
handleScroll: function(ev) {
|
3417
|
+
this.trigger('scroll', ev);
|
3418
|
+
},
|
3419
|
+
|
3420
|
+
|
3421
|
+
// Utils
|
3422
|
+
// -----------------------------------------------------------------------------------------------------------------
|
3423
|
+
|
3424
|
+
stopTouch: function(ev, skipMouseIgnore) {
|
3425
|
+
if (this.isTouching) {
|
3426
|
+
this.isTouching = false;
|
3427
|
+
this.trigger('touchend', ev);
|
3428
|
+
|
3429
|
+
if (!skipMouseIgnore) {
|
3430
|
+
this.startTouchMouseIgnore();
|
3431
|
+
}
|
3432
|
+
}
|
3433
|
+
},
|
3434
|
+
|
3435
|
+
startTouchMouseIgnore: function() {
|
3436
|
+
var _this = this;
|
3437
|
+
var wait = FC.touchMouseIgnoreWait;
|
3438
|
+
|
3439
|
+
if (wait) {
|
3440
|
+
this.mouseIgnoreDepth++;
|
3441
|
+
setTimeout(function() {
|
3442
|
+
_this.mouseIgnoreDepth--;
|
3443
|
+
}, wait);
|
3444
|
+
}
|
3445
|
+
},
|
3446
|
+
|
3447
|
+
shouldIgnoreMouse: function() {
|
3448
|
+
return this.isTouching || Boolean(this.mouseIgnoreDepth);
|
3449
|
+
}
|
3450
|
+
|
3451
|
+
});
|
3452
|
+
|
3453
|
+
|
3454
|
+
// Singleton
|
3455
|
+
// ---------------------------------------------------------------------------------------------------------------------
|
3456
|
+
|
3457
|
+
(function() {
|
3458
|
+
var globalEmitter = null;
|
3459
|
+
var neededCount = 0;
|
3460
|
+
|
3461
|
+
|
3462
|
+
// gets the singleton
|
3463
|
+
GlobalEmitter.get = function() {
|
3464
|
+
|
3465
|
+
if (!globalEmitter) {
|
3466
|
+
globalEmitter = new GlobalEmitter();
|
3467
|
+
globalEmitter.bind();
|
3468
|
+
}
|
3469
|
+
|
3470
|
+
return globalEmitter;
|
3471
|
+
};
|
3472
|
+
|
3473
|
+
|
3474
|
+
// called when an object knows it will need a GlobalEmitter in the near future.
|
3475
|
+
GlobalEmitter.needed = function() {
|
3476
|
+
GlobalEmitter.get(); // ensures globalEmitter
|
3477
|
+
neededCount++;
|
3478
|
+
};
|
3479
|
+
|
3480
|
+
|
3481
|
+
// called when the object that originally called needed() doesn't need a GlobalEmitter anymore.
|
3482
|
+
GlobalEmitter.unneeded = function() {
|
3483
|
+
neededCount--;
|
3484
|
+
|
3485
|
+
if (!neededCount) { // nobody else needs it
|
3486
|
+
globalEmitter.unbind();
|
3487
|
+
globalEmitter = null;
|
3488
|
+
}
|
3489
|
+
};
|
3490
|
+
|
3491
|
+
})();
|
3492
|
+
|
3493
|
+
;;
|
3494
|
+
|
3189
3495
|
/* Creates a clone of an element and lets it track the mouse as it moves
|
3190
3496
|
----------------------------------------------------------------------------------------------------------------------*/
|
3191
3497
|
|
@@ -3383,7 +3689,7 @@ var MouseFollower = Class.extend(ListenerMixin, {
|
|
3383
3689
|
/* An abstract class comprised of a "grid" of areas that each represent a specific datetime
|
3384
3690
|
----------------------------------------------------------------------------------------------------------------------*/
|
3385
3691
|
|
3386
|
-
var Grid = FC.Grid = Class.extend(ListenerMixin,
|
3692
|
+
var Grid = FC.Grid = Class.extend(ListenerMixin, {
|
3387
3693
|
|
3388
3694
|
// self-config, overridable by subclasses
|
3389
3695
|
hasDayInteractions: true, // can user click/select ranges of time?
|
@@ -3409,7 +3715,8 @@ var Grid = FC.Grid = Class.extend(ListenerMixin, MouseIgnorerMixin, {
|
|
3409
3715
|
// TODO: port isTimeScale into same system?
|
3410
3716
|
largeUnit: null,
|
3411
3717
|
|
3412
|
-
|
3718
|
+
dayClickListener: null,
|
3719
|
+
daySelectListener: null,
|
3413
3720
|
segDragListener: null,
|
3414
3721
|
segResizeListener: null,
|
3415
3722
|
externalDragListener: null,
|
@@ -3420,8 +3727,8 @@ var Grid = FC.Grid = Class.extend(ListenerMixin, MouseIgnorerMixin, {
|
|
3420
3727
|
this.isRTL = view.opt('isRTL');
|
3421
3728
|
this.elsByFill = {};
|
3422
3729
|
|
3423
|
-
this.
|
3424
|
-
this.
|
3730
|
+
this.dayClickListener = this.buildDayClickListener();
|
3731
|
+
this.daySelectListener = this.buildDaySelectListener();
|
3425
3732
|
},
|
3426
3733
|
|
3427
3734
|
|
@@ -3516,6 +3823,20 @@ var Grid = FC.Grid = Class.extend(ListenerMixin, MouseIgnorerMixin, {
|
|
3516
3823
|
/* Hit Area
|
3517
3824
|
------------------------------------------------------------------------------------------------------------------*/
|
3518
3825
|
|
3826
|
+
hitsNeededDepth: 0, // necessary because multiple callers might need the same hits
|
3827
|
+
|
3828
|
+
hitsNeeded: function() {
|
3829
|
+
if (!(this.hitsNeededDepth++)) {
|
3830
|
+
this.prepareHits();
|
3831
|
+
}
|
3832
|
+
},
|
3833
|
+
|
3834
|
+
hitsNotNeeded: function() {
|
3835
|
+
if (this.hitsNeededDepth && !(--this.hitsNeededDepth)) {
|
3836
|
+
this.releaseHits();
|
3837
|
+
}
|
3838
|
+
},
|
3839
|
+
|
3519
3840
|
|
3520
3841
|
// Called before one or more queryHit calls might happen. Should prepare any cached coordinates for queryHit
|
3521
3842
|
prepareHits: function() {
|
@@ -3643,9 +3964,19 @@ var Grid = FC.Grid = Class.extend(ListenerMixin, MouseIgnorerMixin, {
|
|
3643
3964
|
|
3644
3965
|
// Process a mousedown on an element that represents a day. For day clicking and selecting.
|
3645
3966
|
dayMousedown: function(ev) {
|
3646
|
-
|
3647
|
-
|
3648
|
-
|
3967
|
+
var view = this.view;
|
3968
|
+
|
3969
|
+
// prevent a user's clickaway for unselecting a range or an event from
|
3970
|
+
// causing a dayClick or starting an immediate new selection.
|
3971
|
+
if (view.isSelected || view.selectedEvent) {
|
3972
|
+
return;
|
3973
|
+
}
|
3974
|
+
|
3975
|
+
this.dayClickListener.startInteraction(ev);
|
3976
|
+
|
3977
|
+
if (view.opt('selectable')) {
|
3978
|
+
this.daySelectListener.startInteraction(ev, {
|
3979
|
+
distance: view.opt('selectMinDistance')
|
3649
3980
|
});
|
3650
3981
|
}
|
3651
3982
|
},
|
@@ -3653,40 +3984,79 @@ var Grid = FC.Grid = Class.extend(ListenerMixin, MouseIgnorerMixin, {
|
|
3653
3984
|
|
3654
3985
|
dayTouchStart: function(ev) {
|
3655
3986
|
var view = this.view;
|
3656
|
-
var selectLongPressDelay
|
3987
|
+
var selectLongPressDelay;
|
3657
3988
|
|
3658
|
-
//
|
3659
|
-
//
|
3989
|
+
// prevent a user's clickaway for unselecting a range or an event from
|
3990
|
+
// causing a dayClick or starting an immediate new selection.
|
3660
3991
|
if (view.isSelected || view.selectedEvent) {
|
3661
|
-
|
3992
|
+
return;
|
3662
3993
|
}
|
3663
3994
|
|
3995
|
+
selectLongPressDelay = view.opt('selectLongPressDelay');
|
3664
3996
|
if (selectLongPressDelay == null) {
|
3665
3997
|
selectLongPressDelay = view.opt('longPressDelay'); // fallback
|
3666
3998
|
}
|
3667
3999
|
|
3668
|
-
this.
|
3669
|
-
|
3670
|
-
|
4000
|
+
this.dayClickListener.startInteraction(ev);
|
4001
|
+
|
4002
|
+
if (view.opt('selectable')) {
|
4003
|
+
this.daySelectListener.startInteraction(ev, {
|
4004
|
+
delay: selectLongPressDelay
|
4005
|
+
});
|
4006
|
+
}
|
3671
4007
|
},
|
3672
4008
|
|
3673
4009
|
|
3674
|
-
// Creates a listener that tracks the user's drag across day elements.
|
3675
|
-
|
3676
|
-
buildDayDragListener: function() {
|
4010
|
+
// Creates a listener that tracks the user's drag across day elements, for day clicking.
|
4011
|
+
buildDayClickListener: function() {
|
3677
4012
|
var _this = this;
|
3678
4013
|
var view = this.view;
|
3679
|
-
var isSelectable = view.opt('selectable');
|
3680
4014
|
var dayClickHit; // null if invalid dayClick
|
4015
|
+
|
4016
|
+
var dragListener = new HitDragListener(this, {
|
4017
|
+
scroll: view.opt('dragScroll'),
|
4018
|
+
interactionStart: function() {
|
4019
|
+
dayClickHit = dragListener.origHit;
|
4020
|
+
},
|
4021
|
+
hitOver: function(hit, isOrig, origHit) {
|
4022
|
+
// if user dragged to another cell at any point, it can no longer be a dayClick
|
4023
|
+
if (!isOrig) {
|
4024
|
+
dayClickHit = null;
|
4025
|
+
}
|
4026
|
+
},
|
4027
|
+
hitOut: function() { // called before mouse moves to a different hit OR moved out of all hits
|
4028
|
+
dayClickHit = null;
|
4029
|
+
},
|
4030
|
+
interactionEnd: function(ev, isCancelled) {
|
4031
|
+
if (!isCancelled && dayClickHit) {
|
4032
|
+
view.triggerDayClick(
|
4033
|
+
_this.getHitSpan(dayClickHit),
|
4034
|
+
_this.getHitEl(dayClickHit),
|
4035
|
+
ev
|
4036
|
+
);
|
4037
|
+
}
|
4038
|
+
}
|
4039
|
+
});
|
4040
|
+
|
4041
|
+
// because dayClickListener won't be called with any time delay, "dragging" will begin immediately,
|
4042
|
+
// which will kill any touchmoving/scrolling. Prevent this.
|
4043
|
+
dragListener.shouldCancelTouchScroll = false;
|
4044
|
+
|
4045
|
+
dragListener.scrollAlwaysKills = true;
|
4046
|
+
|
4047
|
+
return dragListener;
|
4048
|
+
},
|
4049
|
+
|
4050
|
+
|
4051
|
+
// Creates a listener that tracks the user's drag across day elements, for day selecting.
|
4052
|
+
buildDaySelectListener: function() {
|
4053
|
+
var _this = this;
|
4054
|
+
var view = this.view;
|
3681
4055
|
var selectionSpan; // null if invalid selection
|
3682
4056
|
|
3683
|
-
// this listener tracks a mousedown on a day element, and a subsequent drag.
|
3684
|
-
// if the drag ends on the same day, it is a 'dayClick'.
|
3685
|
-
// if 'selectable' is enabled, this listener also detects selections.
|
3686
4057
|
var dragListener = new HitDragListener(this, {
|
3687
4058
|
scroll: view.opt('dragScroll'),
|
3688
4059
|
interactionStart: function() {
|
3689
|
-
dayClickHit = dragListener.origHit; // for dayClick, where no dragging happens
|
3690
4060
|
selectionSpan = null;
|
3691
4061
|
},
|
3692
4062
|
dragStart: function() {
|
@@ -3695,27 +4065,20 @@ var Grid = FC.Grid = Class.extend(ListenerMixin, MouseIgnorerMixin, {
|
|
3695
4065
|
hitOver: function(hit, isOrig, origHit) {
|
3696
4066
|
if (origHit) { // click needs to have started on a hit
|
3697
4067
|
|
3698
|
-
|
3699
|
-
|
3700
|
-
|
3701
|
-
|
4068
|
+
selectionSpan = _this.computeSelection(
|
4069
|
+
_this.getHitSpan(origHit),
|
4070
|
+
_this.getHitSpan(hit)
|
4071
|
+
);
|
3702
4072
|
|
3703
|
-
if (
|
3704
|
-
|
3705
|
-
|
3706
|
-
|
3707
|
-
);
|
3708
|
-
if (selectionSpan) {
|
3709
|
-
_this.renderSelection(selectionSpan);
|
3710
|
-
}
|
3711
|
-
else if (selectionSpan === false) {
|
3712
|
-
disableCursor();
|
3713
|
-
}
|
4073
|
+
if (selectionSpan) {
|
4074
|
+
_this.renderSelection(selectionSpan);
|
4075
|
+
}
|
4076
|
+
else if (selectionSpan === false) {
|
4077
|
+
disableCursor();
|
3714
4078
|
}
|
3715
4079
|
}
|
3716
4080
|
},
|
3717
4081
|
hitOut: function() { // called before mouse moves to a different hit OR moved out of all hits
|
3718
|
-
dayClickHit = null;
|
3719
4082
|
selectionSpan = null;
|
3720
4083
|
_this.unrenderSelection();
|
3721
4084
|
},
|
@@ -3723,21 +4086,9 @@ var Grid = FC.Grid = Class.extend(ListenerMixin, MouseIgnorerMixin, {
|
|
3723
4086
|
enableCursor();
|
3724
4087
|
},
|
3725
4088
|
interactionEnd: function(ev, isCancelled) {
|
3726
|
-
if (!isCancelled) {
|
3727
|
-
|
3728
|
-
|
3729
|
-
!_this.isIgnoringMouse // see hack in dayTouchStart
|
3730
|
-
) {
|
3731
|
-
view.triggerDayClick(
|
3732
|
-
_this.getHitSpan(dayClickHit),
|
3733
|
-
_this.getHitEl(dayClickHit),
|
3734
|
-
ev
|
3735
|
-
);
|
3736
|
-
}
|
3737
|
-
if (selectionSpan) {
|
3738
|
-
// the selection will already have been rendered. just report it
|
3739
|
-
view.reportSelection(selectionSpan, ev);
|
3740
|
-
}
|
4089
|
+
if (!isCancelled && selectionSpan) {
|
4090
|
+
// the selection will already have been rendered. just report it
|
4091
|
+
view.reportSelection(selectionSpan, ev);
|
3741
4092
|
}
|
3742
4093
|
}
|
3743
4094
|
});
|
@@ -3750,7 +4101,8 @@ var Grid = FC.Grid = Class.extend(ListenerMixin, MouseIgnorerMixin, {
|
|
3750
4101
|
// Useful for when public API methods that result in re-rendering are invoked during a drag.
|
3751
4102
|
// Also useful for when touch devices misbehave and don't fire their touchend.
|
3752
4103
|
clearDragListeners: function() {
|
3753
|
-
this.
|
4104
|
+
this.dayClickListener.endInteraction();
|
4105
|
+
this.daySelectListener.endInteraction();
|
3754
4106
|
|
3755
4107
|
if (this.segDragListener) {
|
3756
4108
|
this.segDragListener.endInteraction(); // will clear this.segDragListener
|
@@ -4269,7 +4621,6 @@ Grid.mixin({
|
|
4269
4621
|
// Attaches event-element-related handlers to an arbitrary container element. leverages bubbling.
|
4270
4622
|
bindSegHandlersToEl: function(el) {
|
4271
4623
|
this.bindSegHandlerToEl(el, 'touchstart', this.handleSegTouchStart);
|
4272
|
-
this.bindSegHandlerToEl(el, 'touchend', this.handleSegTouchEnd);
|
4273
4624
|
this.bindSegHandlerToEl(el, 'mouseenter', this.handleSegMouseover);
|
4274
4625
|
this.bindSegHandlerToEl(el, 'mouseleave', this.handleSegMouseout);
|
4275
4626
|
this.bindSegHandlerToEl(el, 'mousedown', this.handleSegMousedown);
|
@@ -4304,7 +4655,7 @@ Grid.mixin({
|
|
4304
4655
|
// Updates internal state and triggers handlers for when an event element is moused over
|
4305
4656
|
handleSegMouseover: function(seg, ev) {
|
4306
4657
|
if (
|
4307
|
-
!
|
4658
|
+
!GlobalEmitter.get().shouldIgnoreMouse() &&
|
4308
4659
|
!this.mousedOverSeg
|
4309
4660
|
) {
|
4310
4661
|
this.mousedOverSeg = seg;
|
@@ -4374,16 +4725,6 @@ Grid.mixin({
|
|
4374
4725
|
delay: isSelected ? 0 : eventLongPressDelay // do delay if not already selected
|
4375
4726
|
});
|
4376
4727
|
}
|
4377
|
-
|
4378
|
-
// a long tap simulates a mouseover. ignore this bogus mouseover.
|
4379
|
-
this.tempIgnoreMouse();
|
4380
|
-
},
|
4381
|
-
|
4382
|
-
|
4383
|
-
handleSegTouchEnd: function(seg, ev) {
|
4384
|
-
// touchstart+touchend = click, which simulates a mouseover.
|
4385
|
-
// ignore this bogus mouseover.
|
4386
|
-
this.tempIgnoreMouse();
|
4387
4728
|
},
|
4388
4729
|
|
4389
4730
|
|
@@ -4509,7 +4850,7 @@ Grid.mixin({
|
|
4509
4850
|
|
4510
4851
|
if (dropLocation) {
|
4511
4852
|
// no need to re-show original, will rerender all anyways. esp important if eventRenderWait
|
4512
|
-
view.
|
4853
|
+
view.reportSegDrop(seg, dropLocation, _this.largeUnit, el, ev);
|
4513
4854
|
}
|
4514
4855
|
else {
|
4515
4856
|
view.showEvent(event);
|
@@ -4812,7 +5153,7 @@ Grid.mixin({
|
|
4812
5153
|
|
4813
5154
|
if (resizeLocation) { // valid date to resize to?
|
4814
5155
|
// no need to re-show original, will rerender all anyways. esp important if eventRenderWait
|
4815
|
-
view.
|
5156
|
+
view.reportSegResize(seg, resizeLocation, _this.largeUnit, el, ev);
|
4816
5157
|
}
|
4817
5158
|
else {
|
4818
5159
|
view.showEvent(event);
|
@@ -6833,9 +7174,9 @@ DayGrid.mixin({
|
|
6833
7174
|
|
6834
7175
|
// because segments in the popover are not part of a grid coordinate system, provide a hint to any
|
6835
7176
|
// grids that want to do drag-n-drop about which cell it came from
|
6836
|
-
this.
|
7177
|
+
this.hitsNeeded();
|
6837
7178
|
segs[i].hit = this.getCellHit(row, col);
|
6838
|
-
this.
|
7179
|
+
this.hitsNotNeeded();
|
6839
7180
|
|
6840
7181
|
segContainer.append(segs[i].el);
|
6841
7182
|
}
|
@@ -8261,11 +8602,23 @@ var View = FC.View = Class.extend(EmitterMixin, ListenerMixin, {
|
|
8261
8602
|
|
8262
8603
|
// Computes what the title at the top of the calendar should be for this view
|
8263
8604
|
computeTitle: function() {
|
8605
|
+
var start, end;
|
8606
|
+
|
8607
|
+
// for views that span a large unit of time, show the proper interval, ignoring stray days before and after
|
8608
|
+
if (this.intervalUnit === 'year' || this.intervalUnit === 'month') {
|
8609
|
+
start = this.intervalStart;
|
8610
|
+
end = this.intervalEnd;
|
8611
|
+
}
|
8612
|
+
else { // for day units or smaller, use the actual day range
|
8613
|
+
start = this.start;
|
8614
|
+
end = this.end;
|
8615
|
+
}
|
8616
|
+
|
8264
8617
|
return this.formatRange(
|
8265
8618
|
{
|
8266
8619
|
// in case intervalStart/End has a time, make sure timezone is correct
|
8267
|
-
start: this.calendar.applyTimezone(
|
8268
|
-
end: this.calendar.applyTimezone(
|
8620
|
+
start: this.calendar.applyTimezone(start),
|
8621
|
+
end: this.calendar.applyTimezone(end)
|
8269
8622
|
},
|
8270
8623
|
this.opt('titleFormat') || this.computeTitleFormat(),
|
8271
8624
|
this.opt('titleRangeSeparator')
|
@@ -8579,14 +8932,16 @@ var View = FC.View = Class.extend(EmitterMixin, ListenerMixin, {
|
|
8579
8932
|
|
8580
8933
|
// Binds DOM handlers to elements that reside outside the view container, such as the document
|
8581
8934
|
bindGlobalHandlers: function() {
|
8582
|
-
this.listenTo(
|
8583
|
-
|
8935
|
+
this.listenTo(GlobalEmitter.get(), {
|
8936
|
+
touchstart: this.processUnselect,
|
8937
|
+
mousedown: this.handleDocumentMousedown
|
8938
|
+
});
|
8584
8939
|
},
|
8585
8940
|
|
8586
8941
|
|
8587
8942
|
// Unbinds DOM handlers from elements that reside outside the view container
|
8588
8943
|
unbindGlobalHandlers: function() {
|
8589
|
-
this.stopListeningTo(
|
8944
|
+
this.stopListeningTo(GlobalEmitter.get());
|
8590
8945
|
},
|
8591
8946
|
|
8592
8947
|
|
@@ -9158,15 +9513,15 @@ var View = FC.View = Class.extend(EmitterMixin, ListenerMixin, {
|
|
9158
9513
|
|
9159
9514
|
// Must be called when an event in the view is dropped onto new location.
|
9160
9515
|
// `dropLocation` is an object that contains the new zoned start/end/allDay values for the event.
|
9161
|
-
|
9516
|
+
reportSegDrop: function(seg, dropLocation, largeUnit, el, ev) {
|
9162
9517
|
var calendar = this.calendar;
|
9163
|
-
var mutateResult = calendar.
|
9518
|
+
var mutateResult = calendar.mutateSeg(seg, dropLocation, largeUnit);
|
9164
9519
|
var undoFunc = function() {
|
9165
9520
|
mutateResult.undo();
|
9166
9521
|
calendar.reportEventChange();
|
9167
9522
|
};
|
9168
9523
|
|
9169
|
-
this.triggerEventDrop(event, mutateResult.dateDelta, undoFunc, el, ev);
|
9524
|
+
this.triggerEventDrop(seg.event, mutateResult.dateDelta, undoFunc, el, ev);
|
9170
9525
|
calendar.reportEventChange(); // will rerender events
|
9171
9526
|
},
|
9172
9527
|
|
@@ -9261,15 +9616,15 @@ var View = FC.View = Class.extend(EmitterMixin, ListenerMixin, {
|
|
9261
9616
|
|
9262
9617
|
|
9263
9618
|
// Must be called when an event in the view has been resized to a new length
|
9264
|
-
|
9619
|
+
reportSegResize: function(seg, resizeLocation, largeUnit, el, ev) {
|
9265
9620
|
var calendar = this.calendar;
|
9266
|
-
var mutateResult = calendar.
|
9621
|
+
var mutateResult = calendar.mutateSeg(seg, resizeLocation, largeUnit);
|
9267
9622
|
var undoFunc = function() {
|
9268
9623
|
mutateResult.undo();
|
9269
9624
|
calendar.reportEventChange();
|
9270
9625
|
};
|
9271
9626
|
|
9272
|
-
this.triggerEventResize(event, mutateResult.durationDelta, undoFunc, el, ev);
|
9627
|
+
this.triggerEventResize(seg.event, mutateResult.durationDelta, undoFunc, el, ev);
|
9273
9628
|
calendar.reportEventChange(); // will rerender events
|
9274
9629
|
},
|
9275
9630
|
|
@@ -10202,6 +10557,9 @@ Calendar.mixin(EmitterMixin);
|
|
10202
10557
|
function Calendar_constructor(element, overrides) {
|
10203
10558
|
var t = this;
|
10204
10559
|
|
10560
|
+
// declare the current calendar instance relies on GlobalEmitter. needed for garbage collection.
|
10561
|
+
GlobalEmitter.needed();
|
10562
|
+
|
10205
10563
|
|
10206
10564
|
// Exports
|
10207
10565
|
// -----------------------------------------------------------------------------------
|
@@ -10545,6 +10903,8 @@ function Calendar_constructor(element, overrides) {
|
|
10545
10903
|
if (windowResizeProxy) {
|
10546
10904
|
$(window).unbind('resize', windowResizeProxy);
|
10547
10905
|
}
|
10906
|
+
|
10907
|
+
GlobalEmitter.unneeded();
|
10548
10908
|
}
|
10549
10909
|
|
10550
10910
|
|
@@ -11176,6 +11536,7 @@ Calendar.defaults = {
|
|
11176
11536
|
|
11177
11537
|
//selectable: false,
|
11178
11538
|
unselectAuto: true,
|
11539
|
+
//selectMinDistance: 0,
|
11179
11540
|
|
11180
11541
|
dropAccept: '*',
|
11181
11542
|
|
@@ -12551,6 +12912,12 @@ function EventManager() { // assumed to be a calendar
|
|
12551
12912
|
}
|
12552
12913
|
|
12553
12914
|
|
12915
|
+
// returns an undo function
|
12916
|
+
Calendar.prototype.mutateSeg = function(seg, newProps) {
|
12917
|
+
return this.mutateEvent(seg.event, newProps);
|
12918
|
+
};
|
12919
|
+
|
12920
|
+
|
12554
12921
|
// hook for external libs to manipulate event properties upon creation.
|
12555
12922
|
// should manipulate the event in-place.
|
12556
12923
|
Calendar.prototype.normalizeEvent = function(event) {
|
@@ -13096,6 +13463,16 @@ var BasicView = FC.BasicView = View.extend({
|
|
13096
13463
|
// forward all hit-related method calls to dayGrid
|
13097
13464
|
|
13098
13465
|
|
13466
|
+
hitsNeeded: function() {
|
13467
|
+
this.dayGrid.hitsNeeded();
|
13468
|
+
},
|
13469
|
+
|
13470
|
+
|
13471
|
+
hitsNotNeeded: function() {
|
13472
|
+
this.dayGrid.hitsNotNeeded();
|
13473
|
+
},
|
13474
|
+
|
13475
|
+
|
13099
13476
|
prepareHits: function() {
|
13100
13477
|
this.dayGrid.prepareHits();
|
13101
13478
|
},
|
@@ -13623,6 +14000,22 @@ var AgendaView = FC.AgendaView = View.extend({
|
|
13623
14000
|
// forward all hit-related method calls to the grids (dayGrid might not be defined)
|
13624
14001
|
|
13625
14002
|
|
14003
|
+
hitsNeeded: function() {
|
14004
|
+
this.timeGrid.hitsNeeded();
|
14005
|
+
if (this.dayGrid) {
|
14006
|
+
this.dayGrid.hitsNeeded();
|
14007
|
+
}
|
14008
|
+
},
|
14009
|
+
|
14010
|
+
|
14011
|
+
hitsNotNeeded: function() {
|
14012
|
+
this.timeGrid.hitsNotNeeded();
|
14013
|
+
if (this.dayGrid) {
|
14014
|
+
this.dayGrid.hitsNotNeeded();
|
14015
|
+
}
|
14016
|
+
},
|
14017
|
+
|
14018
|
+
|
13626
14019
|
prepareHits: function() {
|
13627
14020
|
this.timeGrid.prepareHits();
|
13628
14021
|
if (this.dayGrid) {
|