fullcalendar.io-rails 2.9.1 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (125) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/lib/fullcalendar.io/rails/version.rb +1 -1
  4. data/spec/features/assets_spec.rb +2 -2
  5. data/vendor/assets/javascripts/fullcalendar.js +897 -668
  6. data/vendor/assets/javascripts/fullcalendar/gcal.js +3 -3
  7. data/vendor/assets/javascripts/fullcalendar/locale-all.js +5 -0
  8. data/vendor/assets/javascripts/fullcalendar/locale/ar-ma.js +1 -0
  9. data/vendor/assets/javascripts/fullcalendar/locale/ar-sa.js +1 -0
  10. data/vendor/assets/javascripts/fullcalendar/locale/ar-tn.js +1 -0
  11. data/vendor/assets/javascripts/fullcalendar/locale/ar.js +1 -0
  12. data/vendor/assets/javascripts/fullcalendar/locale/bg.js +1 -0
  13. data/vendor/assets/javascripts/fullcalendar/locale/ca.js +1 -0
  14. data/vendor/assets/javascripts/fullcalendar/locale/cs.js +1 -0
  15. data/vendor/assets/javascripts/fullcalendar/locale/da.js +1 -0
  16. data/vendor/assets/javascripts/fullcalendar/locale/de-at.js +1 -0
  17. data/vendor/assets/javascripts/fullcalendar/locale/de.js +1 -0
  18. data/vendor/assets/javascripts/fullcalendar/locale/el.js +1 -0
  19. data/vendor/assets/javascripts/fullcalendar/locale/en-au.js +1 -0
  20. data/vendor/assets/javascripts/fullcalendar/locale/en-ca.js +1 -0
  21. data/vendor/assets/javascripts/fullcalendar/locale/en-gb.js +1 -0
  22. data/vendor/assets/javascripts/fullcalendar/locale/en-ie.js +1 -0
  23. data/vendor/assets/javascripts/fullcalendar/locale/en-nz.js +1 -0
  24. data/vendor/assets/javascripts/fullcalendar/locale/es-do.js +1 -0
  25. data/vendor/assets/javascripts/fullcalendar/locale/es.js +1 -0
  26. data/vendor/assets/javascripts/fullcalendar/locale/eu.js +1 -0
  27. data/vendor/assets/javascripts/fullcalendar/locale/fa.js +1 -0
  28. data/vendor/assets/javascripts/fullcalendar/locale/fi.js +1 -0
  29. data/vendor/assets/javascripts/fullcalendar/locale/fr-ca.js +1 -0
  30. data/vendor/assets/javascripts/fullcalendar/locale/fr-ch.js +1 -0
  31. data/vendor/assets/javascripts/fullcalendar/locale/fr.js +1 -0
  32. data/vendor/assets/javascripts/fullcalendar/locale/gl.js +1 -0
  33. data/vendor/assets/javascripts/fullcalendar/locale/he.js +1 -0
  34. data/vendor/assets/javascripts/fullcalendar/locale/hi.js +1 -0
  35. data/vendor/assets/javascripts/fullcalendar/locale/hr.js +1 -0
  36. data/vendor/assets/javascripts/fullcalendar/locale/hu.js +1 -0
  37. data/vendor/assets/javascripts/fullcalendar/locale/id.js +1 -0
  38. data/vendor/assets/javascripts/fullcalendar/locale/is.js +1 -0
  39. data/vendor/assets/javascripts/fullcalendar/locale/it.js +1 -0
  40. data/vendor/assets/javascripts/fullcalendar/locale/ja.js +1 -0
  41. data/vendor/assets/javascripts/fullcalendar/locale/ko.js +1 -0
  42. data/vendor/assets/javascripts/fullcalendar/locale/lb.js +1 -0
  43. data/vendor/assets/javascripts/fullcalendar/locale/lt.js +1 -0
  44. data/vendor/assets/javascripts/fullcalendar/locale/lv.js +1 -0
  45. data/vendor/assets/javascripts/fullcalendar/locale/mk.js +1 -0
  46. data/vendor/assets/javascripts/fullcalendar/locale/ms-my.js +1 -0
  47. data/vendor/assets/javascripts/fullcalendar/locale/ms.js +1 -0
  48. data/vendor/assets/javascripts/fullcalendar/locale/nb.js +1 -0
  49. data/vendor/assets/javascripts/fullcalendar/locale/nl.js +1 -0
  50. data/vendor/assets/javascripts/fullcalendar/locale/nn.js +1 -0
  51. data/vendor/assets/javascripts/fullcalendar/locale/pl.js +1 -0
  52. data/vendor/assets/javascripts/fullcalendar/locale/pt-br.js +1 -0
  53. data/vendor/assets/javascripts/fullcalendar/locale/pt.js +1 -0
  54. data/vendor/assets/javascripts/fullcalendar/locale/ro.js +1 -0
  55. data/vendor/assets/javascripts/fullcalendar/locale/ru.js +1 -0
  56. data/vendor/assets/javascripts/fullcalendar/locale/sk.js +1 -0
  57. data/vendor/assets/javascripts/fullcalendar/locale/sl.js +1 -0
  58. data/vendor/assets/javascripts/fullcalendar/locale/sr-cyrl.js +1 -0
  59. data/vendor/assets/javascripts/fullcalendar/locale/sr.js +1 -0
  60. data/vendor/assets/javascripts/fullcalendar/locale/sv.js +1 -0
  61. data/vendor/assets/javascripts/fullcalendar/locale/th.js +1 -0
  62. data/vendor/assets/javascripts/fullcalendar/locale/tr.js +1 -0
  63. data/vendor/assets/javascripts/fullcalendar/locale/uk.js +1 -0
  64. data/vendor/assets/javascripts/fullcalendar/locale/vi.js +1 -0
  65. data/vendor/assets/javascripts/fullcalendar/locale/zh-cn.js +1 -0
  66. data/vendor/assets/javascripts/fullcalendar/locale/zh-tw.js +1 -0
  67. data/vendor/assets/stylesheets/fullcalendar.css +172 -36
  68. data/vendor/assets/stylesheets/fullcalendar.print.css +3 -3
  69. metadata +132 -58
  70. data/vendor/assets/javascripts/fullcalendar/lang-all.js +0 -4
  71. data/vendor/assets/javascripts/fullcalendar/lang/ar-ma.js +0 -1
  72. data/vendor/assets/javascripts/fullcalendar/lang/ar-sa.js +0 -1
  73. data/vendor/assets/javascripts/fullcalendar/lang/ar-tn.js +0 -1
  74. data/vendor/assets/javascripts/fullcalendar/lang/ar.js +0 -1
  75. data/vendor/assets/javascripts/fullcalendar/lang/bg.js +0 -1
  76. data/vendor/assets/javascripts/fullcalendar/lang/ca.js +0 -1
  77. data/vendor/assets/javascripts/fullcalendar/lang/cs.js +0 -1
  78. data/vendor/assets/javascripts/fullcalendar/lang/da.js +0 -1
  79. data/vendor/assets/javascripts/fullcalendar/lang/de-at.js +0 -1
  80. data/vendor/assets/javascripts/fullcalendar/lang/de.js +0 -1
  81. data/vendor/assets/javascripts/fullcalendar/lang/el.js +0 -1
  82. data/vendor/assets/javascripts/fullcalendar/lang/en-au.js +0 -1
  83. data/vendor/assets/javascripts/fullcalendar/lang/en-ca.js +0 -1
  84. data/vendor/assets/javascripts/fullcalendar/lang/en-gb.js +0 -1
  85. data/vendor/assets/javascripts/fullcalendar/lang/en-ie.js +0 -1
  86. data/vendor/assets/javascripts/fullcalendar/lang/en-nz.js +0 -1
  87. data/vendor/assets/javascripts/fullcalendar/lang/es.js +0 -1
  88. data/vendor/assets/javascripts/fullcalendar/lang/eu.js +0 -1
  89. data/vendor/assets/javascripts/fullcalendar/lang/fa.js +0 -1
  90. data/vendor/assets/javascripts/fullcalendar/lang/fi.js +0 -1
  91. data/vendor/assets/javascripts/fullcalendar/lang/fr-ca.js +0 -1
  92. data/vendor/assets/javascripts/fullcalendar/lang/fr-ch.js +0 -1
  93. data/vendor/assets/javascripts/fullcalendar/lang/fr.js +0 -1
  94. data/vendor/assets/javascripts/fullcalendar/lang/gl.js +0 -1
  95. data/vendor/assets/javascripts/fullcalendar/lang/he.js +0 -1
  96. data/vendor/assets/javascripts/fullcalendar/lang/hi.js +0 -1
  97. data/vendor/assets/javascripts/fullcalendar/lang/hr.js +0 -1
  98. data/vendor/assets/javascripts/fullcalendar/lang/hu.js +0 -1
  99. data/vendor/assets/javascripts/fullcalendar/lang/id.js +0 -1
  100. data/vendor/assets/javascripts/fullcalendar/lang/is.js +0 -1
  101. data/vendor/assets/javascripts/fullcalendar/lang/it.js +0 -1
  102. data/vendor/assets/javascripts/fullcalendar/lang/ja.js +0 -1
  103. data/vendor/assets/javascripts/fullcalendar/lang/ko.js +0 -1
  104. data/vendor/assets/javascripts/fullcalendar/lang/lb.js +0 -1
  105. data/vendor/assets/javascripts/fullcalendar/lang/lt.js +0 -1
  106. data/vendor/assets/javascripts/fullcalendar/lang/lv.js +0 -1
  107. data/vendor/assets/javascripts/fullcalendar/lang/nb.js +0 -1
  108. data/vendor/assets/javascripts/fullcalendar/lang/nl.js +0 -1
  109. data/vendor/assets/javascripts/fullcalendar/lang/nn.js +0 -1
  110. data/vendor/assets/javascripts/fullcalendar/lang/pl.js +0 -1
  111. data/vendor/assets/javascripts/fullcalendar/lang/pt-br.js +0 -1
  112. data/vendor/assets/javascripts/fullcalendar/lang/pt.js +0 -1
  113. data/vendor/assets/javascripts/fullcalendar/lang/ro.js +0 -1
  114. data/vendor/assets/javascripts/fullcalendar/lang/ru.js +0 -1
  115. data/vendor/assets/javascripts/fullcalendar/lang/sk.js +0 -1
  116. data/vendor/assets/javascripts/fullcalendar/lang/sl.js +0 -1
  117. data/vendor/assets/javascripts/fullcalendar/lang/sr-cyrl.js +0 -1
  118. data/vendor/assets/javascripts/fullcalendar/lang/sr.js +0 -1
  119. data/vendor/assets/javascripts/fullcalendar/lang/sv.js +0 -1
  120. data/vendor/assets/javascripts/fullcalendar/lang/th.js +0 -1
  121. data/vendor/assets/javascripts/fullcalendar/lang/tr.js +0 -1
  122. data/vendor/assets/javascripts/fullcalendar/lang/uk.js +0 -1
  123. data/vendor/assets/javascripts/fullcalendar/lang/vi.js +0 -1
  124. data/vendor/assets/javascripts/fullcalendar/lang/zh-cn.js +0 -1
  125. data/vendor/assets/javascripts/fullcalendar/lang/zh-tw.js +0 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c0bebf9a03f44e166f1bb2f582fcdefae588048d
4
- data.tar.gz: 51203a4a908a30503e0b96986fbd2fb33e5b6d23
3
+ metadata.gz: 080eb65bbd868198fdad6cba05d63a26cd761794
4
+ data.tar.gz: a78008ae83226e149ba731c61741b0dddf4ff447
5
5
  SHA512:
6
- metadata.gz: cee3cb8ea1fd78a1661ae39649aee060232fc87dd6b09fb671dc287e0a526761f128f2849a4028060e1c457978c94ac79fc9de348f399ad7af899cf242bec72b
7
- data.tar.gz: 6fe447d37fcc65e6f1463982e668b1fb93ecb8bf405024e32d981a8c61582ff9884e19730ba407df51c63256bd9c83f80635a3a8637605d2041c9323d8a7f37d
6
+ metadata.gz: e187a13847636e4a8ab145ecb702adb0688aa6ff71d57cf4bc86040c9bd0ca12c043b98ac8e265743bf3ac94ecf75b734b4bb38fc69c0513577f9dba3f298c1f
7
+ data.tar.gz: 3d9bff653feea5d47b21c5937d534cf8955df67109f76abef3336928534a049b6aeb8b3686a7a62e6a3d4c9d1235a7298510fa71bc9b2da7c8c20fde9a1d2e9c
data/README.md CHANGED
@@ -28,10 +28,10 @@ And in your application.js:
28
28
  //= require moment
29
29
  //= require fullcalendar
30
30
 
31
- Language files, gcal.js and lang-all.js are located under "fullcalendar" subdirectory.
31
+ Language files, gcal.js and locale-all.js are located under "fullcalendar" subdirectory.
32
32
 
33
33
  //= require fullcalendar/gcal
34
- //= require fullcalendar/lang/pl
34
+ //= require fullcalendar/locale/pl
35
35
 
36
36
  ## Usage
37
37
 
@@ -1,5 +1,5 @@
1
1
  module Fullcalendario
2
2
  module Rails
3
- VERSION = "2.9.1"
3
+ VERSION = "3.0.0"
4
4
  end
5
5
  end
@@ -11,8 +11,8 @@ feature 'Assets integration' do
11
11
  expect(page.status_code).to be 200
12
12
  end
13
13
 
14
- it 'provides fullcalendar/lang/pl.js on the asset pipeline' do
15
- visit '/assets/fullcalendar/lang/pl.js'
14
+ it 'provides fullcalendar/locale/pl.js on the asset pipeline' do
15
+ visit '/assets/fullcalendar/locale/pl.js'
16
16
  expect(page.status_code).to be 200
17
17
  end
18
18
 
@@ -1,7 +1,7 @@
1
1
  /*!
2
- * <%= meta.title %> v<%= meta.version %>
3
- * Docs & License: <%= meta.homepage %>
4
- * (c) <%= meta.copyright %>
2
+ * FullCalendar v3.0.0
3
+ * Docs & License: http://fullcalendar.io/
4
+ * (c) 2016 Adam Shaw
5
5
  */
6
6
 
