fullcalendar.io-rails 2.9.1 → 3.0.0

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