fullcalendar-rails 3.1.0.0 → 3.2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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) {
|