7
7
  (function(factory) {
@@ -19,8 +19,8 @@
19
19
  ;;
20
20
 
21
21
  var FC = $.fullCalendar = {
22
- version: "<%= meta.version %>",
23
- internalApiVersion: 5
22
+ version: "3.0.0",
23
+ internalApiVersion: 6
24
24
  };
25
25
  var fcViews = FC.views = {};
26
26
 
@@ -71,56 +71,6 @@ function mergeOptions(optionObjs) {
71
71
  return mergeProps(optionObjs, complexOptions);
72
72
  }
73
73
 
74
-
75
- // Given options specified for the calendar's constructor, massages any legacy options into a non-legacy form.
76
- // Converts View-Option-Hashes into the View-Specific-Options format.
77
- function massageOverrides(input) {
78
- var overrides = { views: input.views || {} }; // the output. ensure a `views` hash
79
- var subObj;
80
-
81
- // iterate through all option override properties (except `views`)
82
- $.each(input, function(name, val) {
83
- if (name != 'views') {
84
-
85
- // could the value be a legacy View-Option-Hash?
86
- if (
87
- $.isPlainObject(val) &&
88
- !/(time|duration|interval)$/i.test(name) && // exclude duration options. might be given as objects
89
- $.inArray(name, complexOptions) == -1 // complex options aren't allowed to be View-Option-Hashes
90
- ) {
91
- subObj = null;
92
-
93
- // iterate through the properties of this possible View-Option-Hash value
94
- $.each(val, function(subName, subVal) {
95
-
96
- // is the property targeting a view?
97
- if (/^(month|week|day|default|basic(Week|Day)?|agenda(Week|Day)?)$/.test(subName)) {
98
- if (!overrides.views[subName]) { // ensure the view-target entry exists
99
- overrides.views[subName] = {};
100
- }
101
- overrides.views[subName][name] = subVal; // record the value in the `views` object
102
- }
103
- else { // a non-View-Option-Hash property
104
- if (!subObj) {
105
- subObj = {};
106
- }
107
- subObj[subName] = subVal; // accumulate these unrelated values for later
108
- }
109
- });
110
-
111
- if (subObj) { // non-View-Option-Hash properties? transfer them as-is
112
- overrides[name] = subObj;
113
- }
114
- }
115
- else {
116
- overrides[name] = val; // transfer normal options as-is
117
- }
118
- }
119
- });
120
-
121
- return overrides;
122
- }
123
-
124
74
  ;;
125
75
 
126
76
  // exports
@@ -247,7 +197,7 @@ function undistributeHeight(els) {
247
197
  function matchCellWidths(els) {
248
198
  var maxInnerWidth = 0;
249
199
 
250
- els.find('> span').each(function(i, innerEl) {
200
+ els.find('> *').each(function(i, innerEl) {
251
201
  var innerWidth = $(innerEl).outerWidth();
252
202
  if (innerWidth > maxInnerWidth) {
253
203
  maxInnerWidth = innerWidth;
@@ -628,7 +578,8 @@ function flexibleCompare(a, b) {
628
578
  ----------------------------------------------------------------------------------------------------------------------*/
629
579
 
630
580
 
631
- // Computes the intersection of the two ranges. Returns undefined if no intersection.
581
+ // Computes the intersection of the two ranges. Will return fresh date clones in a range.
582
+ // Returns undefined if no intersection.
632
583
  // Expects all dates to be normalized to the same timezone beforehand.
633
584
  // TODO: move to date section?
634
585
  function intersectRanges(subjectRange, constraintRange) {
@@ -908,22 +859,6 @@ function copyOwnProps(src, dest) {
908
859
  }
909
860
 
910
861
 
911
- // Copies over certain methods with the same names as Object.prototype methods. Overcomes an IE<=8 bug:
912
- // https://developer.mozilla.org/en-US/docs/ECMAScript_DontEnum_attribute#JScript_DontEnum_Bug
913
- function copyNativeMethods(src, dest) {
914
- var names = [ 'constructor', 'toString', 'valueOf' ];
915
- var i, name;
916
-
917
- for (i = 0; i < names.length; i++) {
918
- name = names[i];
919
-
920
- if (src[name] !== Object.prototype[name]) {
921
- dest[name] = src[name];
922
- }
923
- }
924
- }
925
-
926
-
927
862
  function hasOwnProp(obj, name) {
928
863
  return hasOwnPropMethod.call(obj, name);
929
864
  }
@@ -989,6 +924,21 @@ function cssToStr(cssProps) {
989
924
  }
990
925
 
991
926
 
927
+ // Given an object hash of HTML attribute names to values,
928
+ // generates a string that can be injected between < > in HTML
929
+ function attrsToStr(attrs) {
930
+ var parts = [];
931
+
932
+ $.each(attrs, function(name, val) {
933
+ if (val != null) {
934
+ parts.push(name + '="' + htmlEscape(val) + '"');
935
+ }
936
+ });
937
+
938
+ return parts.join(' ');
939
+ }
940
+
941
+
992
942
  function capitaliseFirstLetter(str) {
993
943
  return str.charAt(0).toUpperCase() + str.slice(1);
994
944
  }
@@ -1070,14 +1020,24 @@ function syncThen(promise, thenFunc) {
1070
1020
 
1071
1021
  ;;
1072
1022
 
1023
+ /*
1024
+ GENERAL NOTE on moments throughout the *entire rest* of the codebase:
1025
+ All moments are assumed to be ambiguously-zoned unless otherwise noted,
1026
+ with the NOTABLE EXCEOPTION of start/end dates that live on *Event Objects*.
1027
+ Ambiguously-TIMED moments are assumed to be ambiguously-zoned by nature.
1028
+ */
1029
+
1073
1030
  var ambigDateOfMonthRegex = /^\s*\d{4}-\d\d$/;
1074
1031
  var ambigTimeOrZoneRegex =
1075
1032
  /^\s*\d{4}-(?:(\d\d-\d\d)|(W\d\d$)|(W\d\d-\d)|(\d\d\d))((T| )(\d\d(:\d\d(:\d\d(\.\d+)?)?)?)?)?$/;
1076
1033
  var newMomentProto = moment.fn; // where we will attach our new methods
1077
1034
  var oldMomentProto = $.extend({}, newMomentProto); // copy of original moment methods
1078
- var allowValueOptimization;
1079
- var setUTCValues; // function defined below
1080
- var setLocalValues; // function defined below
1035
+
1036
+ // tell momentjs to transfer these properties upon clone
1037
+ var momentProperties = moment.momentProperties;
1038
+ momentProperties.push('_fullCalendar');
1039
+ momentProperties.push('_ambigTime');
1040
+ momentProperties.push('_ambigZone');
1081
1041
 
1082
1042
 
1083
1043
  // Creating
@@ -1123,12 +1083,8 @@ function makeMoment(args, parseAsUTC, parseZone) {
1123
1083
  var ambigMatch;
1124
1084
  var mom;
1125
1085
 
1126
- if (moment.isMoment(input)) {
1127
- mom = moment.apply(null, args); // clone it
1128
- transferAmbigs(input, mom); // the ambig flags weren't transfered with the clone
1129
- }
1130
- else if (isNativeDate(input) || input === undefined) {
1131
- mom = moment.apply(null, args); // will be local
1086
+ if (moment.isMoment(input) || isNativeDate(input) || input === undefined) {
1087
+ mom = moment.apply(null, args);
1132
1088
  }
1133
1089
  else { // "parsing" is required
1134
1090
  isAmbigTime = false;
@@ -1169,12 +1125,7 @@ function makeMoment(args, parseAsUTC, parseZone) {
1169
1125
  mom._ambigZone = true;
1170
1126
  }
1171
1127
  else if (isSingleString) {
1172
- if (mom.utcOffset) {
1173
- mom.utcOffset(input); // if not a valid zone, will assign UTC
1174
- }
1175
- else {
1176
- mom.zone(input); // for moment-pre-2.9
1177
- }
1128
+ mom.utcOffset(input); // if not a valid zone, will assign UTC
1178
1129
  }
1179
1130
  }
1180
1131
  }
@@ -1185,21 +1136,6 @@ function makeMoment(args, parseAsUTC, parseZone) {
1185
1136
  }
1186
1137
 
1187
1138
 
1188
- // A clone method that works with the flags related to our enhanced functionality.
1189
- // In the future, use moment.momentProperties
1190
- newMomentProto.clone = function() {
1191
- var mom = oldMomentProto.clone.apply(this, arguments);
1192
-
1193
- // these flags weren't transfered with the clone
1194
- transferAmbigs(this, mom);
1195
- if (this._fullCalendar) {
1196
- mom._fullCalendar = true;
1197
- }
1198
-
1199
- return mom;
1200
- };
1201
-
1202
-
1203
1139
  // Week Number
1204
1140
  // -------------------------------------------------------------------------------------------------
1205
1141
 
@@ -1207,8 +1143,7 @@ newMomentProto.clone = function() {
1207
1143
  // Returns the week number, considering the locale's custom week number calcuation
1208
1144
  // `weeks` is an alias for `week`
1209
1145
  newMomentProto.week = newMomentProto.weeks = function(input) {
1210
- var weekCalc = (this._locale || this._lang) // works pre-moment-2.8
1211
- ._fullCalendar_weekCalc;
1146
+ var weekCalc = this._locale._fullCalendar_weekCalc;
1212
1147
 
1213
1148
  if (input == null && typeof weekCalc === 'function') { // custom function only works for getter
1214
1149
  return weekCalc(this);
@@ -1275,19 +1210,21 @@ newMomentProto.time = function(time) {
1275
1210
  // but preserving its YMD. A moment with a stripped time will display no time
1276
1211
  // nor timezone offset when .format() is called.
1277
1212
  newMomentProto.stripTime = function() {
1278
- var a;
1279
1213
 
1280
1214
  if (!this._ambigTime) {
1281
1215
 
1282
- // get the values before any conversion happens
1283
- a = this.toArray(); // array of y/m/d/h/m/s/ms
1216
+ this.utc(true); // keepLocalTime=true (for keeping *date* value)
1284
1217
 
1285
- // TODO: use keepLocalTime in the future
1286
- this.utc(); // set the internal UTC flag (will clear the ambig flags)
1287
- setUTCValues(this, a.slice(0, 3)); // set the year/month/date. time will be zero
1218
+ // set time to zero
1219
+ this.set({
1220
+ hours: 0,
1221
+ minutes: 0,
1222
+ seconds: 0,
1223
+ ms: 0
1224
+ });
1288
1225
 
1289
1226
  // Mark the time as ambiguous. This needs to happen after the .utc() call, which might call .utcOffset(),
1290
- // which clears all ambig flags. Same with setUTCValues with moment-timezone.
1227
+ // which clears all ambig flags.
1291
1228
  this._ambigTime = true;
1292
1229
  this._ambigZone = true; // if ambiguous time, also ambiguous timezone offset
1293
1230
  }
@@ -1307,24 +1244,20 @@ newMomentProto.hasTime = function() {
1307
1244
  // Converts the moment to UTC, stripping out its timezone offset, but preserving its
1308
1245
  // YMD and time-of-day. A moment with a stripped timezone offset will display no
1309
1246
  // timezone offset when .format() is called.
1310
- // TODO: look into Moment's keepLocalTime functionality
1311
1247
  newMomentProto.stripZone = function() {
1312
- var a, wasAmbigTime;
1248
+ var wasAmbigTime;
1313
1249
 
1314
1250
  if (!this._ambigZone) {
1315
1251
 
1316
- // get the values before any conversion happens
1317
- a = this.toArray(); // array of y/m/d/h/m/s/ms
1318
1252
  wasAmbigTime = this._ambigTime;
1319
1253
 
1320
- this.utc(); // set the internal UTC flag (might clear the ambig flags, depending on Moment internals)
1321
- setUTCValues(this, a); // will set the year/month/date/hours/minutes/seconds/ms
1254
+ this.utc(true); // keepLocalTime=true (for keeping date and time values)
1322
1255
 
1323
1256
  // the above call to .utc()/.utcOffset() unfortunately might clear the ambig flags, so restore
1324
1257
  this._ambigTime = wasAmbigTime || false;
1325
1258
 
1326
1259
  // Mark the zone as ambiguous. This needs to happen after the .utc() call, which might call .utcOffset(),
1327
- // which clears the ambig flags. Same with setUTCValues with moment-timezone.
1260
+ // which clears the ambig flags.
1328
1261
  this._ambigZone = true;
1329
1262
  }
1330
1263
 
@@ -1337,32 +1270,26 @@ newMomentProto.hasZone = function() {
1337
1270
  };
1338
1271
 
1339
1272
 
1340
- // this method implicitly marks a zone
1341
- newMomentProto.local = function() {
1342
- var a = this.toArray(); // year,month,date,hours,minutes,seconds,ms as an array
1343
- var wasAmbigZone = this._ambigZone;
1273
+ // implicitly marks a zone
1274
+ newMomentProto.local = function(keepLocalTime) {
1344
1275
 
1345
- oldMomentProto.local.apply(this, arguments);
1276
+ // for when converting from ambiguously-zoned to local,
1277
+ // keep the time values when converting from UTC -> local
1278
+ oldMomentProto.local.call(this, this._ambigZone || keepLocalTime);
1346
1279
 
1347
1280
  // ensure non-ambiguous
1348
1281
  // this probably already happened via local() -> utcOffset(), but don't rely on Moment's internals
1349
1282
  this._ambigTime = false;
1350
1283
  this._ambigZone = false;
1351
1284
 
1352
- if (wasAmbigZone) {
1353
- // If the moment was ambiguously zoned, the date fields were stored as UTC.
1354
- // We want to preserve these, but in local time.
1355
- // TODO: look into Moment's keepLocalTime functionality
1356
- setLocalValues(this, a);
1357
- }
1358
-
1359
1285
  return this; // for chaining
1360
1286
  };
1361
1287
 
1362
1288
 
1363
1289
  // implicitly marks a zone
1364
- newMomentProto.utc = function() {
1365
- oldMomentProto.utc.apply(this, arguments);
1290
+ newMomentProto.utc = function(keepLocalTime) {
1291
+
1292
+ oldMomentProto.utc.call(this, keepLocalTime);
1366
1293
 
1367
1294
  // ensure non-ambiguous
1368
1295
  // this probably already happened via utc() -> utcOffset(), but don't rely on Moment's internals
@@ -1373,28 +1300,18 @@ newMomentProto.utc = function() {
1373
1300
  };
1374
1301
 
1375
1302
 
1376
- // methods for arbitrarily manipulating timezone offset.
1377
- // should clear time/zone ambiguity when called.
1378
- $.each([
1379
- 'zone', // only in moment-pre-2.9. deprecated afterwards
1380
- 'utcOffset'
1381
- ], function(i, name) {
1382
- if (oldMomentProto[name]) { // original method exists?
1383
-
1384
- // this method implicitly marks a zone (will probably get called upon .utc() and .local())
1385
- newMomentProto[name] = function(tzo) {
1303
+ // implicitly marks a zone (will probably get called upon .utc() and .local())
1304
+ newMomentProto.utcOffset = function(tzo) {
1386
1305
 
1387
- if (tzo != null) { // setter
1388
- // these assignments needs to happen before the original zone method is called.
1389
- // I forget why, something to do with a browser crash.
1390
- this._ambigTime = false;
1391
- this._ambigZone = false;
1392
- }
1393
-
1394
- return oldMomentProto[name].apply(this, arguments);
1395
- };
1306
+ if (tzo != null) { // setter
1307
+ // these assignments needs to happen before the original zone method is called.
1308
+ // I forget why, something to do with a browser crash.
1309
+ this._ambigTime = false;
1310
+ this._ambigZone = false;
1396
1311
  }
1397
- });
1312
+
1313
+ return oldMomentProto.utcOffset.apply(this, arguments);
1314
+ };
1398
1315
 
1399
1316
 
1400
1317
  // Formatting
@@ -1423,156 +1340,6 @@ newMomentProto.toISOString = function() {
1423
1340
  return oldMomentProto.toISOString.apply(this, arguments);
1424
1341
  };
1425
1342
 
1426
-
1427
- // Querying
1428
- // -------------------------------------------------------------------------------------------------
1429
-
1430
- // Is the moment within the specified range? `end` is exclusive.
1431
- // FYI, this method is not a standard Moment method, so always do our enhanced logic.
1432
- newMomentProto.isWithin = function(start, end) {
1433
- var a = commonlyAmbiguate([ this, start, end ]);
1434
- return a[0] >= a[1] && a[0] < a[2];
1435
- };
1436
-
1437
- // When isSame is called with units, timezone ambiguity is normalized before the comparison happens.
1438
- // If no units specified, the two moments must be identically the same, with matching ambig flags.
1439
- newMomentProto.isSame = function(input, units) {
1440
- var a;
1441
-
1442
- // only do custom logic if this is an enhanced moment
1443
- if (!this._fullCalendar) {
1444
- return oldMomentProto.isSame.apply(this, arguments);
1445
- }
1446
-
1447
- if (units) {
1448
- a = commonlyAmbiguate([ this, input ], true); // normalize timezones but don't erase times
1449
- return oldMomentProto.isSame.call(a[0], a[1], units);
1450
- }
1451
- else {
1452
- input = FC.moment.parseZone(input); // normalize input
1453
- return oldMomentProto.isSame.call(this, input) &&
1454
- Boolean(this._ambigTime) === Boolean(input._ambigTime) &&
1455
- Boolean(this._ambigZone) === Boolean(input._ambigZone);
1456
- }
1457
- };
1458
-
1459
- // Make these query methods work with ambiguous moments
1460
- $.each([
1461
- 'isBefore',
1462
- 'isAfter'
1463
- ], function(i, methodName) {
1464
- newMomentProto[methodName] = function(input, units) {
1465
- var a;
1466
-
1467
- // only do custom logic if this is an enhanced moment
1468
- if (!this._fullCalendar) {
1469
- return oldMomentProto[methodName].apply(this, arguments);
1470
- }
1471
-
1472
- a = commonlyAmbiguate([ this, input ]);
1473
- return oldMomentProto[methodName].call(a[0], a[1], units);
1474
- };
1475
- });
1476
-
1477
-
1478
- // Misc Internals
1479
- // -------------------------------------------------------------------------------------------------
1480
-
1481
- // given an array of moment-like inputs, return a parallel array w/ moments similarly ambiguated.
1482
- // for example, of one moment has ambig time, but not others, all moments will have their time stripped.
1483
- // set `preserveTime` to `true` to keep times, but only normalize zone ambiguity.
1484
- // returns the original moments if no modifications are necessary.
1485
- function commonlyAmbiguate(inputs, preserveTime) {
1486
- var anyAmbigTime = false;
1487
- var anyAmbigZone = false;
1488
- var len = inputs.length;
1489
- var moms = [];
1490
- var i, mom;
1491
-
1492
- // parse inputs into real moments and query their ambig flags
1493
- for (i = 0; i < len; i++) {
1494
- mom = inputs[i];
1495
- if (!moment.isMoment(mom)) {
1496
- mom = FC.moment.parseZone(mom);
1497
- }
1498
- anyAmbigTime = anyAmbigTime || mom._ambigTime;
1499
- anyAmbigZone = anyAmbigZone || mom._ambigZone;
1500
- moms.push(mom);
1501
- }
1502
-
1503
- // strip each moment down to lowest common ambiguity
1504
- // use clones to avoid modifying the original moments
1505
- for (i = 0; i < len; i++) {
1506
- mom = moms[i];
1507
- if (!preserveTime && anyAmbigTime && !mom._ambigTime) {
1508
- moms[i] = mom.clone().stripTime();
1509
- }
1510
- else if (anyAmbigZone && !mom._ambigZone) {
1511
- moms[i] = mom.clone().stripZone();
1512
- }
1513
- }
1514
-
1515
- return moms;
1516
- }
1517
-
1518
- // Transfers all the flags related to ambiguous time/zone from the `src` moment to the `dest` moment
1519
- // TODO: look into moment.momentProperties for this.
1520
- function transferAmbigs(src, dest) {
1521
- if (src._ambigTime) {
1522
- dest._ambigTime = true;
1523
- }
1524
- else if (dest._ambigTime) {
1525
- dest._ambigTime = false;
1526
- }
1527
-
1528
- if (src._ambigZone) {
1529
- dest._ambigZone = true;
1530
- }
1531
- else if (dest._ambigZone) {
1532
- dest._ambigZone = false;
1533
- }
1534
- }
1535
-
1536
-
1537
- // Sets the year/month/date/etc values of the moment from the given array.
1538
- // Inefficient because it calls each individual setter.
1539
- function setMomentValues(mom, a) {
1540
- mom.year(a[0] || 0)
1541
- .month(a[1] || 0)
1542
- .date(a[2] || 0)
1543
- .hours(a[3] || 0)
1544
- .minutes(a[4] || 0)
1545
- .seconds(a[5] || 0)
1546
- .milliseconds(a[6] || 0);
1547
- }
1548
-
1549
- // Can we set the moment's internal date directly?
1550
- allowValueOptimization = '_d' in moment() && 'updateOffset' in moment;
1551
-
1552
- // Utility function. Accepts a moment and an array of the UTC year/month/date/etc values to set.
1553
- // Assumes the given moment is already in UTC mode.
1554
- setUTCValues = allowValueOptimization ? function(mom, a) {
1555
- // simlate what moment's accessors do
1556
- mom._d.setTime(Date.UTC.apply(Date, a));
1557
- moment.updateOffset(mom, false); // keepTime=false
1558
- } : setMomentValues;
1559
-
1560
- // Utility function. Accepts a moment and an array of the local year/month/date/etc values to set.
1561
- // Assumes the given moment is already in local mode.
1562
- setLocalValues = allowValueOptimization ? function(mom, a) {
1563
- // simlate what moment's accessors do
1564
- mom._d.setTime(+new Date( // FYI, there is now way to apply an array of args to a constructor
1565
- a[0] || 0,
1566
- a[1] || 0,
1567
- a[2] || 0,
1568
- a[3] || 0,
1569
- a[4] || 0,
1570
- a[5] || 0,
1571
- a[6] || 0
1572
- ));
1573
- moment.updateOffset(mom, false); // keepTime=false
1574
- } : setMomentValues;
1575
-
1576
1343
  ;;
1577
1344
 
1578
1345
  // Single Date Formatting
@@ -1653,7 +1420,7 @@ function formatRange(date1, date2, formatStr, separator, isRTL) {
1653
1420
  date1 = FC.moment.parseZone(date1);
1654
1421
  date2 = FC.moment.parseZone(date2);
1655
1422
 
1656
- localeData = (date1.localeData || date1.lang).call(date1); // works with moment-pre-2.8
1423
+ localeData = date1.localeData();
1657
1424
 
1658
1425
  // Expand localized format strings, like "LL" -> "MMMM D YYYY"
1659
1426
  formatStr = localeData.longDateFormat(formatStr) || formatStr;
@@ -1858,7 +1625,6 @@ function extendClass(superClass, members) {
1858
1625
 
1859
1626
  // copy each member variable/method onto the the subclass's prototype
1860
1627
  copyOwnProps(members, subClass.prototype);
1861
- copyNativeMethods(members, subClass.prototype); // hack for IE8
1862
1628
 
1863
1629
  // copy over all class variables/methods to the subclass, such as `extend` and `mixin`
1864
1630
  copyOwnProps(superClass, subClass);
@@ -1868,7 +1634,7 @@ function extendClass(superClass, members) {
1868
1634
 
1869
1635
 
1870
1636
  function mixIntoClass(theClass, members) {
1871
- copyOwnProps(members, theClass.prototype); // TODO: copyNativeMethods?
1637
+ copyOwnProps(members, theClass.prototype);
1872
1638
  }
1873
1639
  ;;
1874
1640
 
@@ -2446,10 +2212,7 @@ var CoordCache = FC.CoordCache = Class.extend({
2446
2212
  var DragListener = FC.DragListener = Class.extend(ListenerMixin, MouseIgnorerMixin, {
2447
2213
 
2448
2214
  options: null,
2449
-
2450
- // for IE8 bug-fighting behavior
2451
2215
  subjectEl: null,
2452
- subjectHref: null,
2453
2216
 
2454
2217
  // coordinates of the initial mousedown
2455
2218
  originX: null,
@@ -2640,7 +2403,6 @@ var DragListener = FC.DragListener = Class.extend(ListenerMixin, MouseIgnorerMix
2640
2403
 
2641
2404
  handleDragStart: function(ev) {
2642
2405
  this.trigger('dragStart', ev);
2643
- this.initHrefHack();
2644
2406
  },
2645
2407
 
2646
2408
 
@@ -2680,7 +2442,6 @@ var DragListener = FC.DragListener = Class.extend(ListenerMixin, MouseIgnorerMix
2680
2442
 
2681
2443
  handleDragEnd: function(ev) {
2682
2444
  this.trigger('dragEnd', ev);
2683
- this.destroyHrefHack();
2684
2445
  },
2685
2446
 
2686
2447
 
@@ -2756,33 +2517,6 @@ var DragListener = FC.DragListener = Class.extend(ListenerMixin, MouseIgnorerMix
2756
2517
  },
2757
2518
 
2758
2519
 
2759
- // <A> HREF Hack
2760
- // -----------------------------------------------------------------------------------------------------------------
2761
-
2762
-
2763
- initHrefHack: function() {
2764
- var subjectEl = this.subjectEl;
2765
-
2766
- // remove a mousedown'd <a>'s href so it is not visited (IE8 bug)
2767
- if ((this.subjectHref = subjectEl ? subjectEl.attr('href') : null)) {
2768
- subjectEl.removeAttr('href');
2769
- }
2770
- },
2771
-
2772
-
2773
- destroyHrefHack: function() {
2774
- var subjectEl = this.subjectEl;
2775
- var subjectHref = this.subjectHref;
2776
-
2777
- // restore a mousedown'd <a>'s href (for IE8 bug)
2778
- setTimeout(function() { // must be outside of the click's execution
2779
- if (subjectHref) {
2780
- subjectEl.attr('href', subjectHref);
2781
- }
2782
- }, 0);
2783
- },
2784
-
2785
-
2786
2520
  // Utils
2787
2521
  // -----------------------------------------------------------------------------------------------------------------
2788
2522
 
@@ -3306,7 +3040,6 @@ var MouseFollower = Class.extend(ListenerMixin, {
3306
3040
  var el = this.el;
3307
3041
 
3308
3042
  if (!el) {
3309
- this.sourceEl.width(); // hack to force IE8 to compute correct bounding box
3310
3043
  el = this.el = this.sourceEl.clone()
3311
3044
  .addClass(this.options.additionalClass || '')
3312
3045
  .css({
@@ -3351,7 +3084,6 @@ var MouseFollower = Class.extend(ListenerMixin, {
3351
3084
 
3352
3085
  // make sure origin info was computed
3353
3086
  if (this.top0 === null) {
3354
- this.sourceEl.width(); // hack to force IE8 to compute correct bounding box
3355
3087
  sourceOffset = this.sourceEl.offset();
3356
3088
  origin = this.el.offsetParent().offset();
3357
3089
  this.top0 = sourceOffset.top - origin.top;
@@ -3405,6 +3137,9 @@ var MouseFollower = Class.extend(ListenerMixin, {
3405
3137
 
3406
3138
  var Grid = FC.Grid = Class.extend(ListenerMixin, MouseIgnorerMixin, {
3407
3139
 
3140
+ // self-config, overridable by subclasses
3141
+ hasDayInteractions: true, // can user click/select ranges of time?
3142
+
3408
3143
  view: null, // a View object
3409
3144
  isRTL: null, // shortcut to the view's isRTL option
3410
3145
 
@@ -3572,10 +3307,13 @@ var Grid = FC.Grid = Class.extend(ListenerMixin, MouseIgnorerMixin, {
3572
3307
  // Does other DOM-related initializations.
3573
3308
  setElement: function(el) {
3574
3309
  this.el = el;
3575
- preventSelection(el);
3576
3310
 
3577
- this.bindDayHandler('touchstart', this.dayTouchStart);
3578
- this.bindDayHandler('mousedown', this.dayMousedown);
3311
+ if (this.hasDayInteractions) {
3312
+ preventSelection(el);
3313
+
3314
+ this.bindDayHandler('touchstart', this.dayTouchStart);
3315
+ this.bindDayHandler('mousedown', this.dayMousedown);
3316
+ }
3579
3317
 
3580
3318
  // attach event-element-related handlers. in Grid.events
3581
3319
  // same garbage collection note as above.
@@ -3592,7 +3330,12 @@ var Grid = FC.Grid = Class.extend(ListenerMixin, MouseIgnorerMixin, {
3592
3330
  // jQuery will take care of unregistering them when removeElement gets called.
3593
3331
  this.el.on(name, function(ev) {
3594
3332
  if (
3595
- !$(ev.target).is('.fc-event-container *, .fc-more') // not an an event element, or "more.." link
3333
+ !$(ev.target).is(
3334
+ _this.segSelector + ',' + // directly on an event element
3335
+ _this.segSelector + ' *,' + // within an event element
3336
+ '.fc-more,' + // a "more.." link
3337
+ 'a[data-goto]' // a clickable nav link
3338
+ )
3596
3339
  ) {
3597
3340
  return handler.call(_this, ev);
3598
3341
  }
@@ -4044,6 +3787,9 @@ var Grid = FC.Grid = Class.extend(ListenerMixin, MouseIgnorerMixin, {
4044
3787
 
4045
3788
  Grid.mixin({
4046
3789
 
3790
+ // self-config, overridable by subclasses
3791
+ segSelector: '.fc-event-container > *', // what constitutes an event element?
3792
+
4047
3793
  mousedOverSeg: null, // the segment object the user's mouse is over. null if over nothing
4048
3794
  isDraggingSeg: false, // is a segment being dragged? boolean
4049
3795
  isResizingSeg: false, // is a segment being resized? boolean
@@ -4265,7 +4011,7 @@ Grid.mixin({
4265
4011
  bindSegHandlerToEl: function(el, name, handler) {
4266
4012
  var _this = this;
4267
4013
 
4268
- el.on(name, '.fc-event-container > *', function(ev) {
4014
+ el.on(name, this.segSelector, function(ev) {
4269
4015
  var seg = $(this).data('fc-seg'); // grab segment data. put there by View::renderEvents
4270
4016
 
4271
4017
  // only call the handlers if there is not a drag/resize in progress
@@ -4277,7 +4023,10 @@ Grid.mixin({
4277
4023
 
4278
4024
 
4279
4025
  handleSegClick: function(seg, ev) {
4280
- return this.view.trigger('eventClick', seg.el[0], seg.event, ev); // can return `false` to cancel
4026
+ var res = this.view.trigger('eventClick', seg.el[0], seg.event, ev); // can return `false` to cancel
4027
+ if (res === false) {
4028
+ ev.preventDefault();
4029
+ }
4281
4030
  },
4282
4031
 
4283
4032
 
@@ -4288,7 +4037,9 @@ Grid.mixin({
4288
4037
  !this.mousedOverSeg
4289
4038
  ) {
4290
4039
  this.mousedOverSeg = seg;
4291
- seg.el.addClass('fc-allow-mouse-resize');
4040
+ if (this.view.isEventResizable(seg.event)) {
4041
+ seg.el.addClass('fc-allow-mouse-resize');
4042
+ }
4292
4043
  this.view.trigger('eventMouseover', seg.el[0], seg.event, ev);
4293
4044
  }
4294
4045
  },
@@ -4302,7 +4053,9 @@ Grid.mixin({
4302
4053
  if (this.mousedOverSeg) {
4303
4054
  seg = seg || this.mousedOverSeg; // if given no args, use the currently moused-over segment
4304
4055
  this.mousedOverSeg = null;
4305
- seg.el.removeClass('fc-allow-mouse-resize');
4056
+ if (this.view.isEventResizable(seg.event)) {
4057
+ seg.el.removeClass('fc-allow-mouse-resize');
4058
+ }
4306
4059
  this.view.trigger('eventMouseout', seg.el[0], seg.event, ev);
4307
4060
  }
4308
4061
  },
@@ -4557,11 +4310,7 @@ Grid.mixin({
4557
4310
  }
4558
4311
  // othewise, work off existing values
4559
4312
  else {
4560
- dropLocation = {
4561
- start: event.start.clone(),
4562
- end: event.end ? event.end.clone() : null,
4563
- allDay: event.allDay // keep it the same
4564
- };
4313
+ dropLocation = pluckEventDateProps(event);
4565
4314
  }
4566
4315
 
4567
4316
  dropLocation.start.add(delta);
@@ -4587,11 +4336,7 @@ Grid.mixin({
4587
4336
  var opacity = this.view.opt('dragOpacity');
4588
4337
 
4589
4338
  if (opacity != null) {
4590
- els.each(function(i, node) {
4591
- // Don't use jQuery (will set an IE filter), do it the old fashioned way.
4592
- // In IE8, a helper element will disappears if there's a filter.
4593
- node.style.opacity = opacity;
4594
- });
4339
+ els.css('opacity', opacity);
4595
4340
  }
4596
4341
  },
4597
4342
 
@@ -4757,8 +4502,11 @@ Grid.mixin({
4757
4502
  disableCursor();
4758
4503
  resizeLocation = null;
4759
4504
  }
4760
- // no change? (TODO: how does this work with timezones?)
4761
- else if (resizeLocation.start.isSame(event.start) && resizeLocation.end.isSame(eventEnd)) {
4505
+ // no change? (FYI, event dates might have zones)
4506
+ else if (
4507
+ resizeLocation.start.isSame(event.start.clone().stripZone()) &&
4508
+ resizeLocation.end.isSame(eventEnd.clone().stripZone())
4509
+ ) {
4762
4510
  resizeLocation = null;
4763
4511
  }
4764
4512
  }
@@ -4911,15 +4659,11 @@ Grid.mixin({
4911
4659
  // Generic utility for generating the HTML classNames for an event segment's element
4912
4660
  getSegClasses: function(seg, isDraggable, isResizable) {
4913
4661
  var view = this.view;
4914
- var event = seg.event;
4915
4662
  var classes = [
4916
4663
  'fc-event',
4917
4664
  seg.isStart ? 'fc-start' : 'fc-not-start',
4918
4665
  seg.isEnd ? 'fc-end' : 'fc-not-end'
4919
- ].concat(
4920
- event.className,
4921
- event.source ? event.source.className : []
4922
- );
4666
+ ].concat(this.getSegCustomClasses(seg));
4923
4667
 
4924
4668
  if (isDraggable) {
4925
4669
  classes.push('fc-draggable');
@@ -4929,7 +4673,7 @@ Grid.mixin({
4929
4673
  }
4930
4674
 
4931
4675
  // event is currently selected? attach a className.
4932
- if (view.isEventSelected(event)) {
4676
+ if (view.isEventSelected(seg.event)) {
4933
4677
  classes.push('fc-selected');
4934
4678
  }
4935
4679
 
@@ -4937,38 +4681,78 @@ Grid.mixin({
4937
4681
  },
4938
4682
 
4939
4683
 
4940
- // Utility for generating event skin-related CSS properties
4941
- getSegSkinCss: function(seg) {
4684
+ // List of classes that were defined by the caller of the API in some way
4685
+ getSegCustomClasses: function(seg) {
4942
4686
  var event = seg.event;
4943
- var view = this.view;
4944
- var source = event.source || {};
4945
- var eventColor = event.color;
4946
- var sourceColor = source.color;
4947
- var optionColor = view.opt('eventColor');
4948
4687
 
4688
+ return [].concat(
4689
+ event.className, // guaranteed to be an array
4690
+ event.source ? event.source.className : []
4691
+ );
4692
+ },
4693
+
4694
+
4695
+ // Utility for generating event skin-related CSS properties
4696
+ getSegSkinCss: function(seg) {
4949
4697
  return {
4950
- 'background-color':
4951
- event.backgroundColor ||
4952
- eventColor ||
4953
- source.backgroundColor ||
4954
- sourceColor ||
4955
- view.opt('eventBackgroundColor') ||
4956
- optionColor,
4957
- 'border-color':
4958
- event.borderColor ||
4959
- eventColor ||
4960
- source.borderColor ||
4961
- sourceColor ||
4962
- view.opt('eventBorderColor') ||
4963
- optionColor,
4964
- color:
4965
- event.textColor ||
4966
- source.textColor ||
4967
- view.opt('eventTextColor')
4698
+ 'background-color': this.getSegBackgroundColor(seg),
4699
+ 'border-color': this.getSegBorderColor(seg),
4700
+ color: this.getSegTextColor(seg)
4968
4701
  };
4969
4702
  },
4970
4703
 
4971
4704
 
4705
+ // Queries for caller-specified color, then falls back to default
4706
+ getSegBackgroundColor: function(seg) {
4707
+ return seg.event.backgroundColor ||
4708
+ seg.event.color ||
4709
+ this.getSegDefaultBackgroundColor(seg);
4710
+ },
4711
+
4712
+
4713
+ getSegDefaultBackgroundColor: function(seg) {
4714
+ var source = seg.event.source || {};
4715
+
4716
+ return source.backgroundColor ||
4717
+ source.color ||
4718
+ this.view.opt('eventBackgroundColor') ||
4719
+ this.view.opt('eventColor');
4720
+ },
4721
+
4722
+
4723
+ // Queries for caller-specified color, then falls back to default
4724
+ getSegBorderColor: function(seg) {
4725
+ return seg.event.borderColor ||
4726
+ seg.event.color ||
4727
+ this.getSegDefaultBorderColor(seg);
4728
+ },
4729
+
4730
+
4731
+ getSegDefaultBorderColor: function(seg) {
4732
+ var source = seg.event.source || {};
4733
+
4734
+ return source.borderColor ||
4735
+ source.color ||
4736
+ this.view.opt('eventBorderColor') ||
4737
+ this.view.opt('eventColor');
4738
+ },
4739
+
4740
+
4741
+ // Queries for caller-specified color, then falls back to default
4742
+ getSegTextColor: function(seg) {
4743
+ return seg.event.textColor ||
4744
+ this.getSegDefaultTextColor(seg);
4745
+ },
4746
+
4747
+
4748
+ getSegDefaultTextColor: function(seg) {
4749
+ var source = seg.event.source || {};
4750
+
4751
+ return source.textColor ||
4752
+ this.view.opt('eventTextColor');
4753
+ },
4754
+
4755
+
4972
4756
  /* Converting events -> eventRange -> eventSpan -> eventSegs
4973
4757
  ------------------------------------------------------------------------------------------------------------------*/
4974
4758
 
@@ -5036,20 +4820,25 @@ Grid.mixin({
5036
4820
  // Generates the unzoned start/end dates an event appears to occupy
5037
4821
  // Can accept an event "location" as well (which only has start/end and no allDay)
5038
4822
  eventToRange: function(event) {
5039
- return {
5040
- start: event.start.clone().stripZone(),
5041
- end: (
4823
+ var calendar = this.view.calendar;
4824
+ var start = event.start.clone().stripZone();
4825
+ var end = (
5042
4826
  event.end ?
5043
4827
  event.end.clone() :
5044
4828
  // derive the end from the start and allDay. compute allDay if necessary
5045
- this.view.calendar.getDefaultEventEnd(
4829
+ calendar.getDefaultEventEnd(
5046
4830
  event.allDay != null ?
5047
4831
  event.allDay :
5048
4832
  !event.start.hasTime(),
5049
4833
  event.start
5050
4834
  )
5051
- ).stripZone()
5052
- };
4835
+ ).stripZone();
4836
+
4837
+ // hack: dynamic locale change forgets to upate stored event localed
4838
+ calendar.localizeMoment(start);
4839
+ calendar.localizeMoment(end);
4840
+
4841
+ return { start: start, end: end };
5053
4842
  },
5054
4843
 
5055
4844
 
@@ -5152,6 +4941,16 @@ Grid.mixin({
5152
4941
  ----------------------------------------------------------------------------------------------------------------------*/
5153
4942
 
5154
4943
 
4944
+ function pluckEventDateProps(event) {
4945
+ return {
4946
+ start: event.start.clone(),
4947
+ end: event.end ? event.end.clone() : null,
4948
+ allDay: event.allDay // keep it the same
4949
+ };
4950
+ }
4951
+ FC.pluckEventDateProps = pluckEventDateProps;
4952
+
4953
+
5155
4954
  function isBgEvent(event) { // returns true if background OR inverse-background
5156
4955
  var rendering = getEventRendering(event);
5157
4956
  return rendering === 'background' || rendering === 'inverse-background';
@@ -5542,7 +5341,7 @@ var DayTableMixin = FC.DayTableMixin = {
5542
5341
 
5543
5342
  return '' +
5544
5343
  '<th class="fc-day-header ' + view.widgetHeaderClass + ' fc-' + dayIDs[date.day()] + '"' +
5545
- (this.rowCnt == 1 ?
5344
+ (this.rowCnt === 1 ?
5546
5345
  ' data-date="' + date.format('YYYY-MM-DD') + '"' :
5547
5346
  '') +
5548
5347
  (colspan > 1 ?
@@ -5551,8 +5350,12 @@ var DayTableMixin = FC.DayTableMixin = {
5551
5350
  (otherAttrs ?
5552
5351
  ' ' + otherAttrs :
5553
5352
  '') +
5554
- '>' +
5555
- htmlEscape(date.format(this.colHeadFormat)) +
5353
+ '>' +
5354
+ // don't make a link if the heading could represent multiple days, or if there's only one day (forceOff)
5355
+ view.buildGotoAnchorHtml(
5356
+ { date: date, forceOff: this.rowCnt > 1 || this.colCnt === 1 },
5357
+ htmlEscape(date.format(this.colHeadFormat)) // inner HTML
5358
+ ) +
5556
5359
  '</th>';
5557
5360
  },
5558
5361
 
@@ -5781,19 +5584,53 @@ var DayGrid = FC.DayGrid = Grid.extend(DayTableMixin, {
5781
5584
  // Generates the HTML for the <td>s of the "number" row in the DayGrid's content skeleton.
5782
5585
  // The number row will only exist if either day numbers or week numbers are turned on.
5783
5586
  renderNumberCellHtml: function(date) {
5587
+ var html = '';
5784
5588
  var classes;
5589
+ var weekCalcFirstDoW;
5785
5590
 
5786
- if (!this.view.dayNumbersVisible) { // if there are week numbers but not day numbers
5591
+ if (!this.view.dayNumbersVisible && !this.view.cellWeekNumbersVisible) {
5592
+ // no numbers in day cell (week number must be along the side)
5787
5593
  return '<td/>'; // will create an empty space above events :(
5788
5594
  }
5789
5595
 
5790
5596
  classes = this.getDayClasses(date);
5791
- classes.unshift('fc-day-number');
5597
+ classes.unshift('fc-day-top');
5792
5598
 
5793
- return '' +
5794
- '<td class="' + classes.join(' ') + '" data-date="' + date.format() + '">' +
5795
- date.date() +
5796
- '</td>';
5599
+ if (this.view.cellWeekNumbersVisible) {
5600
+ // To determine the day of week number change under ISO, we cannot
5601
+ // rely on moment.js methods such as firstDayOfWeek() or weekday(),
5602
+ // because they rely on the locale's dow (possibly overridden by
5603
+ // our firstDay option), which may not be Monday. We cannot change
5604
+ // dow, because that would affect the calendar start day as well.
5605
+ if (date._locale._fullCalendar_weekCalc === 'ISO') {
5606
+ weekCalcFirstDoW = 1; // Monday by ISO 8601 definition
5607
+ }
5608
+ else {
5609
+ weekCalcFirstDoW = date._locale.firstDayOfWeek();
5610
+ }
5611
+ }
5612
+
5613
+ html += '<td class="' + classes.join(' ') + '" data-date="' + date.format() + '">';
5614
+
5615
+ if (this.view.cellWeekNumbersVisible && (date.day() == weekCalcFirstDoW)) {
5616
+ html += this.view.buildGotoAnchorHtml(
5617
+ { date: date, type: 'week' },
5618
+ { 'class': 'fc-week-number' },
5619
+ date.format('w') // inner HTML
5620
+ );
5621
+ }
5622
+
5623
+ if (this.view.dayNumbersVisible) {
5624
+ html += this.view.buildGotoAnchorHtml(
5625
+ date,
5626
+ { 'class': 'fc-day-number' },
5627
+ date.date() // inner HTML
5628
+ );
5629
+ }
5630
+
5631
+ html += '</td>';
5632
+
5633
+ return html;
5797
5634
  },
5798
5635
 
5799
5636
 
@@ -6901,7 +6738,6 @@ var TimeGrid = FC.TimeGrid = Grid.extend(DayTableMixin, {
6901
6738
 
6902
6739
  this.labelFormat =
6903
6740
  input ||
6904
- view.opt('axisFormat') || // deprecated
6905
6741
  view.opt('smallTimeFormat'); // the computed default
6906
6742
 
6907
6743
  input = view.opt('slotLabelInterval');
@@ -8133,6 +7969,62 @@ var View = FC.View = Class.extend(EmitterMixin, ListenerMixin, {
8133
7969
  },
8134
7970
 
8135
7971
 
7972
+ getAllDayHtml: function() {
7973
+ return this.opt('allDayHtml') || htmlEscape(this.opt('allDayText'));
7974
+ },
7975
+
7976
+
7977
+ /* Navigation
7978
+ ------------------------------------------------------------------------------------------------------------------*/
7979
+
7980
+
7981
+ // Generates HTML for an anchor to another view into the calendar.
7982
+ // Will either generate an <a> tag or a non-clickable <span> tag, depending on enabled settings.
7983
+ // `gotoOptions` can either be a moment input, or an object with the form:
7984
+ // { date, type, forceOff }
7985
+ // `type` is a view-type like "day" or "week". default value is "day".
7986
+ // `attrs` and `innerHtml` are use to generate the rest of the HTML tag.
7987
+ buildGotoAnchorHtml: function(gotoOptions, attrs, innerHtml) {
7988
+ var date, type, forceOff;
7989
+ var finalOptions;
7990
+
7991
+ if ($.isPlainObject(gotoOptions)) {
7992
+ date = gotoOptions.date;
7993
+ type = gotoOptions.type;
7994
+ forceOff = gotoOptions.forceOff;
7995
+ }
7996
+ else {
7997
+ date = gotoOptions; // a single moment input
7998
+ }
7999
+ date = FC.moment(date); // if a string, parse it
8000
+
8001
+ finalOptions = { // for serialization into the link
8002
+ date: date.format('YYYY-MM-DD'),
8003
+ type: type || 'day'
8004
+ };
8005
+
8006
+ if (typeof attrs === 'string') {
8007
+ innerHtml = attrs;
8008
+ attrs = null;
8009
+ }
8010
+
8011
+ attrs = attrs ? ' ' + attrsToStr(attrs) : ''; // will have a leading space
8012
+ innerHtml = innerHtml || '';
8013
+
8014
+ if (!forceOff && this.opt('navLinks')) {
8015
+ return '<a' + attrs +
8016
+ ' data-goto="' + htmlEscape(JSON.stringify(finalOptions)) + '">' +
8017
+ innerHtml +
8018
+ '</a>';
8019
+ }
8020
+ else {
8021
+ return '<span' + attrs + '>' +
8022
+ innerHtml +
8023
+ '</span>';
8024
+ }
8025
+ },
8026
+
8027
+
8136
8028
  /* Rendering
8137
8029
  ------------------------------------------------------------------------------------------------------------------*/
8138
8030
 
@@ -8628,14 +8520,24 @@ var View = FC.View = Class.extend(EmitterMixin, ListenerMixin, {
8628
8520
 
8629
8521
  // Computes if the given event is allowed to be dragged by the user
8630
8522
  isEventDraggable: function(event) {
8631
- var source = event.source || {};
8523
+ return this.isEventStartEditable(event);
8524
+ },
8525
+
8632
8526
 
8527
+ isEventStartEditable: function(event) {
8633
8528
  return firstDefined(
8634
8529
  event.startEditable,
8635
- source.startEditable,
8530
+ (event.source || {}).startEditable,
8636
8531
  this.opt('eventStartEditable'),
8532
+ this.isEventGenerallyEditable(event)
8533
+ );
8534
+ },
8535
+
8536
+
8537
+ isEventGenerallyEditable: function(event) {
8538
+ return firstDefined(
8637
8539
  event.editable,
8638
- source.editable,
8540
+ (event.source || {}).editable,
8639
8541
  this.opt('editable')
8640
8542
  );
8641
8543
  },
@@ -9135,7 +9037,7 @@ var Scroller = FC.Scroller = Class.extend({
9135
9037
  var Calendar = FC.Calendar = Class.extend({
9136
9038
 
9137
9039
  dirDefaults: null, // option defaults related to LTR or RTL
9138
- langDefaults: null, // option defaults related to current locale
9040
+ localeDefaults: null, // option defaults related to current locale
9139
9041
  overrides: null, // option overrides given to the fullCalendar constructor
9140
9042
  dynamicOverrides: null, // options set with dynamic setter method. higher precedence than view overrides.
9141
9043
  options: null, // all defaults combined with overrides
@@ -9158,33 +9060,33 @@ var Calendar = FC.Calendar = Class.extend({
9158
9060
  // Computes the flattened options hash for the calendar and assigns to `this.options`.
9159
9061
  // Assumes this.overrides and this.dynamicOverrides have already been initialized.
9160
9062
  populateOptionsHash: function() {
9161
- var lang, langDefaults;
9063
+ var locale, localeDefaults;
9162
9064
  var isRTL, dirDefaults;
9163
9065
 
9164
- lang = firstDefined( // explicit lang option given?
9165
- this.dynamicOverrides.lang,
9166
- this.overrides.lang
9066
+ locale = firstDefined( // explicit locale option given?
9067
+ this.dynamicOverrides.locale,
9068
+ this.overrides.locale
9167
9069
  );
9168
- langDefaults = langOptionHash[lang];
9169
- if (!langDefaults) { // explicit lang option not given or invalid?
9170
- lang = Calendar.defaults.lang;
9171
- langDefaults = langOptionHash[lang] || {};
9070
+ localeDefaults = localeOptionHash[locale];
9071
+ if (!localeDefaults) { // explicit locale option not given or invalid?
9072
+ locale = Calendar.defaults.locale;
9073
+ localeDefaults = localeOptionHash[locale] || {};
9172
9074
  }
9173
9075
 
9174
9076
  isRTL = firstDefined( // based on options computed so far, is direction RTL?
9175
9077
  this.dynamicOverrides.isRTL,
9176
9078
  this.overrides.isRTL,
9177
- langDefaults.isRTL,
9079
+ localeDefaults.isRTL,
9178
9080
  Calendar.defaults.isRTL
9179
9081
  );
9180
9082
  dirDefaults = isRTL ? Calendar.rtlDefaults : {};
9181
9083
 
9182
9084
  this.dirDefaults = dirDefaults;
9183
- this.langDefaults = langDefaults;
9085
+ this.localeDefaults = localeDefaults;
9184
9086
  this.options = mergeOptions([ // merge defaults and overrides. lowest to highest precedence
9185
9087
  Calendar.defaults, // global defaults
9186
9088
  dirDefaults,
9187
- langDefaults,
9089
+ localeDefaults,
9188
9090
  this.overrides,
9189
9091
  this.dynamicOverrides
9190
9092
  ]);
@@ -9300,7 +9202,7 @@ var Calendar = FC.Calendar = Class.extend({
9300
9202
  Calendar.defaults, // global defaults
9301
9203
  spec.defaults, // view's defaults (from ViewSubclass.defaults)
9302
9204
  this.dirDefaults,
9303
- this.langDefaults, // locale and dir take precedence over view's defaults!
9205
+ this.localeDefaults, // locale and dir take precedence over view's defaults!
9304
9206
  this.overrides, // calendar's overrides (options given to constructor)
9305
9207
  spec.overrides, // view's overrides (view-specific options)
9306
9208
  this.dynamicOverrides // dynamically set via setter. highest precedence
@@ -9317,6 +9219,9 @@ var Calendar = FC.Calendar = Class.extend({
9317
9219
  function queryButtonText(options) {
9318
9220
  var buttonText = options.buttonText || {};
9319
9221
  return buttonText[requestedViewType] ||
9222
+ // view can decide to look up a certain key
9223
+ (spec.buttonTextKey ? buttonText[spec.buttonTextKey] : null) ||
9224
+ // a key like "month"
9320
9225
  (spec.singleUnit ? buttonText[spec.singleUnit] : null);
9321
9226
  }
9322
9227
 
@@ -9328,7 +9233,7 @@ var Calendar = FC.Calendar = Class.extend({
9328
9233
 
9329
9234
  // highest to lowest priority. mirrors buildViewSpecOptions
9330
9235
  spec.buttonTextDefault =
9331
- queryButtonText(this.langDefaults) ||
9236
+ queryButtonText(this.localeDefaults) ||
9332
9237
  queryButtonText(this.dirDefaults) ||
9333
9238
  spec.defaults.buttonText || // a single string. from ViewSubclass.defaults
9334
9239
  queryButtonText(Calendar.defaults) ||
@@ -9429,29 +9334,31 @@ function Calendar_constructor(element, overrides) {
9429
9334
  t.dynamicOverrides = {};
9430
9335
  t.viewSpecCache = {};
9431
9336
  t.optionHandlers = {}; // for Calendar.options.js
9432
-
9433
- // convert legacy options into non-legacy ones.
9434
- // in the future, when this is removed, don't use `overrides` reference. make a copy.
9435
- t.overrides = massageOverrides(overrides || {});
9337
+ t.overrides = $.extend({}, overrides); // make a copy
9436
9338
 
9437
9339
  t.populateOptionsHash(); // sets this.options
9438
9340
 
9439
9341
 
9440
9342
 
9441
- // Language-data Internals
9343
+ // Locale-data Internals
9442
9344
  // -----------------------------------------------------------------------------------
9443
- // Apply overrides to the current language's data
9345
+ // Apply overrides to the current locale's data
9444
9346
 
9445
9347
  var localeData;
9446
9348
 
9447
9349
  // Called immediately, and when any of the options change.
9448
9350
  // Happens before any internal objects rebuild or rerender, because this is very core.
9449
9351
  t.bindOptions([
9450
- 'lang', 'monthNames', 'monthNamesShort', 'dayNames', 'dayNamesShort', 'firstDay', 'weekNumberCalculation'
9451
- ], function(lang, monthNames, monthNamesShort, dayNames, dayNamesShort, firstDay, weekNumberCalculation) {
9352
+ 'locale', 'monthNames', 'monthNamesShort', 'dayNames', 'dayNamesShort', 'firstDay', 'weekNumberCalculation'
9353
+ ], function(locale, monthNames, monthNamesShort, dayNames, dayNamesShort, firstDay, weekNumberCalculation) {
9354
+
9355
+ // normalize
9356
+ if (weekNumberCalculation === 'iso') {
9357
+ weekNumberCalculation = 'ISO'; // normalize
9358
+ }
9452
9359
 
9453
9360
  localeData = createObject( // make a cheap copy
9454
- getMomentLocaleData(lang) // will fall back to en
9361
+ getMomentLocaleData(locale) // will fall back to en
9455
9362
  );
9456
9363
 
9457
9364
  if (monthNames) {
@@ -9466,15 +9373,16 @@ function Calendar_constructor(element, overrides) {
9466
9373
  if (dayNamesShort) {
9467
9374
  localeData._weekdaysShort = dayNamesShort;
9468
9375
  }
9376
+
9377
+ if (firstDay == null && weekNumberCalculation === 'ISO') {
9378
+ firstDay = 1;
9379
+ }
9469
9380
  if (firstDay != null) {
9470
9381
  var _week = createObject(localeData._week); // _week: { dow: # }
9471
9382
  _week.dow = firstDay;
9472
9383
  localeData._week = _week;
9473
9384
  }
9474
9385
 
9475
- if (weekNumberCalculation === 'iso') {
9476
- weekNumberCalculation = 'ISO'; // normalize
9477
- }
9478
9386
  if ( // whitelist certain kinds of input
9479
9387
  weekNumberCalculation === 'ISO' ||
9480
9388
  weekNumberCalculation === 'local' ||
@@ -9491,7 +9399,6 @@ function Calendar_constructor(element, overrides) {
9491
9399
  });
9492
9400
 
9493
9401
 
9494
-
9495
9402
  // Calendar-specific Date Utilities
9496
9403
  // -----------------------------------------------------------------------------------
9497
9404
 
@@ -9500,7 +9407,7 @@ function Calendar_constructor(element, overrides) {
9500
9407
  t.defaultTimedEventDuration = moment.duration(t.options.defaultTimedEventDuration);
9501
9408
 
9502
9409
 
9503
- // Builds a moment using the settings of the current calendar: timezone and language.
9410
+ // Builds a moment using the settings of the current calendar: timezone and locale.
9504
9411
  // Accepts anything the vanilla moment() constructor accepts.
9505
9412
  t.moment = function() {
9506
9413
  var mom;
@@ -9528,13 +9435,9 @@ function Calendar_constructor(element, overrides) {
9528
9435
 
9529
9436
  // Updates the given moment's locale settings to the current calendar locale settings.
9530
9437
  function localizeMoment(mom) {
9531
- if ('_locale' in mom) { // moment 2.8 and above
9532
- mom._locale = localeData;
9533
- }
9534
- else { // pre-moment-2.8
9535
- mom._lang = localeData;
9536
- }
9438
+ mom._locale = localeData;
9537
9439
  }
9440
+ t.localizeMoment = localizeMoment;
9538
9441
 
9539
9442
 
9540
9443
  // Returns a boolean about whether or not the calendar knows how to calculate
@@ -9611,8 +9514,7 @@ function Calendar_constructor(element, overrides) {
9611
9514
  // Produces a human-readable string for the given duration.
9612
9515
  // Side-effect: changes the locale of the given duration.
9613
9516
  t.humanizeDuration = function(duration) {
9614
- return (duration.locale || duration.lang).call(duration, t.options.lang) // works moment-pre-2.8
9615
- .humanize();
9517
+ return duration.locale(t.options.locale).humanize();
9616
9518
  };
9617
9519
 
9618
9520
 
@@ -9674,16 +9576,37 @@ function Calendar_constructor(element, overrides) {
9674
9576
  function initialRender() {
9675
9577
  element.addClass('fc');
9676
9578
 
9677
- // called immediately, and upon option change
9678
- t.bindOption('theme', function(theme) {
9679
- tm = theme ? 'ui' : 'fc'; // affects a larger scope
9680
- element.toggleClass('ui-widget', theme);
9681
- element.toggleClass('fc-unthemed', !theme);
9579
+ // event delegation for nav links
9580
+ element.on('click.fc', 'a[data-goto]', function(ev) {
9581
+ var anchorEl = $(this);
9582
+ var gotoOptions = anchorEl.data('goto'); // will automatically parse JSON
9583
+ var date = t.moment(gotoOptions.date);
9584
+ var viewType = gotoOptions.type;
9585
+
9586
+ // property like "navLinkDayClick". might be a string or a function
9587
+ var customAction = currentView.opt('navLink' + capitaliseFirstLetter(viewType) + 'Click');
9588
+
9589
+ if (typeof customAction === 'function') {
9590
+ customAction(date, ev);
9591
+ }
9592
+ else {
9593
+ if (typeof customAction === 'string') {
9594
+ viewType = customAction;
9595
+ }
9596
+ zoomTo(date, viewType);
9597
+ }
9598
+ });
9599
+
9600
+ // called immediately, and upon option change
9601
+ t.bindOption('theme', function(theme) {
9602
+ tm = theme ? 'ui' : 'fc'; // affects a larger scope
9603
+ element.toggleClass('ui-widget', theme);
9604
+ element.toggleClass('fc-unthemed', !theme);
9682
9605
  });
9683
9606
 
9684
9607
  // called immediately, and upon option change.
9685
- // HACK: lang often affects isRTL, so we explicitly listen to that too.
9686
- t.bindOptions([ 'isRTL', 'lang' ], function(isRTL) {
9608
+ // HACK: locale often affects isRTL, so we explicitly listen to that too.
9609
+ t.bindOptions([ 'isRTL', 'locale' ], function(isRTL) {
9687
9610
  element.toggleClass('fc-ltr', !isRTL);
9688
9611
  element.toggleClass('fc-rtl', isRTL);
9689
9612
  });
@@ -9724,6 +9647,8 @@ function Calendar_constructor(element, overrides) {
9724
9647
  content.remove();
9725
9648
  element.removeClass('fc fc-ltr fc-rtl fc-unthemed ui-widget');
9726
9649
 
9650
+ element.off('.fc'); // unbind nav link handlers
9651
+
9727
9652
  if (windowResizeProxy) {
9728
9653
  $(window).unbind('resize', windowResizeProxy);
9729
9654
  }
@@ -9772,7 +9697,10 @@ function Calendar_constructor(element, overrides) {
9772
9697
  // render or rerender the view
9773
9698
  if (
9774
9699
  !currentView.displaying ||
9775
- !date.isWithin(currentView.intervalStart, currentView.intervalEnd) // implicit date window change
9700
+ !( // NOT within interval range signals an implicit date window change
9701
+ date >= currentView.intervalStart &&
9702
+ date < currentView.intervalEnd
9703
+ )
9776
9704
  ) {
9777
9705
  if (elementVisible()) {
9778
9706
 
@@ -9971,7 +9899,8 @@ function Calendar_constructor(element, overrides) {
9971
9899
 
9972
9900
  function updateTodayButton() {
9973
9901
  var now = t.getNow();
9974
- if (now.isWithin(currentView.intervalStart, currentView.intervalEnd)) {
9902
+
9903
+ if (now >= currentView.intervalStart && now < currentView.intervalEnd) {
9975
9904
  header.disableButton('today');
9976
9905
  }
9977
9906
  else {
@@ -10258,7 +10187,7 @@ Calendar.mixin({
10258
10187
  Calendar.defaults = {
10259
10188
 
10260
10189
  titleRangeSeparator: ' \u2013 ', // en dash
10261
- monthYearFormat: 'MMMM YYYY', // required for en. other languages rely on datepicker computable option
10190
+ monthYearFormat: 'MMMM YYYY', // required for en. other locales rely on datepicker computable option
10262
10191
 
10263
10192
  defaultTimedEventDuration: '02:00:00',
10264
10193
  defaultAllDayEventDuration: { days: 1 },
@@ -10315,6 +10244,8 @@ Calendar.defaults = {
10315
10244
  prevYear: 'left-double-arrow',
10316
10245
  nextYear: 'right-double-arrow'
10317
10246
  },
10247
+
10248
+ allDayText: 'all-day',
10318
10249
 
10319
10250
  // jquery-ui theming
10320
10251
  theme: false,
@@ -10350,7 +10281,7 @@ Calendar.defaults = {
10350
10281
  };
10351
10282
 
10352
10283
 
10353
- Calendar.englishDefaults = { // used by lang.js
10284
+ Calendar.englishDefaults = { // used by locale.js
10354
10285
  dayPopoverFormat: 'dddd, MMMM D'
10355
10286
  };
10356
10287
 
@@ -10377,19 +10308,18 @@ Calendar.rtlDefaults = { // right-to-left defaults
10377
10308
 
10378
10309
  ;;
10379
10310
 
10380
- var langOptionHash = FC.langs = {}; // initialize and expose
10311
+ var localeOptionHash = FC.locales = {}; // initialize and expose
10381
10312
 
10382
10313
 
10383
- // TODO: document the structure and ordering of a FullCalendar lang file
10384
- // TODO: rename everything "lang" to "locale", like what the moment project did
10314
+ // TODO: document the structure and ordering of a FullCalendar locale file
10385
10315
 
10386
10316
 
10387
10317
  // Initialize jQuery UI datepicker translations while using some of the translations
10388
- // Will set this as the default language for datepicker.
10389
- FC.datepickerLang = function(langCode, dpLangCode, dpOptions) {
10318
+ // Will set this as the default locales for datepicker.
10319
+ FC.datepickerLocale = function(localeCode, dpLocaleCode, dpOptions) {
10390
10320
 
10391
- // get the FullCalendar internal option hash for this language. create if necessary
10392
- var fcOptions = langOptionHash[langCode] || (langOptionHash[langCode] = {});
10321
+ // get the FullCalendar internal option hash for this locale. create if necessary
10322
+ var fcOptions = localeOptionHash[localeCode] || (localeOptionHash[localeCode] = {});
10393
10323
 
10394
10324
  // transfer some simple options from datepicker to fc
10395
10325
  fcOptions.isRTL = dpOptions.isRTL;
@@ -10403,15 +10333,15 @@ FC.datepickerLang = function(langCode, dpLangCode, dpOptions) {
10403
10333
  // is jQuery UI Datepicker is on the page?
10404
10334
  if ($.datepicker) {
10405
10335
 
10406
- // Register the language data.
10407
- // FullCalendar and MomentJS use language codes like "pt-br" but Datepicker
10408
- // does it like "pt-BR" or if it doesn't have the language, maybe just "pt".
10409
- // Make an alias so the language can be referenced either way.
10410
- $.datepicker.regional[dpLangCode] =
10411
- $.datepicker.regional[langCode] = // alias
10336
+ // Register the locale data.
10337
+ // FullCalendar and MomentJS use locale codes like "pt-br" but Datepicker
10338
+ // does it like "pt-BR" or if it doesn't have the locale, maybe just "pt".
10339
+ // Make an alias so the locale can be referenced either way.
10340
+ $.datepicker.regional[dpLocaleCode] =
10341
+ $.datepicker.regional[localeCode] = // alias
10412
10342
  dpOptions;
10413
10343
 
10414
- // Alias 'en' to the default language data. Do this every time.
10344
+ // Alias 'en' to the default locale data. Do this every time.
10415
10345
  $.datepicker.regional.en = $.datepicker.regional[''];
10416
10346
 
10417
10347
  // Set as Datepicker's global defaults.
@@ -10420,35 +10350,35 @@ FC.datepickerLang = function(langCode, dpLangCode, dpOptions) {
10420
10350
  };
10421
10351
 
10422
10352
 
10423
- // Sets FullCalendar-specific translations. Will set the language as the global default.
10424
- FC.lang = function(langCode, newFcOptions) {
10353
+ // Sets FullCalendar-specific translations. Will set the locales as the global default.
10354
+ FC.locale = function(localeCode, newFcOptions) {
10425
10355
  var fcOptions;
10426
10356
  var momOptions;
10427
10357
 
10428
- // get the FullCalendar internal option hash for this language. create if necessary
10429
- fcOptions = langOptionHash[langCode] || (langOptionHash[langCode] = {});
10358
+ // get the FullCalendar internal option hash for this locale. create if necessary
10359
+ fcOptions = localeOptionHash[localeCode] || (localeOptionHash[localeCode] = {});
10430
10360
 
10431
- // provided new options for this language? merge them in
10361
+ // provided new options for this locales? merge them in
10432
10362
  if (newFcOptions) {
10433
- fcOptions = langOptionHash[langCode] = mergeOptions([ fcOptions, newFcOptions ]);
10363
+ fcOptions = localeOptionHash[localeCode] = mergeOptions([ fcOptions, newFcOptions ]);
10434
10364
  }
10435
10365
 
10436
- // compute language options that weren't defined.
10366
+ // compute locale options that weren't defined.
10437
10367
  // always do this. newFcOptions can be undefined when initializing from i18n file,
10438
10368
  // so no way to tell if this is an initialization or a default-setting.
10439
- momOptions = getMomentLocaleData(langCode); // will fall back to en
10369
+ momOptions = getMomentLocaleData(localeCode); // will fall back to en
10440
10370
  $.each(momComputableOptions, function(name, func) {
10441
10371
  if (fcOptions[name] == null) {
10442
10372
  fcOptions[name] = func(momOptions, fcOptions);
10443
10373
  }
10444
10374
  });
10445
10375
 
10446
- // set it as the default language for FullCalendar
10447
- Calendar.defaults.lang = langCode;
10376
+ // set it as the default locale for FullCalendar
10377
+ Calendar.defaults.locale = localeCode;
10448
10378
  };
10449
10379
 
10450
10380
 
10451
- // NOTE: can't guarantee any of these computations will run because not every language has datepicker
10381
+ // NOTE: can't guarantee any of these computations will run because not every locale has datepicker
10452
10382
  // configs, so make sure there are English fallbacks for these in the defaults file.
10453
10383
  var dpComputableOptions = {
10454
10384
 
@@ -10498,7 +10428,7 @@ var momComputableOptions = {
10498
10428
  smallTimeFormat: function(momOptions) {
10499
10429
  return momOptions.longDateFormat('LT')
10500
10430
  .replace(':mm', '(:mm)')
10501
- .replace(/(\Wmm)$/, '($1)') // like above, but for foreign langs
10431
+ .replace(/(\Wmm)$/, '($1)') // like above, but for foreign locales
10502
10432
  .replace(/\s*a$/i, 'a'); // convert AM/PM/am/pm to lowercase. remove any spaces beforehand
10503
10433
  },
10504
10434
 
@@ -10506,7 +10436,7 @@ var momComputableOptions = {
10506
10436
  extraSmallTimeFormat: function(momOptions) {
10507
10437
  return momOptions.longDateFormat('LT')
10508
10438
  .replace(':mm', '(:mm)')
10509
- .replace(/(\Wmm)$/, '($1)') // like above, but for foreign langs
10439
+ .replace(/(\Wmm)$/, '($1)') // like above, but for foreign locales
10510
10440
  .replace(/\s*a$/i, 't'); // convert to AM/PM/am/pm to lowercase one-letter. remove any spaces beforehand
10511
10441
  },
10512
10442
 
@@ -10514,7 +10444,7 @@ var momComputableOptions = {
10514
10444
  hourFormat: function(momOptions) {
10515
10445
  return momOptions.longDateFormat('LT')
10516
10446
  .replace(':mm', '')
10517
- .replace(/(\Wmm)$/, '') // like above, but for foreign langs
10447
+ .replace(/(\Wmm)$/, '') // like above, but for foreign locales
10518
10448
  .replace(/\s*a$/i, 'a'); // convert AM/PM/am/pm to lowercase. remove any spaces beforehand
10519
10449
  },
10520
10450
 
@@ -10528,7 +10458,7 @@ var momComputableOptions = {
10528
10458
 
10529
10459
 
10530
10460
  // options that should be computed off live calendar options (considers override options)
10531
- // TODO: best place for this? related to lang?
10461
+ // TODO: best place for this? related to locale?
10532
10462
  // TODO: flipping text based on isRTL is a bad idea because the CSS `direction` might want to handle it
10533
10463
  var instanceComputableOptions = {
10534
10464
 
@@ -10565,17 +10495,14 @@ function populateInstanceComputableOptions(options) {
10565
10495
 
10566
10496
 
10567
10497
  // Returns moment's internal locale data. If doesn't exist, returns English.
10568
- // Works with moment-pre-2.8
10569
- function getMomentLocaleData(langCode) {
10570
- var func = moment.localeData || moment.langData;
10571
- return func.call(moment, langCode) ||
10572
- func.call(moment, 'en'); // the newer localData could return null, so fall back to en
10498
+ function getMomentLocaleData(localeCode) {
10499
+ return moment.localeData(localeCode) || moment.localeData('en');
10573
10500
  }
10574
10501
 
10575
10502
 
10576
10503
  // Initialize English by forcing computation of moment-derived options.
10577
10504
  // Also, sets it as the default.
10578
- FC.lang('en', Calendar.englishDefaults);
10505
+ FC.locale('en', Calendar.englishDefaults);
10579
10506
 
10580
10507
  ;;
10581
10508
 
@@ -11884,78 +11811,134 @@ function EventManager() { // assumed to be a calendar
11884
11811
  }
11885
11812
 
11886
11813
 
11887
- /* Overlapping / Constraining
11888
- -----------------------------------------------------------------------------------------*/
11814
+ t.getEventCache = function() {
11815
+ return cache;
11816
+ };
11889
11817
 
11890
- t.isEventSpanAllowed = isEventSpanAllowed;
11891
- t.isExternalSpanAllowed = isExternalSpanAllowed;
11892
- t.isSelectionSpanAllowed = isSelectionSpanAllowed;
11818
+ }
11893
11819
 
11894
11820
 
11895
- // Determines if the given event can be relocated to the given span (unzoned start/end with other misc data)
11896
- function isEventSpanAllowed(span, event) {
11897
- var source = event.source || {};
11898
- var constraint = firstDefined(
11899
- event.constraint,
11900
- source.constraint,
11901
- t.options.eventConstraint
11902
- );
11903
- var overlap = firstDefined(
11904
- event.overlap,
11905
- source.overlap,
11906
- t.options.eventOverlap
11907
- );
11908
- return isSpanAllowed(span, constraint, overlap, event);
11909
- }
11821
+ // hook for external libs to manipulate event properties upon creation.
11822
+ // should manipulate the event in-place.
11823
+ Calendar.prototype.normalizeEvent = function(event) {
11824
+ };
11910
11825
 
11911
11826
 
11912
- // Determines if an external event can be relocated to the given span (unzoned start/end with other misc data)
11913
- function isExternalSpanAllowed(eventSpan, eventLocation, eventProps) {
11914
- var eventInput;
11915
- var event;
11827
+ // Does the given span (start, end, and other location information)
11828
+ // fully contain the other?
11829
+ Calendar.prototype.spanContainsSpan = function(outerSpan, innerSpan) {
11830
+ var eventStart = outerSpan.start.clone().stripZone();
11831
+ var eventEnd = this.getEventEnd(outerSpan).stripZone();
11916
11832
 
11917
- // note: very similar logic is in View's reportExternalDrop
11918
- if (eventProps) {
11919
- eventInput = $.extend({}, eventProps, eventLocation);
11920
- event = expandEvent(buildEventFromInput(eventInput))[0];
11921
- }
11833
+ return innerSpan.start >= eventStart && innerSpan.end <= eventEnd;
11834
+ };
11922
11835
 
11923
- if (event) {
11924
- return isEventSpanAllowed(eventSpan, event);
11925
- }
11926
- else { // treat it as a selection
11927
11836
 
11928
- return isSelectionSpanAllowed(eventSpan);
11837
+ // Returns a list of events that the given event should be compared against when being considered for a move to
11838
+ // the specified span. Attached to the Calendar's prototype because EventManager is a mixin for a Calendar.
11839
+ Calendar.prototype.getPeerEvents = function(span, event) {
11840
+ var cache = this.getEventCache();
11841
+ var peerEvents = [];
11842
+ var i, otherEvent;
11843
+
11844
+ for (i = 0; i < cache.length; i++) {
11845
+ otherEvent = cache[i];
11846
+ if (
11847
+ !event ||
11848
+ event._id !== otherEvent._id // don't compare the event to itself or other related [repeating] events
11849
+ ) {
11850
+ peerEvents.push(otherEvent);
11929
11851
  }
11930
11852
  }
11931
11853
 
11854
+ return peerEvents;
11855
+ };
11856
+
11857
+
11858
+ // updates the "backup" properties, which are preserved in order to compute diffs later on.
11859
+ function backupEventDates(event) {
11860
+ event._allDay = event.allDay;
11861
+ event._start = event.start.clone();
11862
+ event._end = event.end ? event.end.clone() : null;
11863
+ }
11864
+
11865
+
11866
+ /* Overlapping / Constraining
11867
+ -----------------------------------------------------------------------------------------*/
11868
+
11869
+
11870
+ // Determines if the given event can be relocated to the given span (unzoned start/end with other misc data)
11871
+ Calendar.prototype.isEventSpanAllowed = function(span, event) {
11872
+ var source = event.source || {};
11932
11873
 
11933
- // Determines the given span (unzoned start/end with other misc data) can be selected.
11934
- function isSelectionSpanAllowed(span) {
11935
- return isSpanAllowed(span, t.options.selectConstraint, t.options.selectOverlap);
11874
+ var constraint = firstDefined(
11875
+ event.constraint,
11876
+ source.constraint,
11877
+ this.options.eventConstraint
11878
+ );
11879
+
11880
+ var overlap = firstDefined(
11881
+ event.overlap,
11882
+ source.overlap,
11883
+ this.options.eventOverlap
11884
+ );
11885
+
11886
+ return this.isSpanAllowed(span, constraint, overlap, event) &&
11887
+ (!this.options.eventAllow || this.options.eventAllow(span, event) !== false);
11888
+ };
11889
+
11890
+
11891
+ // Determines if an external event can be relocated to the given span (unzoned start/end with other misc data)
11892
+ Calendar.prototype.isExternalSpanAllowed = function(eventSpan, eventLocation, eventProps) {
11893
+ var eventInput;
11894
+ var event;
11895
+
11896
+ // note: very similar logic is in View's reportExternalDrop
11897
+ if (eventProps) {
11898
+ eventInput = $.extend({}, eventProps, eventLocation);
11899
+ event = this.expandEvent(
11900
+ this.buildEventFromInput(eventInput)
11901
+ )[0];
11936
11902
  }
11937
11903
 
11904
+ if (event) {
11905
+ return this.isEventSpanAllowed(eventSpan, event);
11906
+ }
11907
+ else { // treat it as a selection
11938
11908
 
11939
- // Returns true if the given span (caused by an event drop/resize or a selection) is allowed to exist
11940
- // according to the constraint/overlap settings.
11941
- // `event` is not required if checking a selection.
11942
- function isSpanAllowed(span, constraint, overlap, event) {
11943
- var constraintEvents;
11944
- var anyContainment;
11945
- var peerEvents;
11946
- var i, peerEvent;
11947
- var peerOverlap;
11909
+ return this.isSelectionSpanAllowed(eventSpan);
11910
+ }
11911
+ };
11912
+
11913
+
11914
+ // Determines the given span (unzoned start/end with other misc data) can be selected.
11915
+ Calendar.prototype.isSelectionSpanAllowed = function(span) {
11916
+ return this.isSpanAllowed(span, this.options.selectConstraint, this.options.selectOverlap) &&
11917
+ (!this.options.selectAllow || this.options.selectAllow(span) !== false);
11918
+ };
11948
11919
 
11949
- // the range must be fully contained by at least one of produced constraint events
11950
- if (constraint != null) {
11951
11920
 
11952
- // not treated as an event! intermediate data structure
11953
- // TODO: use ranges in the future
11954
- constraintEvents = constraintToEvents(constraint);
11921
+ // Returns true if the given span (caused by an event drop/resize or a selection) is allowed to exist
11922
+ // according to the constraint/overlap settings.
11923
+ // `event` is not required if checking a selection.
11924
+ Calendar.prototype.isSpanAllowed = function(span, constraint, overlap, event) {
11925
+ var constraintEvents;
11926
+ var anyContainment;
11927
+ var peerEvents;
11928
+ var i, peerEvent;
11929
+ var peerOverlap;
11930
+
11931
+ // the range must be fully contained by at least one of produced constraint events
11932
+ if (constraint != null) {
11933
+
11934
+ // not treated as an event! intermediate data structure
11935
+ // TODO: use ranges in the future
11936
+ constraintEvents = this.constraintToEvents(constraint);
11937
+ if (constraintEvents) { // not invalid
11955
11938
 
11956
11939
  anyContainment = false;
11957
11940
  for (i = 0; i < constraintEvents.length; i++) {
11958
- if (t.spanContainsSpan(constraintEvents[i], span)) {
11941
+ if (this.spanContainsSpan(constraintEvents[i], span)) {
11959
11942
  anyContainment = true;
11960
11943
  break;
11961
11944
  }
@@ -11965,127 +11948,81 @@ function EventManager() { // assumed to be a calendar
11965
11948
  return false;
11966
11949
  }
11967
11950
  }
11951
+ }
11968
11952
 
11969
- peerEvents = t.getPeerEvents(span, event);
11953
+ peerEvents = this.getPeerEvents(span, event);
11970
11954
 
11971
- for (i = 0; i < peerEvents.length; i++) {
11972
- peerEvent = peerEvents[i];
11955
+ for (i = 0; i < peerEvents.length; i++) {
11956
+ peerEvent = peerEvents[i];
11973
11957
 
11974
- // there needs to be an actual intersection before disallowing anything
11975
- if (eventIntersectsRange(peerEvent, span)) {
11958
+ // there needs to be an actual intersection before disallowing anything
11959
+ if (this.eventIntersectsRange(peerEvent, span)) {
11960
+
11961
+ // evaluate overlap for the given range and short-circuit if necessary
11962
+ if (overlap === false) {
11963
+ return false;
11964
+ }
11965
+ // if the event's overlap is a test function, pass the peer event in question as the first param
11966
+ else if (typeof overlap === 'function' && !overlap(peerEvent, event)) {
11967
+ return false;
11968
+ }
11976
11969
 
11977
- // evaluate overlap for the given range and short-circuit if necessary
11978
- if (overlap === false) {
11970
+ // if we are computing if the given range is allowable for an event, consider the other event's
11971
+ // EventObject-specific or Source-specific `overlap` property
11972
+ if (event) {
11973
+ peerOverlap = firstDefined(
11974
+ peerEvent.overlap,
11975
+ (peerEvent.source || {}).overlap
11976
+ // we already considered the global `eventOverlap`
11977
+ );
11978
+ if (peerOverlap === false) {
11979
11979
  return false;
11980
11980
  }
11981
- // if the event's overlap is a test function, pass the peer event in question as the first param
11982
- else if (typeof overlap === 'function' && !overlap(peerEvent, event)) {
11981
+ // if the peer event's overlap is a test function, pass the subject event as the first param
11982
+ if (typeof peerOverlap === 'function' && !peerOverlap(event, peerEvent)) {
11983
11983
  return false;
11984
11984
  }
11985
-
11986
- // if we are computing if the given range is allowable for an event, consider the other event's
11987
- // EventObject-specific or Source-specific `overlap` property
11988
- if (event) {
11989
- peerOverlap = firstDefined(
11990
- peerEvent.overlap,
11991
- (peerEvent.source || {}).overlap
11992
- // we already considered the global `eventOverlap`
11993
- );
11994
- if (peerOverlap === false) {
11995
- return false;
11996
- }
11997
- // if the peer event's overlap is a test function, pass the subject event as the first param
11998
- if (typeof peerOverlap === 'function' && !peerOverlap(event, peerEvent)) {
11999
- return false;
12000
- }
12001
- }
12002
11985
  }
12003
11986
  }
12004
-
12005
- return true;
12006
11987
  }
12007
11988
 
11989
+ return true;
11990
+ };
12008
11991
 
12009
- // Given an event input from the API, produces an array of event objects. Possible event inputs:
12010
- // 'businessHours'
12011
- // An event ID (number or string)
12012
- // An object with specific start/end dates or a recurring event (like what businessHours accepts)
12013
- function constraintToEvents(constraintInput) {
12014
-
12015
- if (constraintInput === 'businessHours') {
12016
- return t.getCurrentBusinessHourEvents();
12017
- }
12018
11992
 
12019
- if (typeof constraintInput === 'object') {
12020
- return expandEvent(buildEventFromInput(constraintInput));
12021
- }
11993
+ // Given an event input from the API, produces an array of event objects. Possible event inputs:
11994
+ // 'businessHours'
11995
+ // An event ID (number or string)
11996
+ // An object with specific start/end dates or a recurring event (like what businessHours accepts)
11997
+ Calendar.prototype.constraintToEvents = function(constraintInput) {
12022
11998
 
12023
- return clientEvents(constraintInput); // probably an ID
11999
+ if (constraintInput === 'businessHours') {
12000
+ return this.getCurrentBusinessHourEvents();
12024
12001
  }
12025
12002
 
12026
-
12027
- // Does the event's date range intersect with the given range?
12028
- // start/end already assumed to have stripped zones :(
12029
- function eventIntersectsRange(event, range) {
12030
- var eventStart = event.start.clone().stripZone();
12031
- var eventEnd = t.getEventEnd(event).stripZone();
12032
-
12033
- return range.start < eventEnd && range.end > eventStart;
12003
+ if (typeof constraintInput === 'object') {
12004
+ if (constraintInput.start != null) { // needs to be event-like input
12005
+ return this.expandEvent(this.buildEventFromInput(constraintInput));
12006
+ }
12007
+ else {
12008
+ return null; // invalid
12009
+ }
12034
12010
  }
12035
12011
 
12036
-
12037
- t.getEventCache = function() {
12038
- return cache;
12039
- };
12040
-
12041
- }
12042
-
12043
-
12044
- // hook for external libs to manipulate event properties upon creation.
12045
- // should manipulate the event in-place.
12046
- Calendar.prototype.normalizeEvent = function(event) {
12047
- };
12048
-
12049
-
12050
- // Does the given span (start, end, and other location information)
12051
- // fully contain the other?
12052
- Calendar.prototype.spanContainsSpan = function(outerSpan, innerSpan) {
12053
- var eventStart = outerSpan.start.clone().stripZone();
12054
- var eventEnd = this.getEventEnd(outerSpan).stripZone();
12055
-
12056
- return innerSpan.start >= eventStart && innerSpan.end <= eventEnd;
12012
+ return this.clientEvents(constraintInput); // probably an ID
12057
12013
  };
12058
12014
 
12059
12015
 
12060
- // Returns a list of events that the given event should be compared against when being considered for a move to
12061
- // the specified span. Attached to the Calendar's prototype because EventManager is a mixin for a Calendar.
12062
- Calendar.prototype.getPeerEvents = function(span, event) {
12063
- var cache = this.getEventCache();
12064
- var peerEvents = [];
12065
- var i, otherEvent;
12066
-
12067
- for (i = 0; i < cache.length; i++) {
12068
- otherEvent = cache[i];
12069
- if (
12070
- !event ||
12071
- event._id !== otherEvent._id // don't compare the event to itself or other related [repeating] events
12072
- ) {
12073
- peerEvents.push(otherEvent);
12074
- }
12075
- }
12016
+ // Does the event's date range intersect with the given range?
12017
+ // start/end already assumed to have stripped zones :(
12018
+ Calendar.prototype.eventIntersectsRange = function(event, range) {
12019
+ var eventStart = event.start.clone().stripZone();
12020
+ var eventEnd = this.getEventEnd(event).stripZone();
12076
12021
 
12077
- return peerEvents;
12022
+ return range.start < eventEnd && range.end > eventStart;
12078
12023
  };
12079
12024
 
12080
12025
 
12081
- // updates the "backup" properties, which are preserved in order to compute diffs later on.
12082
- function backupEventDates(event) {
12083
- event._allDay = event.allDay;
12084
- event._start = event.start.clone();
12085
- event._end = event.end ? event.end.clone() : null;
12086
- }
12087
-
12088
-
12089
12026
  /* Business Hours
12090
12027
  -----------------------------------------------------------------------------------------*/
12091
12028
 
@@ -12170,7 +12107,8 @@ var BasicView = FC.BasicView = View.extend({
12170
12107
  dayGrid: null, // the main subcomponent that does most of the heavy lifting
12171
12108
 
12172
12109
  dayNumbersVisible: false, // display day numbers on each day cell?
12173
- weekNumbersVisible: false, // display week numbers along the side?
12110
+ colWeekNumbersVisible: false, // display week numbers along the side?
12111
+ cellWeekNumbersVisible: false, // display week numbers in day cell?
12174
12112
 
12175
12113
  weekNumberWidth: null, // width of all the week-number cells running down the side
12176
12114
 
@@ -12231,8 +12169,18 @@ var BasicView = FC.BasicView = View.extend({
12231
12169
  renderDates: function() {
12232
12170
 
12233
12171
  this.dayNumbersVisible = this.dayGrid.rowCnt > 1; // TODO: make grid responsible
12234
- this.weekNumbersVisible = this.opt('weekNumbers');
12235
- this.dayGrid.numbersVisible = this.dayNumbersVisible || this.weekNumbersVisible;
12172
+ if (this.opt('weekNumbers')) {
12173
+ if (this.opt('weekNumbersWithinDays')) {
12174
+ this.cellWeekNumbersVisible = true;
12175
+ this.colWeekNumbersVisible = false;
12176
+ }
12177
+ else {
12178
+ this.cellWeekNumbersVisible = false;
12179
+ this.colWeekNumbersVisible = true;
12180
+ };
12181
+ }
12182
+ this.dayGrid.numbersVisible = this.dayNumbersVisible ||
12183
+ this.cellWeekNumbersVisible || this.colWeekNumbersVisible;
12236
12184
 
12237
12185
  this.el.addClass('fc-basic-view').html(this.renderSkeletonHtml());
12238
12186
  this.renderHead();
@@ -12316,7 +12264,7 @@ var BasicView = FC.BasicView = View.extend({
12316
12264
 
12317
12265
  // Refreshes the horizontal dimensions of the view
12318
12266
  updateWidth: function() {
12319
- if (this.weekNumbersVisible) {
12267
+ if (this.colWeekNumbersVisible) {
12320
12268
  // Make sure all week number cells running down the side have the same width.
12321
12269
  // Record the width for cells created later.
12322
12270
  this.weekNumberWidth = matchCellWidths(
@@ -12457,9 +12405,8 @@ var BasicView = FC.BasicView = View.extend({
12457
12405
  unrenderEvents: function() {
12458
12406
  this.dayGrid.unrenderEvents();
12459
12407
 
12460
- // we DON'T need to call updateHeight() because:
12461
- // A) a renderEvents() call always happens after this, which will eventually call updateHeight()
12462
- // B) in IE8, this causes a flash whenever events are rerendered
12408
+ // we DON'T need to call updateHeight() because
12409
+ // a renderEvents() call always happens after this, which will eventually call updateHeight()
12463
12410
  },
12464
12411
 
12465
12412
 
@@ -12504,7 +12451,7 @@ var basicDayGridMethods = {
12504
12451
  renderHeadIntroHtml: function() {
12505
12452
  var view = this.view;
12506
12453
 
12507
- if (view.weekNumbersVisible) {
12454
+ if (view.colWeekNumbersVisible) {
12508
12455
  return '' +
12509
12456
  '<th class="fc-week-number ' + view.widgetHeaderClass + '" ' + view.weekNumberStyleAttr() + '>' +
12510
12457
  '<span>' + // needed for matchCellWidths
@@ -12520,13 +12467,15 @@ var basicDayGridMethods = {
12520
12467
  // Generates the HTML that will go before content-skeleton cells that display the day/week numbers
12521
12468
  renderNumberIntroHtml: function(row) {
12522
12469
  var view = this.view;
12470
+ var weekStart = this.getCellDate(row, 0);
12523
12471
 
12524
- if (view.weekNumbersVisible) {
12472
+ if (view.colWeekNumbersVisible) {
12525
12473
  return '' +
12526
12474
  '<td class="fc-week-number" ' + view.weekNumberStyleAttr() + '>' +
12527
- '<span>' + // needed for matchCellWidths
12528
- this.getCellDate(row, 0).format('w') +
12529
- '</span>' +
12475
+ view.buildGotoAnchorHtml( // aside from link, important for matchCellWidths
12476
+ { date: weekStart, type: 'week', forceOff: this.colCnt === 1 },
12477
+ weekStart.format('w') // inner HTML
12478
+ ) +
12530
12479
  '</td>';
12531
12480
  }
12532
12481
 
@@ -12538,7 +12487,7 @@ var basicDayGridMethods = {
12538
12487
  renderBgIntroHtml: function() {
12539
12488
  var view = this.view;
12540
12489
 
12541
- if (view.weekNumbersVisible) {
12490
+ if (view.colWeekNumbersVisible) {
12542
12491
  return '<td class="fc-week-number ' + view.widgetContentClass + '" ' +
12543
12492
  view.weekNumberStyleAttr() + '></td>';
12544
12493
  }
@@ -12552,7 +12501,7 @@ var basicDayGridMethods = {
12552
12501
  renderIntroHtml: function() {
12553
12502
  var view = this.view;
12554
12503
 
12555
- if (view.weekNumbersVisible) {
12504
+ if (view.colWeekNumbersVisible) {
12556
12505
  return '<td class="fc-week-number" ' + view.weekNumberStyleAttr() + '></td>';
12557
12506
  }
12558
12507
 
@@ -12586,8 +12535,6 @@ var MonthView = FC.MonthView = BasicView.extend({
12586
12535
  // Overrides the default BasicView behavior to have special multi-week auto-height logic
12587
12536
  setGridHeight: function(height, isAuto) {
12588
12537
 
12589
- isAuto = isAuto || this.opt('weekMode') === 'variable'; // LEGACY: weekMode is deprecated
12590
-
12591
12538
  // if auto, make the height of each row the height that it would be if there were 6 weeks
12592
12539
  if (isAuto) {
12593
12540
  height *= this.rowCnt / 6;
@@ -12598,11 +12545,6 @@ var MonthView = FC.MonthView = BasicView.extend({
12598
12545
 
12599
12546
 
12600
12547
  isFixedWeeks: function() {
12601
- var weekMode = this.opt('weekMode'); // LEGACY: weekMode is deprecated
12602
- if (weekMode) {
12603
- return weekMode === 'fixed'; // if any other type of weekMode, assume NOT fixed
12604
- }
12605
-
12606
12548
  return this.opt('fixedWeekCount');
12607
12549
  }
12608
12550
 
@@ -13032,9 +12974,8 @@ var AgendaView = FC.AgendaView = View.extend({
13032
12974
  this.dayGrid.unrenderEvents();
13033
12975
  }
13034
12976
 
13035
- // we DON'T need to call updateHeight() because:
13036
- // A) a renderEvents() call always happens after this, which will eventually call updateHeight()
13037
- // B) in IE8, this causes a flash whenever events are rerendered
12977
+ // we DON'T need to call updateHeight() because
12978
+ // a renderEvents() call always happens after this, which will eventually call updateHeight()
13038
12979
  },
13039
12980
 
13040
12981
 
@@ -13102,9 +13043,10 @@ var agendaTimeGridMethods = {
13102
13043
 
13103
13044
  return '' +
13104
13045
  '<th class="fc-axis fc-week-number ' + view.widgetHeaderClass + '" ' + view.axisStyleAttr() + '>' +
13105
- '<span>' + // needed for matchCellWidths
13106
- htmlEscape(weekText) +
13107
- '</span>' +
13046
+ view.buildGotoAnchorHtml( // aside from link, important for matchCellWidths
13047
+ { date: this.start, type: 'week', forceOff: this.colCnt > 1 },
13048
+ htmlEscape(weekText) // inner HTML
13049
+ ) +
13108
13050
  '</th>';
13109
13051
  }
13110
13052
  else {
@@ -13143,7 +13085,7 @@ var agendaDayGridMethods = {
13143
13085
  return '' +
13144
13086
  '<td class="fc-axis ' + view.widgetContentClass + '" ' + view.axisStyleAttr() + '>' +
13145
13087
  '<span>' + // needed for matchCellWidths
13146
- (view.opt('allDayHtml') || htmlEscape(view.opt('allDayText'))) +
13088
+ view.getAllDayHtml() +
13147
13089
  '</span>' +
13148
13090
  '</td>';
13149
13091
  },
@@ -13177,7 +13119,6 @@ fcViews.agenda = {
13177
13119
  'class': AgendaView,
13178
13120
  defaults: {
13179
13121
  allDaySlot: true,
13180
- allDayText: 'all-day',
13181
13122
  slotDuration: '00:30:00',
13182
13123
  minTime: '00:00:00',
13183
13124
  maxTime: '24:00:00',
@@ -13195,6 +13136,294 @@ fcViews.agendaWeek = {
13195
13136
  duration: { weeks: 1 }
13196
13137
  };
13197
13138
  ;;
13139
+
13140
+ /*
13141
+ Responsible for the scroller, and forwarding event-related actions into the "grid"
13142
+ */
13143
+ var ListView = View.extend({
13144
+
13145
+ grid: null,
13146
+ scroller: null,
13147
+
13148
+ initialize: function() {
13149
+ this.grid = new ListViewGrid(this);
13150
+ this.scroller = new Scroller({
13151
+ overflowX: 'hidden',
13152
+ overflowY: 'auto'
13153
+ });
13154
+ },
13155
+
13156
+ setRange: function(range) {
13157
+ View.prototype.setRange.call(this, range); // super
13158
+
13159
+ this.grid.setRange(range); // needs to process range-related options
13160
+ },
13161
+
13162
+ renderSkeleton: function() {
13163
+ this.el.addClass(
13164
+ 'fc-list-view ' +
13165
+ this.widgetContentClass
13166
+ );
13167
+
13168
+ this.scroller.render();
13169
+ this.scroller.el.appendTo(this.el);
13170
+
13171
+ this.grid.setElement(this.scroller.scrollEl);
13172
+ },
13173
+
13174
+ unrenderSkeleton: function() {
13175
+ this.scroller.destroy(); // will remove the Grid too
13176
+ },
13177
+
13178
+ setHeight: function(totalHeight, isAuto) {
13179
+ this.scroller.setHeight(this.computeScrollerHeight(totalHeight));
13180
+ },
13181
+
13182
+ computeScrollerHeight: function(totalHeight) {
13183
+ return totalHeight -
13184
+ subtractInnerElHeight(this.el, this.scroller.el); // everything that's NOT the scroller
13185
+ },
13186
+
13187
+ renderEvents: function(events) {
13188
+ this.grid.renderEvents(events);
13189
+ },
13190
+
13191
+ unrenderEvents: function() {
13192
+ this.grid.unrenderEvents();
13193
+ },
13194
+
13195
+ isEventResizable: function(event) {
13196
+ return false;
13197
+ },
13198
+
13199
+ isEventDraggable: function(event) {
13200
+ return false;
13201
+ }
13202
+
13203
+ });
13204
+
13205
+ /*
13206
+ Responsible for event rendering and user-interaction.
13207
+ Its "el" is the inner-content of the above view's scroller.
13208
+ */
13209
+ var ListViewGrid = Grid.extend({
13210
+
13211
+ segSelector: '.fc-list-item', // which elements accept event actions
13212
+ hasDayInteractions: false, // no day selection or day clicking
13213
+
13214
+ // slices by day
13215
+ spanToSegs: function(span) {
13216
+ var view = this.view;
13217
+ var dayStart = view.start.clone();
13218
+ var dayEnd;
13219
+ var seg;
13220
+ var segs = [];
13221
+
13222
+ while (dayStart < view.end) {
13223
+ dayEnd = dayStart.clone().add(1, 'day');
13224
+ seg = intersectRanges(span, {
13225
+ start: dayStart,
13226
+ end: dayEnd
13227
+ });
13228
+ if (seg) {
13229
+ segs.push(seg);
13230
+ }
13231
+ dayStart = dayEnd;
13232
+ }
13233
+
13234
+ return segs;
13235
+ },
13236
+
13237
+ // like "4:00am"
13238
+ computeEventTimeFormat: function() {
13239
+ return this.view.opt('mediumTimeFormat');
13240
+ },
13241
+
13242
+ // for events with a url, the whole <tr> should be clickable,
13243
+ // but it's impossible to wrap with an <a> tag. simulate this.
13244
+ handleSegClick: function(seg, ev) {
13245
+ var url;
13246
+
13247
+ Grid.prototype.handleSegClick.apply(this, arguments); // super. might prevent the default action
13248
+
13249
+ // not clicking on or within an <a> with an href
13250
+ if (!$(ev.target).closest('a[href]').length) {
13251
+ url = seg.event.url;
13252
+ if (url && !ev.isDefaultPrevented()) { // jsEvent not cancelled in handler
13253
+ window.location.href = url; // simulate link click
13254
+ }
13255
+ }
13256
+ },
13257
+
13258
+ // returns list of foreground segs that were actually rendered
13259
+ renderFgSegs: function(segs) {
13260
+ segs = this.renderFgSegEls(segs); // might filter away hidden events
13261
+
13262
+ if (!segs.length) {
13263
+ this.renderEmptyMessage();
13264
+ return segs;
13265
+ }
13266
+ else {
13267
+ return this.renderSegList(segs);
13268
+ }
13269
+ },
13270
+
13271
+ renderEmptyMessage: function() {
13272
+ this.el.html(
13273
+ '<div class="fc-list-empty-wrap2">' + // TODO: try less wraps
13274
+ '<div class="fc-list-empty-wrap1">' +
13275
+ '<div class="fc-list-empty">' +
13276
+ htmlEscape(this.view.opt('noEventsMessage')) +
13277
+ '</div>' +
13278
+ '</div>' +
13279
+ '</div>'
13280
+ );
13281
+ },
13282
+
13283
+ // render the event segments in the view. returns the mutated array.
13284
+ renderSegList: function(segs) {
13285
+ var tableEl = $('<table class="fc-list-table"><tbody/></table>');
13286
+ var tbodyEl = tableEl.find('tbody');
13287
+ var i, seg;
13288
+ var dayDate;
13289
+
13290
+ this.sortEventSegs(segs);
13291
+
13292
+ for (i = 0; i < segs.length; i++) {
13293
+ seg = segs[i];
13294
+
13295
+ // append a day header
13296
+ if (!dayDate || !seg.start.isSame(dayDate, 'day')) {
13297
+ dayDate = seg.start.clone().stripTime();
13298
+ tbodyEl.append(this.dayHeaderHtml(dayDate));
13299
+ }
13300
+
13301
+ tbodyEl.append(seg.el); // append event row
13302
+ }
13303
+
13304
+ this.el.empty().append(tableEl);
13305
+
13306
+ return segs; // return the sorted list
13307
+ },
13308
+
13309
+ // generates the HTML for the day headers that live amongst the event rows
13310
+ dayHeaderHtml: function(dayDate) {
13311
+ var view = this.view;
13312
+ var mainFormat = view.opt('listDayFormat');
13313
+ var altFormat = view.opt('listDayAltFormat');
13314
+
13315
+ return '<tr class="fc-list-heading" data-date="' + dayDate.format('YYYY-MM-DD') + '">' +
13316
+ '<td class="' + view.widgetHeaderClass + '" colspan="3">' +
13317
+ (mainFormat ?
13318
+ view.buildGotoAnchorHtml(
13319
+ dayDate,
13320
+ { 'class': 'fc-list-heading-main' },
13321
+ htmlEscape(dayDate.format(mainFormat)) // inner HTML
13322
+ ) :
13323
+ '') +
13324
+ (altFormat ?
13325
+ view.buildGotoAnchorHtml(
13326
+ dayDate,
13327
+ { 'class': 'fc-list-heading-alt' },
13328
+ htmlEscape(dayDate.format(altFormat)) // inner HTML
13329
+ ) :
13330
+ '') +
13331
+ '</td>' +
13332
+ '</tr>';
13333
+ },
13334
+
13335
+ // generates the HTML for a single event row
13336
+ fgSegHtml: function(seg) {
13337
+ var view = this.view;
13338
+ var classes = [ 'fc-list-item' ].concat(this.getSegCustomClasses(seg));
13339
+ var bgColor = this.getSegBackgroundColor(seg);
13340
+ var event = seg.event;
13341
+ var url = event.url;
13342
+ var timeHtml;
13343
+
13344
+ if (!seg.start.hasTime()) {
13345
+ if (this.displayEventTime) {
13346
+ timeHtml = view.getAllDayHtml();
13347
+ }
13348
+ }
13349
+ else {
13350
+ timeHtml = htmlEscape(this.getEventTimeText(event)); // might return empty
13351
+ }
13352
+
13353
+ if (url) {
13354
+ classes.push('fc-has-url');
13355
+ }
13356
+
13357
+ return '<tr class="' + classes.join(' ') + '">' +
13358
+ (timeHtml ?
13359
+ '<td class="fc-list-item-time ' + view.widgetContentClass + '">' +
13360
+ timeHtml +
13361
+ '</td>' :
13362
+ '') +
13363
+ '<td class="fc-list-item-marker ' + view.widgetContentClass + '">' +
13364
+ '<span class="fc-event-dot"' +
13365
+ (bgColor ?
13366
+ ' style="background-color:' + bgColor + '"' :
13367
+ '') +
13368
+ '></span>' +
13369
+ '</td>' +
13370
+ '<td class="fc-list-item-title ' + view.widgetContentClass + '">' +
13371
+ '<a' + (url ? ' href="' + htmlEscape(url) + '"' : '') + '>' +
13372
+ htmlEscape(seg.event.title) +
13373
+ '</a>' +
13374
+ '</td>' +
13375
+ '</tr>';
13376
+ }
13377
+
13378
+ });
13379
+
13380
+ ;;
13381
+
13382
+ fcViews.list = {
13383
+ 'class': ListView,
13384
+ buttonTextKey: 'list', // what to lookup in locale files
13385
+ defaults: {
13386
+ buttonText: 'list', // text to display for English
13387
+ listTime: true, // show the time column?
13388
+ listDayFormat: 'LL', // like "January 1, 2016"
13389
+ noEventsMessage: 'No events to display'
13390
+ }
13391
+ };
13392
+
13393
+ fcViews.listDay = {
13394
+ type: 'list',
13395
+ duration: { days: 1 },
13396
+ defaults: {
13397
+ listDayFormat: 'dddd' // day-of-week is all we need. full date is probably in header
13398
+ }
13399
+ };
13400
+
13401
+ fcViews.listWeek = {
13402
+ type: 'list',
13403
+ duration: { weeks: 1 },
13404
+ defaults: {
13405
+ listDayFormat: 'dddd', // day-of-week is more important
13406
+ listDayAltFormat: 'LL'
13407
+ }
13408
+ };
13409
+
13410
+ fcViews.listMonth = {
13411
+ type: 'list',
13412
+ duration: { month: 1 },
13413
+ defaults: {
13414
+ listDayAltFormat: 'dddd' // day-of-week is nice-to-have
13415
+ }
13416
+ };
13417
+
13418
+ fcViews.listYear = {
13419
+ type: 'list',
13420
+ duration: { year: 1 },
13421
+ defaults: {
13422
+ listDayAltFormat: 'dddd' // day-of-week is nice-to-have
13423
+ }
13424
+ };
13425
+
13426
+ ;;
13198
13427
 
13199
13428
  return FC; // export for Node/CommonJS
13200
13429
  });