fullcalendar.io-rails 3.3.1 → 3.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. checksums.yaml +4 -4
  2. data/lib/fullcalendar.io/rails/version.rb +1 -1
  3. data/vendor/assets/javascripts/fullcalendar.js +1707 -1407
  4. data/vendor/assets/javascripts/fullcalendar/gcal.js +3 -3
  5. data/vendor/assets/javascripts/fullcalendar/locale-all.js +5 -5
  6. data/vendor/assets/javascripts/fullcalendar/locale/af.js +1 -1
  7. data/vendor/assets/javascripts/fullcalendar/locale/ar-dz.js +1 -1
  8. data/vendor/assets/javascripts/fullcalendar/locale/ar-kw.js +1 -0
  9. data/vendor/assets/javascripts/fullcalendar/locale/ar-ly.js +1 -1
  10. data/vendor/assets/javascripts/fullcalendar/locale/ar-ma.js +1 -1
  11. data/vendor/assets/javascripts/fullcalendar/locale/ar-sa.js +1 -1
  12. data/vendor/assets/javascripts/fullcalendar/locale/ar-tn.js +1 -1
  13. data/vendor/assets/javascripts/fullcalendar/locale/ar.js +1 -1
  14. data/vendor/assets/javascripts/fullcalendar/locale/bg.js +1 -1
  15. data/vendor/assets/javascripts/fullcalendar/locale/ca.js +1 -1
  16. data/vendor/assets/javascripts/fullcalendar/locale/cs.js +1 -1
  17. data/vendor/assets/javascripts/fullcalendar/locale/da.js +1 -1
  18. data/vendor/assets/javascripts/fullcalendar/locale/de-at.js +1 -1
  19. data/vendor/assets/javascripts/fullcalendar/locale/de-ch.js +1 -0
  20. data/vendor/assets/javascripts/fullcalendar/locale/de.js +1 -1
  21. data/vendor/assets/javascripts/fullcalendar/locale/el.js +1 -1
  22. data/vendor/assets/javascripts/fullcalendar/locale/en-au.js +1 -1
  23. data/vendor/assets/javascripts/fullcalendar/locale/en-ca.js +1 -1
  24. data/vendor/assets/javascripts/fullcalendar/locale/en-gb.js +1 -1
  25. data/vendor/assets/javascripts/fullcalendar/locale/en-ie.js +1 -1
  26. data/vendor/assets/javascripts/fullcalendar/locale/en-nz.js +1 -1
  27. data/vendor/assets/javascripts/fullcalendar/locale/es-do.js +1 -1
  28. data/vendor/assets/javascripts/fullcalendar/locale/es.js +1 -1
  29. data/vendor/assets/javascripts/fullcalendar/locale/et.js +1 -0
  30. data/vendor/assets/javascripts/fullcalendar/locale/eu.js +1 -1
  31. data/vendor/assets/javascripts/fullcalendar/locale/fa.js +1 -1
  32. data/vendor/assets/javascripts/fullcalendar/locale/fi.js +1 -1
  33. data/vendor/assets/javascripts/fullcalendar/locale/fr-ca.js +1 -1
  34. data/vendor/assets/javascripts/fullcalendar/locale/fr-ch.js +1 -1
  35. data/vendor/assets/javascripts/fullcalendar/locale/fr.js +1 -1
  36. data/vendor/assets/javascripts/fullcalendar/locale/gl.js +1 -1
  37. data/vendor/assets/javascripts/fullcalendar/locale/he.js +1 -1
  38. data/vendor/assets/javascripts/fullcalendar/locale/hi.js +1 -1
  39. data/vendor/assets/javascripts/fullcalendar/locale/hr.js +1 -1
  40. data/vendor/assets/javascripts/fullcalendar/locale/hu.js +1 -1
  41. data/vendor/assets/javascripts/fullcalendar/locale/id.js +1 -1
  42. data/vendor/assets/javascripts/fullcalendar/locale/is.js +1 -1
  43. data/vendor/assets/javascripts/fullcalendar/locale/it.js +1 -1
  44. data/vendor/assets/javascripts/fullcalendar/locale/ja.js +1 -1
  45. data/vendor/assets/javascripts/fullcalendar/locale/kk.js +1 -1
  46. data/vendor/assets/javascripts/fullcalendar/locale/ko.js +1 -1
  47. data/vendor/assets/javascripts/fullcalendar/locale/lb.js +1 -1
  48. data/vendor/assets/javascripts/fullcalendar/locale/lt.js +1 -1
  49. data/vendor/assets/javascripts/fullcalendar/locale/lv.js +1 -1
  50. data/vendor/assets/javascripts/fullcalendar/locale/mk.js +1 -1
  51. data/vendor/assets/javascripts/fullcalendar/locale/ms-my.js +1 -1
  52. data/vendor/assets/javascripts/fullcalendar/locale/ms.js +1 -1
  53. data/vendor/assets/javascripts/fullcalendar/locale/nb.js +1 -1
  54. data/vendor/assets/javascripts/fullcalendar/locale/nl-be.js +1 -1
  55. data/vendor/assets/javascripts/fullcalendar/locale/nl.js +1 -1
  56. data/vendor/assets/javascripts/fullcalendar/locale/nn.js +1 -1
  57. data/vendor/assets/javascripts/fullcalendar/locale/pl.js +1 -1
  58. data/vendor/assets/javascripts/fullcalendar/locale/pt-br.js +1 -1
  59. data/vendor/assets/javascripts/fullcalendar/locale/pt.js +1 -1
  60. data/vendor/assets/javascripts/fullcalendar/locale/ro.js +1 -1
  61. data/vendor/assets/javascripts/fullcalendar/locale/ru.js +1 -1
  62. data/vendor/assets/javascripts/fullcalendar/locale/sk.js +1 -1
  63. data/vendor/assets/javascripts/fullcalendar/locale/sl.js +1 -1
  64. data/vendor/assets/javascripts/fullcalendar/locale/sr-cyrl.js +1 -1
  65. data/vendor/assets/javascripts/fullcalendar/locale/sr.js +1 -1
  66. data/vendor/assets/javascripts/fullcalendar/locale/sv.js +1 -1
  67. data/vendor/assets/javascripts/fullcalendar/locale/th.js +1 -1
  68. data/vendor/assets/javascripts/fullcalendar/locale/tr.js +1 -1
  69. data/vendor/assets/javascripts/fullcalendar/locale/uk.js +1 -1
  70. data/vendor/assets/javascripts/fullcalendar/locale/vi.js +1 -1
  71. data/vendor/assets/javascripts/fullcalendar/locale/zh-cn.js +1 -1
  72. data/vendor/assets/javascripts/fullcalendar/locale/zh-tw.js +1 -1
  73. data/vendor/assets/stylesheets/fullcalendar.css +1 -1
  74. data/vendor/assets/stylesheets/fullcalendar.print.css +1 -1
  75. metadata +101 -156
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 183d16d2fc0cd0013d71ab3931e407e1c462d825
4
- data.tar.gz: c240d22deeced6d3ed07fa23f1c2fd5773146531
3
+ metadata.gz: b02874e67890f4d0b6f1992597ecead7235953ff
4
+ data.tar.gz: 6afc0a25ade54f6389f810f86833ba26067be68b
5
5
  SHA512:
6
- metadata.gz: 647278a51aa2ad0b2416cd7e6f2be82499e04a06eeaa3f40a7950d02884b75538d6d2dd0d5567313053c20c9c4b3cf45f4c94d6862355def7fa66bd6318f4f9f
7
- data.tar.gz: eec81bf6e5a4038afb977bb8d725751159582fbe657ae95cdc7cde4598be179c7a54a6caed08d97872e0d642ef02d16d14f5adac7f001b51ca13471dc825d3c4
6
+ metadata.gz: 030d51c3449b814cf150dab1c0d96b8124515317252a3c9cef5a4c2f1325ed7cc37e88281aa71e9452eae3883172e7c7ec9d7d3c22894195711cb1eed89deecc
7
+ data.tar.gz: 90b2d739fc26cd1d7fc5fd3fa2170c62ec63fe6942e3a7cc2db766e0f39ac11f931fb35a4e2364545ab4d56140d9119c29208769bf25cfa0cf5abe085e910604
@@ -1,5 +1,5 @@
1
1
  module Fullcalendario
2
2
  module Rails
3
- VERSION = "3.3.1"
3
+ VERSION = "3.4.0"
4
4
  end
5
5
  end
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * FullCalendar v3.3.1
2
+ * FullCalendar v3.4.0
3
3
  * Docs & License: https://fullcalendar.io/
4
4
  * (c) 2017 Adam Shaw
5
5
  */
@@ -19,7 +19,7 @@
19
19
  ;;
20
20
 
21
21
  var FC = $.fullCalendar = {
22
- version: "3.3.1",
22
+ version: "3.4.0",
23
23
  // When introducing internal API incompatibilities (where fullcalendar plugins would break),
24
24
  // the minor version of the calendar should be upped (ex: 2.7.2 -> 2.8.0)
25
25
  // and the below integer should be incremented.
@@ -1402,28 +1402,48 @@ newMomentProto.utcOffset = function(tzo) {
1402
1402
  // -------------------------------------------------------------------------------------------------
1403
1403
 
1404
1404
  newMomentProto.format = function() {
1405
+
1405
1406
  if (this._fullCalendar && arguments[0]) { // an enhanced moment? and a format string provided?
1406
1407
  return formatDate(this, arguments[0]); // our extended formatting
1407
1408
  }
1408
1409
  if (this._ambigTime) {
1409
- return oldMomentFormat(this, 'YYYY-MM-DD');
1410
+ return oldMomentFormat(englishMoment(this), 'YYYY-MM-DD');
1410
1411
  }
1411
1412
  if (this._ambigZone) {
1412
- return oldMomentFormat(this, 'YYYY-MM-DD[T]HH:mm:ss');
1413
+ return oldMomentFormat(englishMoment(this), 'YYYY-MM-DD[T]HH:mm:ss');
1414
+ }
1415
+ if (this._fullCalendar) { // enhanced non-ambig moment?
1416
+ // moment.format() doesn't ensure english, but we want to.
1417
+ return oldMomentFormat(englishMoment(this));
1413
1418
  }
1419
+
1414
1420
  return oldMomentProto.format.apply(this, arguments);
1415
1421
  };
1416
1422
 
1417
1423
  newMomentProto.toISOString = function() {
1424
+
1418
1425
  if (this._ambigTime) {
1419
- return oldMomentFormat(this, 'YYYY-MM-DD');
1426
+ return oldMomentFormat(englishMoment(this), 'YYYY-MM-DD');
1420
1427
  }
1421
1428
  if (this._ambigZone) {
1422
- return oldMomentFormat(this, 'YYYY-MM-DD[T]HH:mm:ss');
1429
+ return oldMomentFormat(englishMoment(this), 'YYYY-MM-DD[T]HH:mm:ss');
1423
1430
  }
1431
+ if (this._fullCalendar) { // enhanced non-ambig moment?
1432
+ // depending on browser, moment might not output english. ensure english.
1433
+ // https://github.com/moment/moment/blob/2.18.1/src/lib/moment/format.js#L22
1434
+ return oldMomentProto.toISOString.apply(englishMoment(this), arguments);
1435
+ }
1436
+
1424
1437
  return oldMomentProto.toISOString.apply(this, arguments);
1425
1438
  };
1426
1439
 
1440
+ function englishMoment(mom) {
1441
+ if (mom.locale() !== 'en') {
1442
+ return mom.clone().locale('en');
1443
+ }
1444
+ return mom;
1445
+ }
1446
+
1427
1447
  ;;
1428
1448
  (function() {
1429
1449
 
@@ -1906,293 +1926,742 @@ function mixIntoClass(theClass, members) {
1906
1926
  }
1907
1927
  ;;
1908
1928
 
1909
- /*
1910
- Wrap jQuery's Deferred Promise object to be slightly more Promise/A+ compliant.
1911
- With the added non-standard feature of synchronously executing handlers on resolved promises,
1912
- which doesn't always happen otherwise (esp with nested .then handlers!?),
1913
- so, this makes things a lot easier, esp because jQuery 3 changed the synchronicity for Deferred objects.
1929
+ var Model = Class.extend(EmitterMixin, ListenerMixin, {
1914
1930
 
1915
- TODO: write tests and more comments
1916
- */
1931
+ _props: null,
1932
+ _watchers: null,
1933
+ _globalWatchArgs: null,
1917
1934
 
1918
- function Promise(executor) {
1919
- var deferred = $.Deferred();
1920
- var promise = deferred.promise();
1935
+ constructor: function() {
1936
+ this._watchers = {};
1937
+ this._props = {};
1938
+ this.applyGlobalWatchers();
1939
+ },
1921
1940
 
1922
- if (typeof executor === 'function') {
1923
- executor(
1924
- function(value) { // resolve
1925
- if (Promise.immediate) {
1926
- promise._value = value;
1927
- }
1928
- deferred.resolve(value);
1929
- },
1930
- function() { // reject
1931
- deferred.reject();
1941
+ applyGlobalWatchers: function() {
1942
+ var argSets = this._globalWatchArgs || [];
1943
+ var i;
1944
+
1945
+ for (i = 0; i < argSets.length; i++) {
1946
+ this.watch.apply(this, argSets[i]);
1947
+ }
1948
+ },
1949
+
1950
+ has: function(name) {
1951
+ return name in this._props;
1952
+ },
1953
+
1954
+ get: function(name) {
1955
+ if (name === undefined) {
1956
+ return this._props;
1957
+ }
1958
+
1959
+ return this._props[name];
1960
+ },
1961
+
1962
+ set: function(name, val) {
1963
+ var newProps;
1964
+
1965
+ if (typeof name === 'string') {
1966
+ newProps = {};
1967
+ newProps[name] = val === undefined ? null : val;
1968
+ }
1969
+ else {
1970
+ newProps = name;
1971
+ }
1972
+
1973
+ this.setProps(newProps);
1974
+ },
1975
+
1976
+ reset: function(newProps) {
1977
+ var oldProps = this._props;
1978
+ var changeset = {}; // will have undefined's to signal unsets
1979
+ var name;
1980
+
1981
+ for (name in oldProps) {
1982
+ changeset[name] = undefined;
1983
+ }
1984
+
1985
+ for (name in newProps) {
1986
+ changeset[name] = newProps[name];
1987
+ }
1988
+
1989
+ this.setProps(changeset);
1990
+ },
1991
+
1992
+ unset: function(name) { // accepts a string or array of strings
1993
+ var newProps = {};
1994
+ var names;
1995
+ var i;
1996
+
1997
+ if (typeof name === 'string') {
1998
+ names = [ name ];
1999
+ }
2000
+ else {
2001
+ names = name;
2002
+ }
2003
+
2004
+ for (i = 0; i < names.length; i++) {
2005
+ newProps[names[i]] = undefined;
2006
+ }
2007
+
2008
+ this.setProps(newProps);
2009
+ },
2010
+
2011
+ setProps: function(newProps) {
2012
+ var changedProps = {};
2013
+ var changedCnt = 0;
2014
+ var name, val;
2015
+
2016
+ for (name in newProps) {
2017
+ val = newProps[name];
2018
+
2019
+ // a change in value?
2020
+ // if an object, don't check equality, because might have been mutated internally.
2021
+ // TODO: eventually enforce immutability.
2022
+ if (
2023
+ typeof val === 'object' ||
2024
+ val !== this._props[name]
2025
+ ) {
2026
+ changedProps[name] = val;
2027
+ changedCnt++;
1932
2028
  }
1933
- );
1934
- }
1935
-
1936
- if (Promise.immediate) {
1937
- var origThen = promise.then;
1938
-
1939
- promise.then = function(onFulfilled, onRejected) {
1940
- var state = promise.state();
1941
-
1942
- if (state === 'resolved') {
1943
- if (typeof onFulfilled === 'function') {
1944
- return Promise.resolve(onFulfilled(promise._value));
2029
+ }
2030
+
2031
+ if (changedCnt) {
2032
+
2033
+ this.trigger('before:batchChange', changedProps);
2034
+
2035
+ for (name in changedProps) {
2036
+ val = changedProps[name];
2037
+
2038
+ this.trigger('before:change', name, val);
2039
+ this.trigger('before:change:' + name, val);
2040
+ }
2041
+
2042
+ for (name in changedProps) {
2043
+ val = changedProps[name];
2044
+
2045
+ if (val === undefined) {
2046
+ delete this._props[name];
1945
2047
  }
2048
+ else {
2049
+ this._props[name] = val;
2050
+ }
2051
+
2052
+ this.trigger('change:' + name, val);
2053
+ this.trigger('change', name, val);
2054
+ }
2055
+
2056
+ this.trigger('batchChange', changedProps);
2057
+ }
2058
+ },
2059
+
2060
+ watch: function(name, depList, startFunc, stopFunc) {
2061
+ var _this = this;
2062
+
2063
+ this.unwatch(name);
2064
+
2065
+ this._watchers[name] = this._watchDeps(depList, function(deps) {
2066
+ var res = startFunc.call(_this, deps);
2067
+
2068
+ if (res && res.then) {
2069
+ _this.unset(name); // put in an unset state while resolving
2070
+ res.then(function(val) {
2071
+ _this.set(name, val);
2072
+ });
2073
+ }
2074
+ else {
2075
+ _this.set(name, res);
2076
+ }
2077
+ }, function() {
2078
+ _this.unset(name);
2079
+
2080
+ if (stopFunc) {
2081
+ stopFunc.call(_this);
1946
2082
  }
1947
- else if (state === 'rejected') {
1948
- if (typeof onRejected === 'function') {
1949
- onRejected();
1950
- return promise; // already rejected
2083
+ });
2084
+ },
2085
+
2086
+ unwatch: function(name) {
2087
+ var watcher = this._watchers[name];
2088
+
2089
+ if (watcher) {
2090
+ delete this._watchers[name];
2091
+ watcher.teardown();
2092
+ }
2093
+ },
2094
+
2095
+ _watchDeps: function(depList, startFunc, stopFunc) {
2096
+ var _this = this;
2097
+ var queuedChangeCnt = 0;
2098
+ var depCnt = depList.length;
2099
+ var satisfyCnt = 0;
2100
+ var values = {}; // what's passed as the `deps` arguments
2101
+ var bindTuples = []; // array of [ eventName, handlerFunc ] arrays
2102
+ var isCallingStop = false;
2103
+
2104
+ function onBeforeDepChange(depName, val, isOptional) {
2105
+ queuedChangeCnt++;
2106
+ if (queuedChangeCnt === 1) { // first change to cause a "stop" ?
2107
+ if (satisfyCnt === depCnt) { // all deps previously satisfied?
2108
+ isCallingStop = true;
2109
+ stopFunc();
2110
+ isCallingStop = false;
1951
2111
  }
1952
2112
  }
2113
+ }
1953
2114
 
1954
- return origThen.call(promise, onFulfilled, onRejected);
1955
- };
1956
- }
2115
+ function onDepChange(depName, val, isOptional) {
1957
2116
 
1958
- return promise; // instanceof Promise will break :( TODO: make Promise a real class
1959
- }
2117
+ if (val === undefined) { // unsetting a value?
1960
2118
 
1961
- FC.Promise = Promise;
2119
+ // required dependency that was previously set?
2120
+ if (!isOptional && values[depName] !== undefined) {
2121
+ satisfyCnt--;
2122
+ }
1962
2123
 
1963
- Promise.immediate = true;
2124
+ delete values[depName];
2125
+ }
2126
+ else { // setting a value?
1964
2127
 
2128
+ // required dependency that was previously unset?
2129
+ if (!isOptional && values[depName] === undefined) {
2130
+ satisfyCnt++;
2131
+ }
1965
2132
 
1966
- Promise.resolve = function(value) {
1967
- if (value && typeof value.resolve === 'function') {
1968
- return value.promise();
1969
- }
1970
- if (value && typeof value.then === 'function') {
1971
- return value;
1972
- }
1973
- else {
1974
- var deferred = $.Deferred().resolve(value);
1975
- var promise = deferred.promise();
2133
+ values[depName] = val;
2134
+ }
1976
2135
 
1977
- if (Promise.immediate) {
1978
- var origThen = promise.then;
2136
+ queuedChangeCnt--;
2137
+ if (!queuedChangeCnt) { // last change to cause a "start"?
1979
2138
 
1980
- promise._value = value;
2139
+ // now finally satisfied or satisfied all along?
2140
+ if (satisfyCnt === depCnt) {
1981
2141
 
1982
- promise.then = function(onFulfilled, onRejected) {
1983
- if (typeof onFulfilled === 'function') {
1984
- return Promise.resolve(onFulfilled(value));
2142
+ // if the stopFunc initiated another value change, ignore it.
2143
+ // it will be processed by another change event anyway.
2144
+ if (!isCallingStop) {
2145
+ startFunc(values);
2146
+ }
1985
2147
  }
1986
- return origThen.call(promise, onFulfilled, onRejected);
1987
- };
2148
+ }
1988
2149
  }
1989
2150
 
1990
- return promise;
1991
- }
1992
- };
2151
+ // intercept for .on() that remembers handlers
2152
+ function bind(eventName, handler) {
2153
+ _this.on(eventName, handler);
2154
+ bindTuples.push([ eventName, handler ]);
2155
+ }
1993
2156
 
2157
+ // listen to dependency changes
2158
+ depList.forEach(function(depName) {
2159
+ var isOptional = false;
1994
2160
 
1995
- Promise.reject = function() {
1996
- return $.Deferred().reject().promise();
1997
- };
2161
+ if (depName.charAt(0) === '?') { // TODO: more DRY
2162
+ depName = depName.substring(1);
2163
+ isOptional = true;
2164
+ }
1998
2165
 
2166
+ bind('before:change:' + depName, function(val) {
2167
+ onBeforeDepChange(depName, val, isOptional);
2168
+ });
1999
2169
 
2000
- Promise.all = function(inputs) {
2001
- var hasAllValues = false;
2002
- var values;
2003
- var i, input;
2170
+ bind('change:' + depName, function(val) {
2171
+ onDepChange(depName, val, isOptional);
2172
+ });
2173
+ });
2004
2174
 
2005
- if (Promise.immediate) {
2006
- hasAllValues = true;
2007
- values = [];
2175
+ // process current dependency values
2176
+ depList.forEach(function(depName) {
2177
+ var isOptional = false;
2008
2178
 
2009
- for (i = 0; i < inputs.length; i++) {
2010
- input = inputs[i];
2179
+ if (depName.charAt(0) === '?') { // TODO: more DRY
2180
+ depName = depName.substring(1);
2181
+ isOptional = true;
2182
+ }
2011
2183
 
2012
- if (input && typeof input.state === 'function' && input.state() === 'resolved' && ('_value' in input)) {
2013
- values.push(input._value);
2184
+ if (_this.has(depName)) {
2185
+ values[depName] = _this.get(depName);
2186
+ satisfyCnt++;
2014
2187
  }
2015
- else if (input && typeof input.then === 'function') {
2016
- hasAllValues = false;
2017
- break;
2188
+ else if (isOptional) {
2189
+ satisfyCnt++;
2018
2190
  }
2019
- else {
2020
- values.push(input);
2191
+ });
2192
+
2193
+ // initially satisfied
2194
+ if (satisfyCnt === depCnt) {
2195
+ startFunc(values);
2196
+ }
2197
+
2198
+ return {
2199
+ teardown: function() {
2200
+ // remove all handlers
2201
+ for (var i = 0; i < bindTuples.length; i++) {
2202
+ _this.off(bindTuples[i][0], bindTuples[i][1]);
2203
+ }
2204
+ bindTuples = null;
2205
+
2206
+ // was satisfied, so call stopFunc
2207
+ if (satisfyCnt === depCnt) {
2208
+ stopFunc();
2209
+ }
2210
+ },
2211
+ flash: function() {
2212
+ if (satisfyCnt === depCnt) {
2213
+ stopFunc();
2214
+ startFunc(values);
2215
+ }
2021
2216
  }
2217
+ };
2218
+ },
2219
+
2220
+ flash: function(name) {
2221
+ var watcher = this._watchers[name];
2222
+
2223
+ if (watcher) {
2224
+ watcher.flash();
2022
2225
  }
2023
2226
  }
2024
2227
 
2025
- if (hasAllValues) {
2026
- return Promise.resolve(values);
2027
- }
2028
- else {
2029
- return $.when.apply($.when, inputs).then(function() {
2030
- return $.when($.makeArray(arguments));
2031
- });
2228
+ });
2229
+
2230
+
2231
+ Model.watch = function(/* same arguments as this.watch() */) {
2232
+ var proto = this.prototype;
2233
+
2234
+ if (!proto._globalWatchArgs) {
2235
+ proto._globalWatchArgs = [];
2032
2236
  }
2237
+
2238
+ proto._globalWatchArgs.push(arguments);
2033
2239
  };
2034
2240
 
2241
+
2242
+ FC.Model = Model;
2243
+
2244
+
2035
2245
  ;;
2036
2246
 
2037
- // TODO: write tests and clean up code
2247
+ var Promise = {
2038
2248
 
2039
- function TaskQueue(debounceWait) {
2040
- var q = []; // array of runFuncs
2249
+ construct: function(executor) {
2250
+ var deferred = $.Deferred();
2251
+ var promise = deferred.promise();
2041
2252
 
2042
- function addTask(taskFunc) {
2043
- return new Promise(function(resolve) {
2253
+ if (typeof executor === 'function') {
2254
+ executor(
2255
+ function(val) { // resolve
2256
+ deferred.resolve(val);
2257
+ attachImmediatelyResolvingThen(promise, val);
2258
+ },
2259
+ function() { // reject
2260
+ deferred.reject();
2261
+ attachImmediatelyRejectingThen(promise);
2262
+ }
2263
+ );
2264
+ }
2044
2265
 
2045
- // should run this function when it's taskFunc's turn to run.
2046
- // responsible for popping itself off the queue.
2047
- var runFunc = function() {
2048
- Promise.resolve(taskFunc()) // result might be async, coerce to promise
2049
- .then(resolve) // resolve TaskQueue::push's promise, for the caller. will receive result of taskFunc.
2050
- .then(function() {
2051
- q.shift(); // pop itself off
2266
+ return promise;
2267
+ },
2052
2268
 
2053
- // run the next task, if any
2054
- if (q.length) {
2055
- q[0]();
2056
- }
2057
- });
2058
- };
2269
+ resolve: function(val) {
2270
+ var deferred = $.Deferred().resolve(val);
2271
+ var promise = deferred.promise();
2059
2272
 
2060
- // always put the task at the end of the queue, BEFORE running the task
2061
- q.push(runFunc);
2273
+ attachImmediatelyResolvingThen(promise, val);
2062
2274
 
2063
- // if it's the only task in the queue, run immediately
2064
- if (q.length === 1) {
2065
- runFunc();
2066
- }
2067
- });
2068
- }
2275
+ return promise;
2276
+ },
2069
2277
 
2070
- this.add = // potentially debounce, for the public method
2071
- typeof debounceWait === 'number' ?
2072
- debounce(addTask, debounceWait) :
2073
- addTask; // if not a number (null/undefined/false), no debounce at all
2278
+ reject: function() {
2279
+ var deferred = $.Deferred().reject();
2280
+ var promise = deferred.promise();
2074
2281
 
2075
- this.addQuickly = addTask; // guaranteed no debounce
2076
- }
2282
+ attachImmediatelyRejectingThen(promise);
2077
2283
 
2078
- FC.TaskQueue = TaskQueue;
2284
+ return promise;
2285
+ }
2079
2286
 
2080
- /*
2081
- q = new TaskQueue();
2287
+ };
2082
2288
 
2083
- function work(i) {
2084
- return q.push(function() {
2085
- trigger();
2086
- console.log('work' + i);
2087
- });
2289
+
2290
+ function attachImmediatelyResolvingThen(promise, val) {
2291
+ promise.then = function(onResolve) {
2292
+ if (typeof onResolve === 'function') {
2293
+ onResolve(val);
2294
+ }
2295
+ return promise; // for chaining
2296
+ };
2088
2297
  }
2089
2298
 
2090
- var cnt = 0;
2091
2299
 
2092
- function trigger() {
2093
- if (cnt < 5) {
2094
- cnt++;
2095
- work(cnt);
2096
- }
2300
+ function attachImmediatelyRejectingThen(promise) {
2301
+ promise.then = function(onResolve, onReject) {
2302
+ if (typeof onReject === 'function') {
2303
+ onReject();
2304
+ }
2305
+ return promise; // for chaining
2306
+ };
2097
2307
  }
2098
2308
 
2099
- work(9);
2100
- */
2309
+
2310
+ FC.Promise = Promise;
2101
2311
 
2102
2312
  ;;
2103
2313
 
2104
- var EmitterMixin = FC.EmitterMixin = {
2314
+ var TaskQueue = Class.extend(EmitterMixin, {
2105
2315
 
2106
- // jQuery-ification via $(this) allows a non-DOM object to have
2107
- // the same event handling capabilities (including namespaces).
2316
+ q: null,
2317
+ isPaused: false,
2318
+ isRunning: false,
2108
2319
 
2109
2320
 
2110
- on: function(types, handler) {
2111
- $(this).on(types, this._prepareIntercept(handler));
2112
- return this; // for chaining
2321
+ constructor: function() {
2322
+ this.q = [];
2113
2323
  },
2114
2324
 
2115
2325
 
2116
- one: function(types, handler) {
2117
- $(this).one(types, this._prepareIntercept(handler));
2118
- return this; // for chaining
2326
+ queue: function(/* taskFunc, taskFunc... */) {
2327
+ this.q.push.apply(this.q, arguments); // append
2328
+ this.tryStart();
2119
2329
  },
2120
2330
 
2121
2331
 
2122
- _prepareIntercept: function(handler) {
2123
- // handlers are always called with an "event" object as their first param.
2124
- // sneak the `this` context and arguments into the extra parameter object
2125
- // and forward them on to the original handler.
2126
- var intercept = function(ev, extra) {
2127
- return handler.apply(
2128
- extra.context || this,
2129
- extra.args || []
2130
- );
2131
- };
2332
+ pause: function() {
2333
+ this.isPaused = true;
2334
+ },
2132
2335
 
2133
- // mimick jQuery's internal "proxy" system (risky, I know)
2134
- // causing all functions with the same .guid to appear to be the same.
2135
- // https://github.com/jquery/jquery/blob/2.2.4/src/core.js#L448
2136
- // this is needed for calling .off with the original non-intercept handler.
2137
- if (!handler.guid) {
2138
- handler.guid = $.guid++;
2139
- }
2140
- intercept.guid = handler.guid;
2141
2336
 
2142
- return intercept;
2337
+ resume: function() {
2338
+ this.isPaused = false;
2339
+ this.tryStart();
2143
2340
  },
2144
2341
 
2145
2342
 
2146
- off: function(types, handler) {
2147
- $(this).off(types, handler);
2343
+ tryStart: function() {
2344
+ if (!this.isRunning && this.canRunNext()) {
2345
+ this.isRunning = true;
2346
+ this.trigger('start');
2347
+ this.runNext();
2348
+ }
2349
+ },
2148
2350
 
2149
- return this; // for chaining
2351
+
2352
+ canRunNext: function() {
2353
+ return !this.isPaused && this.q.length;
2150
2354
  },
2151
2355
 
2152
2356
 
2153
- trigger: function(types) {
2154
- var args = Array.prototype.slice.call(arguments, 1); // arguments after the first
2357
+ runNext: function() { // does not check canRunNext
2358
+ this.runTask(this.q.shift());
2359
+ },
2155
2360
 
2156
- // pass in "extra" info to the intercept
2157
- $(this).triggerHandler(types, { args: args });
2158
2361
 
2159
- return this; // for chaining
2362
+ runTask: function(task) {
2363
+ this.runTaskFunc(task);
2160
2364
  },
2161
2365
 
2162
2366
 
2163
- triggerWith: function(types, context, args) {
2367
+ runTaskFunc: function(taskFunc) {
2368
+ var _this = this;
2369
+ var res = taskFunc();
2164
2370
 
2165
- // `triggerHandler` is less reliant on the DOM compared to `trigger`.
2166
- // pass in "extra" info to the intercept.
2167
- $(this).triggerHandler(types, { context: context, args: args });
2371
+ if (res && res.then) {
2372
+ res.then(done);
2373
+ }
2374
+ else {
2375
+ done();
2376
+ }
2168
2377
 
2169
- return this; // for chaining
2378
+ function done() {
2379
+ if (_this.canRunNext()) {
2380
+ _this.runNext();
2381
+ }
2382
+ else {
2383
+ _this.isRunning = false;
2384
+ _this.trigger('stop');
2385
+ }
2386
+ }
2170
2387
  }
2171
2388
 
2172
- };
2389
+ });
2390
+
2391
+ FC.TaskQueue = TaskQueue;
2173
2392
 
2174
2393
  ;;
2175
2394
 
2176
- /*
2177
- Utility methods for easily listening to events on another object,
2178
- and more importantly, easily unlistening from them.
2179
- */
2180
- var ListenerMixin = FC.ListenerMixin = (function() {
2181
- var guid = 0;
2182
- var ListenerMixin = {
2395
+ var RenderQueue = TaskQueue.extend({
2183
2396
 
2184
- listenerId: null,
2397
+ waitsByNamespace: null,
2398
+ waitNamespace: null,
2399
+ waitId: null,
2185
2400
 
2186
- /*
2187
- Given an `other` object that has on/off methods, bind the given `callback` to an event by the given name.
2188
- The `callback` will be called with the `this` context of the object that .listenTo is being called on.
2189
- Can be called:
2190
- .listenTo(other, eventName, callback)
2191
- OR
2192
- .listenTo(other, {
2193
- eventName1: callback1,
2194
- eventName2: callback2
2195
- })
2401
+
2402
+ constructor: function(waitsByNamespace) {
2403
+ TaskQueue.call(this); // super-constructor
2404
+
2405
+ this.waitsByNamespace = waitsByNamespace || {};
2406
+ },
2407
+
2408
+
2409
+ queue: function(taskFunc, namespace, type) {
2410
+ var task = {
2411
+ func: taskFunc,
2412
+ namespace: namespace,
2413
+ type: type
2414
+ };
2415
+ var waitMs;
2416
+
2417
+ if (namespace) {
2418
+ waitMs = this.waitsByNamespace[namespace];
2419
+ }
2420
+
2421
+ if (this.waitNamespace) {
2422
+ if (namespace === this.waitNamespace && waitMs != null) {
2423
+ this.delayWait(waitMs);
2424
+ }
2425
+ else {
2426
+ this.clearWait();
2427
+ this.tryStart();
2428
+ }
2429
+ }
2430
+
2431
+ if (this.compoundTask(task)) { // appended to queue?
2432
+
2433
+ if (!this.waitNamespace && waitMs != null) {
2434
+ this.startWait(namespace, waitMs);
2435
+ }
2436
+ else {
2437
+ this.tryStart();
2438
+ }
2439
+ }
2440
+ },
2441
+
2442
+
2443
+ startWait: function(namespace, waitMs) {
2444
+ this.waitNamespace = namespace;
2445
+ this.spawnWait(waitMs);
2446
+ },
2447
+
2448
+
2449
+ delayWait: function(waitMs) {
2450
+ clearTimeout(this.waitId);
2451
+ this.spawnWait(waitMs);
2452
+ },
2453
+
2454
+
2455
+ spawnWait: function(waitMs) {
2456
+ var _this = this;
2457
+
2458
+ this.waitId = setTimeout(function() {
2459
+ _this.waitNamespace = null;
2460
+ _this.tryStart();
2461
+ }, waitMs);
2462
+ },
2463
+
2464
+
2465
+ clearWait: function() {
2466
+ if (this.waitNamespace) {
2467
+ clearTimeout(this.waitId);
2468
+ this.waitId = null;
2469
+ this.waitNamespace = null;
2470
+ }
2471
+ },
2472
+
2473
+
2474
+ canRunNext: function() {
2475
+ if (!TaskQueue.prototype.canRunNext.apply(this, arguments)) {
2476
+ return false;
2477
+ }
2478
+
2479
+ // waiting for a certain namespace to stop receiving tasks?
2480
+ if (this.waitNamespace) {
2481
+
2482
+ // if there was a different namespace task in the meantime,
2483
+ // that forces all previously-waiting tasks to suddenly execute.
2484
+ // TODO: find a way to do this in constant time.
2485
+ for (var q = this.q, i = 0; i < q.length; i++) {
2486
+ if (q[i].namespace !== this.waitNamespace) {
2487
+ return true; // allow execution
2488
+ }
2489
+ }
2490
+
2491
+ return false;
2492
+ }
2493
+
2494
+ return true;
2495
+ },
2496
+
2497
+
2498
+ runTask: function(task) {
2499
+ this.runTaskFunc(task.func);
2500
+ },
2501
+
2502
+
2503
+ compoundTask: function(newTask) {
2504
+ var q = this.q;
2505
+ var shouldAppend = true;
2506
+ var i, task;
2507
+
2508
+ if (newTask.namespace) {
2509
+
2510
+ if (newTask.type === 'destroy' || newTask.type === 'init') {
2511
+
2512
+ // remove all add/remove ops with same namespace, regardless of order
2513
+ for (i = q.length - 1; i >= 0; i--) {
2514
+ task = q[i];
2515
+
2516
+ if (
2517
+ task.namespace === newTask.namespace &&
2518
+ (task.type === 'add' || task.type === 'remove')
2519
+ ) {
2520
+ q.splice(i, 1); // remove task
2521
+ }
2522
+ }
2523
+
2524
+ if (newTask.type === 'destroy') {
2525
+ // eat away final init/destroy operation
2526
+ if (q.length) {
2527
+ task = q[q.length - 1]; // last task
2528
+
2529
+ if (task.namespace === newTask.namespace) {
2530
+
2531
+ // the init and our destroy cancel each other out
2532
+ if (task.type === 'init') {
2533
+ shouldAppend = false;
2534
+ q.pop();
2535
+ }
2536
+ // prefer to use the destroy operation that's already present
2537
+ else if (task.type === 'destroy') {
2538
+ shouldAppend = false;
2539
+ }
2540
+ }
2541
+ }
2542
+ }
2543
+ else if (newTask.type === 'init') {
2544
+ // eat away final init operation
2545
+ if (q.length) {
2546
+ task = q[q.length - 1]; // last task
2547
+
2548
+ if (
2549
+ task.namespace === newTask.namespace &&
2550
+ task.type === 'init'
2551
+ ) {
2552
+ // our init operation takes precedence
2553
+ q.pop();
2554
+ }
2555
+ }
2556
+ }
2557
+ }
2558
+ }
2559
+
2560
+ if (shouldAppend) {
2561
+ q.push(newTask);
2562
+ }
2563
+
2564
+ return shouldAppend;
2565
+ }
2566
+
2567
+ });
2568
+
2569
+ FC.RenderQueue = RenderQueue;
2570
+
2571
+ ;;
2572
+
2573
+ var EmitterMixin = FC.EmitterMixin = {
2574
+
2575
+ // jQuery-ification via $(this) allows a non-DOM object to have
2576
+ // the same event handling capabilities (including namespaces).
2577
+
2578
+
2579
+ on: function(types, handler) {
2580
+ $(this).on(types, this._prepareIntercept(handler));
2581
+ return this; // for chaining
2582
+ },
2583
+
2584
+
2585
+ one: function(types, handler) {
2586
+ $(this).one(types, this._prepareIntercept(handler));
2587
+ return this; // for chaining
2588
+ },
2589
+
2590
+
2591
+ _prepareIntercept: function(handler) {
2592
+ // handlers are always called with an "event" object as their first param.
2593
+ // sneak the `this` context and arguments into the extra parameter object
2594
+ // and forward them on to the original handler.
2595
+ var intercept = function(ev, extra) {
2596
+ return handler.apply(
2597
+ extra.context || this,
2598
+ extra.args || []
2599
+ );
2600
+ };
2601
+
2602
+ // mimick jQuery's internal "proxy" system (risky, I know)
2603
+ // causing all functions with the same .guid to appear to be the same.
2604
+ // https://github.com/jquery/jquery/blob/2.2.4/src/core.js#L448
2605
+ // this is needed for calling .off with the original non-intercept handler.
2606
+ if (!handler.guid) {
2607
+ handler.guid = $.guid++;
2608
+ }
2609
+ intercept.guid = handler.guid;
2610
+
2611
+ return intercept;
2612
+ },
2613
+
2614
+
2615
+ off: function(types, handler) {
2616
+ $(this).off(types, handler);
2617
+
2618
+ return this; // for chaining
2619
+ },
2620
+
2621
+
2622
+ trigger: function(types) {
2623
+ var args = Array.prototype.slice.call(arguments, 1); // arguments after the first
2624
+
2625
+ // pass in "extra" info to the intercept
2626
+ $(this).triggerHandler(types, { args: args });
2627
+
2628
+ return this; // for chaining
2629
+ },
2630
+
2631
+
2632
+ triggerWith: function(types, context, args) {
2633
+
2634
+ // `triggerHandler` is less reliant on the DOM compared to `trigger`.
2635
+ // pass in "extra" info to the intercept.
2636
+ $(this).triggerHandler(types, { context: context, args: args });
2637
+
2638
+ return this; // for chaining
2639
+ }
2640
+
2641
+ };
2642
+
2643
+ ;;
2644
+
2645
+ /*
2646
+ Utility methods for easily listening to events on another object,
2647
+ and more importantly, easily unlistening from them.
2648
+ */
2649
+ var ListenerMixin = FC.ListenerMixin = (function() {
2650
+ var guid = 0;
2651
+ var ListenerMixin = {
2652
+
2653
+ listenerId: null,
2654
+
2655
+ /*
2656
+ Given an `other` object that has on/off methods, bind the given `callback` to an event by the given name.
2657
+ The `callback` will be called with the `this` context of the object that .listenTo is being called on.
2658
+ Can be called:
2659
+ .listenTo(other, eventName, callback)
2660
+ OR
2661
+ .listenTo(other, {
2662
+ eventName1: callback1,
2663
+ eventName2: callback2
2664
+ })
2196
2665
  */
2197
2666
  listenTo: function(other, arg, callback) {
2198
2667
  if (typeof arg === 'object') { // given dictionary of callbacks
@@ -4734,7 +5203,7 @@ Grid.mixin({
4734
5203
  if (businessHours == null) {
4735
5204
  // fallback
4736
5205
  // access from calendawr. don't access from view. doesn't update with dynamic options.
4737
- businessHours = calendar.options.businessHours;
5206
+ businessHours = calendar.opt('businessHours');
4738
5207
  }
4739
5208
 
4740
5209
  events = calendar.computeBusinessHourEvents(wholeDay, businessHours);
@@ -5802,7 +6271,9 @@ Grid.mixin({
5802
6271
  });
5803
6272
  }
5804
6273
 
5805
- start = range.end;
6274
+ if (range.end > start) {
6275
+ start = range.end;
6276
+ }
5806
6277
  }
5807
6278
 
5808
6279
  // add the span of time after the last event (if there is any)
@@ -8686,7 +9157,7 @@ function isSlotSegCollision(seg1, seg2) {
8686
9157
  /* An abstract class from which other views inherit from
8687
9158
  ----------------------------------------------------------------------------------------------------------------------*/
8688
9159
 
8689
- var View = FC.View = Class.extend(EmitterMixin, ListenerMixin, {
9160
+ var View = FC.View = Model.extend({
8690
9161
 
8691
9162
  type: null, // subclass' view name (string)
8692
9163
  name: null, // deprecated. use `type` instead
@@ -8697,14 +9168,13 @@ var View = FC.View = Class.extend(EmitterMixin, ListenerMixin, {
8697
9168
  options: null, // hash containing all options. already merged with view-specific-options
8698
9169
  el: null, // the view's containing element. set by Calendar
8699
9170
 
8700
- isDateSet: false,
8701
- isDateRendered: false,
8702
- dateRenderQueue: null,
8703
-
8704
- isEventsBound: false,
8705
- isEventsSet: false,
9171
+ renderQueue: null,
9172
+ batchRenderDepth: 0,
9173
+ isDatesRendered: false,
8706
9174
  isEventsRendered: false,
8707
- eventRenderQueue: null,
9175
+ isBaseRendered: false, // related to viewRender/viewDestroy triggers
9176
+
9177
+ queuedScroll: null,
8708
9178
 
8709
9179
  isRTL: false,
8710
9180
  isSelected: false, // boolean whether a range of time is user-selected or not
@@ -8730,6 +9200,7 @@ var View = FC.View = Class.extend(EmitterMixin, ListenerMixin, {
8730
9200
 
8731
9201
 
8732
9202
  constructor: function(calendar, viewSpec) {
9203
+ Model.prototype.constructor.call(this);
8733
9204
 
8734
9205
  this.calendar = calendar;
8735
9206
  this.viewSpec = viewSpec;
@@ -8748,13 +9219,60 @@ var View = FC.View = Class.extend(EmitterMixin, ListenerMixin, {
8748
9219
 
8749
9220
  this.eventOrderSpecs = parseFieldSpecs(this.opt('eventOrder'));
8750
9221
 
8751
- this.dateRenderQueue = new TaskQueue();
8752
- this.eventRenderQueue = new TaskQueue(this.opt('eventRenderWait'));
9222
+ this.renderQueue = this.buildRenderQueue();
9223
+ this.initAutoBatchRender();
8753
9224
 
8754
9225
  this.initialize();
8755
9226
  },
8756
9227
 
8757
9228
 
9229
+ buildRenderQueue: function() {
9230
+ var _this = this;
9231
+ var renderQueue = new RenderQueue({
9232
+ event: this.opt('eventRenderWait')
9233
+ });
9234
+
9235
+ renderQueue.on('start', function() {
9236
+ _this.freezeHeight();
9237
+ _this.addScroll(_this.queryScroll());
9238
+ });
9239
+
9240
+ renderQueue.on('stop', function() {
9241
+ _this.thawHeight();
9242
+ _this.popScroll();
9243
+ });
9244
+
9245
+ return renderQueue;
9246
+ },
9247
+
9248
+
9249
+ initAutoBatchRender: function() {
9250
+ var _this = this;
9251
+
9252
+ this.on('before:change', function() {
9253
+ _this.startBatchRender();
9254
+ });
9255
+
9256
+ this.on('change', function() {
9257
+ _this.stopBatchRender();
9258
+ });
9259
+ },
9260
+
9261
+
9262
+ startBatchRender: function() {
9263
+ if (!(this.batchRenderDepth++)) {
9264
+ this.renderQueue.pause();
9265
+ }
9266
+ },
9267
+
9268
+
9269
+ stopBatchRender: function() {
9270
+ if (!(--this.batchRenderDepth)) {
9271
+ this.renderQueue.resume();
9272
+ }
9273
+ },
9274
+
9275
+
8758
9276
  // A good place for subclasses to initialize member variables
8759
9277
  initialize: function() {
8760
9278
  // subclasses can implement
@@ -8781,29 +9299,6 @@ var View = FC.View = Class.extend(EmitterMixin, ListenerMixin, {
8781
9299
  },
8782
9300
 
8783
9301
 
8784
- // Returns a proxy of the given promise that will be rejected if the given event fires
8785
- // before the promise resolves.
8786
- rejectOn: function(eventName, promise) {
8787
- var _this = this;
8788
-
8789
- return new Promise(function(resolve, reject) {
8790
- _this.one(eventName, reject);
8791
-
8792
- function cleanup() {
8793
- _this.off(eventName, reject);
8794
- }
8795
-
8796
- promise.then(function(res) { // success
8797
- cleanup();
8798
- resolve(res);
8799
- }, function() { // failure
8800
- cleanup();
8801
- reject();
8802
- });
8803
- });
8804
- },
8805
-
8806
-
8807
9302
  /* Title and Date Formatting
8808
9303
  ------------------------------------------------------------------------------------------------------------------*/
8809
9304
 
@@ -8936,6 +9431,7 @@ var View = FC.View = Class.extend(EmitterMixin, ListenerMixin, {
8936
9431
  setElement: function(el) {
8937
9432
  this.el = el;
8938
9433
  this.bindGlobalHandlers();
9434
+ this.bindBaseRenderHandlers();
8939
9435
  this.renderSkeleton();
8940
9436
  },
8941
9437
 
@@ -8947,6 +9443,7 @@ var View = FC.View = Class.extend(EmitterMixin, ListenerMixin, {
8947
9443
  this.unrenderSkeleton();
8948
9444
 
8949
9445
  this.unbindGlobalHandlers();
9446
+ this.unbindBaseRenderHandlers();
8950
9447
 
8951
9448
  this.el.remove();
8952
9449
  // NOTE: don't null-out this.el in case the View was destroyed within an API callback.
@@ -8972,165 +9469,155 @@ var View = FC.View = Class.extend(EmitterMixin, ListenerMixin, {
8972
9469
 
8973
9470
 
8974
9471
  setDate: function(date) {
8975
- var isReset = this.isDateSet;
9472
+ var currentDateProfile = this.get('dateProfile');
9473
+ var newDateProfile = this.buildDateProfile(date, null, true); // forceToValid=true
9474
+
9475
+ if (
9476
+ !currentDateProfile ||
9477
+ !isRangesEqual(currentDateProfile.activeRange, newDateProfile.activeRange)
9478
+ ) {
9479
+ this.set('dateProfile', newDateProfile);
9480
+ }
8976
9481
 
8977
- this.isDateSet = true;
8978
- this.handleRawDate(date);
8979
- this.trigger(isReset ? 'dateReset' : 'dateSet', date);
9482
+ return newDateProfile.date;
8980
9483
  },
8981
9484
 
8982
9485
 
8983
9486
  unsetDate: function() {
8984
- if (this.isDateSet) {
8985
- this.isDateSet = false;
8986
- this.handleDateUnset();
8987
- this.trigger('dateUnset');
8988
- }
9487
+ this.unset('dateProfile');
8989
9488
  },
8990
9489
 
8991
9490
 
8992
- // Date Handling
9491
+ // Date Rendering
8993
9492
  // -----------------------------------------------------------------------------------------------------------------
8994
9493
 
8995
9494
 
8996
- handleRawDate: function(date) {
9495
+ requestDateRender: function(dateProfile) {
8997
9496
  var _this = this;
8998
- var dateProfile = this.buildDateProfile(date, null, true); // forceToValid=true
8999
9497
 
9000
- if (!this.isSameDateProfile(dateProfile)) { // real change
9001
- this.handleDate(dateProfile);
9002
- }
9003
- else {
9004
- // View might have no date change, but still needs to render (because of a view unrender/rerender).
9005
- // Wait for possible queued unrenders. TODO: refactor.
9006
- this.dateRenderQueue.add(function() {
9007
- if (!_this.isDateRendered) {
9008
- _this.handleDate(dateProfile);
9009
- }
9010
- });
9011
- }
9498
+ this.renderQueue.queue(function() {
9499
+ _this.executeDateRender(dateProfile);
9500
+ }, 'date', 'init');
9012
9501
  },
9013
9502
 
9014
9503
 
9015
- handleDate: function(dateProfile) {
9504
+ requestDateUnrender: function() {
9016
9505
  var _this = this;
9017
9506
 
9018
- this.unbindEvents(); // will do nothing if not already bound
9019
- this.requestDateRender(dateProfile).then(function() {
9020
- // wish we could start earlier, but setDateProfile needs to execute first
9021
- _this.bindEvents(); // will request events
9022
- });
9507
+ this.renderQueue.queue(function() {
9508
+ _this.executeDateUnrender();
9509
+ }, 'date', 'destroy');
9023
9510
  },
9024
9511
 
9025
9512
 
9026
- handleDateUnset: function() {
9027
- this.unbindEvents();
9028
- this.requestDateUnrender();
9513
+ // Event Data
9514
+ // -----------------------------------------------------------------------------------------------------------------
9515
+
9516
+
9517
+ fetchInitialEvents: function(dateProfile) {
9518
+ return this.calendar.requestEvents(
9519
+ dateProfile.activeRange.start,
9520
+ dateProfile.activeRange.end
9521
+ );
9029
9522
  },
9030
9523
 
9031
9524
 
9032
- // Date Render Queuing
9033
- // -----------------------------------------------------------------------------------------------------------------
9525
+ bindEventChanges: function() {
9526
+ this.listenTo(this.calendar, 'eventsReset', this.resetEvents);
9527
+ },
9034
9528
 
9035
9529
 
9036
- // if dateProfile not specified, uses current
9037
- requestDateRender: function(dateProfile) {
9038
- var _this = this;
9530
+ unbindEventChanges: function() {
9531
+ this.stopListeningTo(this.calendar, 'eventsReset');
9532
+ },
9039
9533
 
9040
- return this.dateRenderQueue.add(function() {
9041
- return _this.executeDateRender(dateProfile);
9042
- });
9534
+
9535
+ setEvents: function(events) {
9536
+ this.set('currentEvents', events);
9537
+ this.set('hasEvents', true);
9043
9538
  },
9044
9539
 
9045
9540
 
9046
- requestDateUnrender: function() {
9047
- var _this = this;
9541
+ unsetEvents: function() {
9542
+ this.unset('currentEvents');
9543
+ this.unset('hasEvents');
9544
+ },
9048
9545
 
9049
- return this.dateRenderQueue.add(function() {
9050
- return _this.executeDateUnrender();
9051
- });
9546
+
9547
+ resetEvents: function(events) {
9548
+ this.startBatchRender();
9549
+ this.unsetEvents();
9550
+ this.setEvents(events);
9551
+ this.stopBatchRender();
9052
9552
  },
9053
9553
 
9054
9554
 
9055
- // Date High-level Rendering
9555
+ // Event Rendering
9056
9556
  // -----------------------------------------------------------------------------------------------------------------
9057
9557
 
9058
9558
 
9059
- // if dateProfile not specified, uses current
9060
- executeDateRender: function(dateProfile) {
9559
+ requestEventsRender: function(events) {
9061
9560
  var _this = this;
9062
9561
 
9063
- if (dateProfile) {
9064
- _this.setDateProfile(dateProfile);
9065
- }
9562
+ this.renderQueue.queue(function() {
9563
+ _this.executeEventsRender(events);
9564
+ }, 'event', 'init');
9565
+ },
9066
9566
 
9067
- this.updateTitle();
9068
- this.calendar.updateToolbarButtons();
9069
9567
 
9070
- // if rendering a new date, reset scroll to initial state (scrollTime)
9071
- if (dateProfile) {
9072
- this.captureInitialScroll();
9073
- }
9074
- else {
9075
- this.captureScroll(); // a rerender of the current date
9076
- }
9568
+ requestEventsUnrender: function() {
9569
+ var _this = this;
9077
9570
 
9078
- this.freezeHeight();
9571
+ this.renderQueue.queue(function() {
9572
+ _this.executeEventsUnrender();
9573
+ }, 'event', 'destroy');
9574
+ },
9079
9575
 
9080
- // potential issue: date-unrendering will happen with the *new* range
9081
- return this.executeDateUnrender().then(function() {
9082
9576
 
9083
- if (_this.render) {
9084
- _this.render(); // TODO: deprecate
9085
- }
9577
+ // Date High-level Rendering
9578
+ // -----------------------------------------------------------------------------------------------------------------
9086
9579
 
9087
- _this.renderDates();
9088
- _this.updateSize();
9089
- _this.renderBusinessHours(); // might need coordinates, so should go after updateSize()
9090
- _this.startNowIndicator();
9091
9580
 
9092
- _this.thawHeight();
9093
- _this.releaseScroll();
9581
+ // if dateProfile not specified, uses current
9582
+ executeDateRender: function(dateProfile, skipScroll) {
9094
9583
 
9095
- _this.isDateRendered = true;
9096
- _this.onDateRender();
9097
- _this.trigger('dateRender');
9098
- });
9099
- },
9584
+ this.setDateProfileForRendering(dateProfile);
9585
+ this.updateTitle();
9586
+ this.calendar.updateToolbarButtons();
9100
9587
 
9588
+ if (this.render) {
9589
+ this.render(); // TODO: deprecate
9590
+ }
9101
9591
 
9102
- executeDateUnrender: function() {
9103
- var _this = this;
9592
+ this.renderDates();
9593
+ this.updateSize();
9594
+ this.renderBusinessHours(); // might need coordinates, so should go after updateSize()
9595
+ this.startNowIndicator();
9104
9596
 
9105
- if (_this.isDateRendered) {
9106
- return this.requestEventsUnrender().then(function() {
9597
+ if (!skipScroll) {
9598
+ this.addScroll(this.computeInitialDateScroll());
9599
+ }
9107
9600
 
9108
- _this.unselect();
9109
- _this.stopNowIndicator();
9110
- _this.triggerUnrender();
9111
- _this.unrenderBusinessHours();
9112
- _this.unrenderDates();
9601
+ this.isDatesRendered = true;
9602
+ this.trigger('datesRendered');
9603
+ },
9113
9604
 
9114
- if (_this.destroy) {
9115
- _this.destroy(); // TODO: deprecate
9116
- }
9117
9605
 
9118
- _this.isDateRendered = false;
9119
- _this.trigger('dateUnrender');
9120
- });
9121
- }
9122
- else {
9123
- return Promise.resolve();
9124
- }
9125
- },
9606
+ executeDateUnrender: function() {
9126
9607
 
9608
+ this.unselect();
9609
+ this.stopNowIndicator();
9127
9610
 
9128
- // Date Rendering Triggers
9129
- // -----------------------------------------------------------------------------------------------------------------
9611
+ this.trigger('before:datesUnrendered');
9130
9612
 
9613
+ this.unrenderBusinessHours();
9614
+ this.unrenderDates();
9615
+
9616
+ if (this.destroy) {
9617
+ this.destroy(); // TODO: deprecate
9618
+ }
9131
9619
 
9132
- onDateRender: function() {
9133
- this.triggerRender();
9620
+ this.isDatesRendered = false;
9134
9621
  },
9135
9622
 
9136
9623
 
@@ -9150,22 +9637,44 @@ var View = FC.View = Class.extend(EmitterMixin, ListenerMixin, {
9150
9637
  },
9151
9638
 
9152
9639
 
9153
- // Misc view rendering utils
9154
- // -------------------------
9640
+ // Determing when the "meat" of the view is rendered (aka the base)
9641
+ // -----------------------------------------------------------------------------------------------------------------
9642
+
9643
+
9644
+ bindBaseRenderHandlers: function() {
9645
+ var _this = this;
9646
+
9647
+ this.on('datesRendered.baseHandler', function() {
9648
+ _this.onBaseRender();
9649
+ });
9650
+
9651
+ this.on('before:datesUnrendered.baseHandler', function() {
9652
+ _this.onBeforeBaseUnrender();
9653
+ });
9654
+ },
9655
+
9656
+
9657
+ unbindBaseRenderHandlers: function() {
9658
+ this.off('.baseHandler');
9659
+ },
9155
9660
 
9156
9661
 
9157
- // Signals that the view's content has been rendered
9158
- triggerRender: function() {
9662
+ onBaseRender: function() {
9663
+ this.applyScreenState();
9159
9664
  this.publiclyTrigger('viewRender', this, this, this.el);
9160
9665
  },
9161
9666
 
9162
9667
 
9163
- // Signals that the view's content is about to be unrendered
9164
- triggerUnrender: function() {
9668
+ onBeforeBaseUnrender: function() {
9669
+ this.applyScreenState();
9165
9670
  this.publiclyTrigger('viewDestroy', this, this, this.el);
9166
9671
  },
9167
9672
 
9168
9673
 
9674
+ // Misc view rendering utils
9675
+ // -----------------------------------------------------------------------------------------------------------------
9676
+
9677
+
9169
9678
  // Binds DOM handlers to elements that reside outside the view container, such as the document
9170
9679
  bindGlobalHandlers: function() {
9171
9680
  this.listenTo(GlobalEmitter.get(), {
@@ -9301,9 +9810,10 @@ var View = FC.View = Class.extend(EmitterMixin, ListenerMixin, {
9301
9810
 
9302
9811
  // Refreshes anything dependant upon sizing of the container element of the grid
9303
9812
  updateSize: function(isResize) {
9813
+ var scroll;
9304
9814
 
9305
9815
  if (isResize) {
9306
- this.captureScroll();
9816
+ scroll = this.queryScroll();
9307
9817
  }
9308
9818
 
9309
9819
  this.updateHeight(isResize);
@@ -9311,7 +9821,7 @@ var View = FC.View = Class.extend(EmitterMixin, ListenerMixin, {
9311
9821
  this.updateNowIndicator();
9312
9822
 
9313
9823
  if (isResize) {
9314
- this.releaseScroll();
9824
+ this.applyScroll(scroll);
9315
9825
  }
9316
9826
  },
9317
9827
 
@@ -9344,90 +9854,65 @@ var View = FC.View = Class.extend(EmitterMixin, ListenerMixin, {
9344
9854
  ------------------------------------------------------------------------------------------------------------------*/
9345
9855
 
9346
9856
 
9347
- capturedScroll: null,
9348
- capturedScrollDepth: 0,
9349
-
9350
-
9351
- captureScroll: function() {
9352
- if (!(this.capturedScrollDepth++)) {
9353
- this.capturedScroll = this.isDateRendered ? this.queryScroll() : {}; // require a render first
9354
- return true; // root?
9355
- }
9356
- return false;
9857
+ addForcedScroll: function(scroll) {
9858
+ this.addScroll(
9859
+ $.extend(scroll, { isForced: true })
9860
+ );
9357
9861
  },
9358
9862
 
9359
9863
 
9360
- captureInitialScroll: function(forcedScroll) {
9361
- if (this.captureScroll()) { // root?
9362
- this.capturedScroll.isInitial = true;
9864
+ addScroll: function(scroll) {
9865
+ var queuedScroll = this.queuedScroll || (this.queuedScroll = {});
9363
9866
 
9364
- if (forcedScroll) {
9365
- $.extend(this.capturedScroll, forcedScroll);
9366
- }
9367
- else {
9368
- this.capturedScroll.isComputed = true;
9369
- }
9867
+ if (!queuedScroll.isForced) {
9868
+ $.extend(queuedScroll, scroll);
9370
9869
  }
9371
9870
  },
9372
9871
 
9373
9872
 
9374
- releaseScroll: function() {
9375
- var scroll = this.capturedScroll;
9376
- var isRoot = this.discardScroll();
9377
-
9378
- if (scroll.isComputed) {
9379
- if (isRoot) {
9380
- // only compute initial scroll if it will actually be used (is the root capture)
9381
- $.extend(scroll, this.computeInitialScroll());
9382
- }
9383
- else {
9384
- scroll = null; // scroll couldn't be computed. don't apply it to the DOM
9385
- }
9386
- }
9873
+ popScroll: function() {
9874
+ this.applyQueuedScroll();
9875
+ this.queuedScroll = null;
9876
+ },
9387
9877
 
9388
- if (scroll) {
9389
- // we act immediately on a releaseScroll operation, as opposed to captureScroll.
9390
- // if capture/release wraps a render operation that screws up the scroll,
9391
- // we still want to restore it a good state after, regardless of depth.
9392
9878
 
9393
- if (scroll.isInitial) {
9394
- this.hardSetScroll(scroll); // outsmart how browsers set scroll on initial DOM
9395
- }
9396
- else {
9397
- this.setScroll(scroll);
9398
- }
9879
+ applyQueuedScroll: function() {
9880
+ if (this.queuedScroll) {
9881
+ this.applyScroll(this.queuedScroll);
9399
9882
  }
9400
9883
  },
9401
9884
 
9402
9885
 
9403
- discardScroll: function() {
9404
- if (!(--this.capturedScrollDepth)) {
9405
- this.capturedScroll = null;
9406
- return true; // root?
9886
+ queryScroll: function() {
9887
+ var scroll = {};
9888
+
9889
+ if (this.isDatesRendered) {
9890
+ $.extend(scroll, this.queryDateScroll());
9407
9891
  }
9408
- return false;
9892
+
9893
+ return scroll;
9409
9894
  },
9410
9895
 
9411
9896
 
9412
- computeInitialScroll: function() {
9413
- return {};
9897
+ applyScroll: function(scroll) {
9898
+ if (this.isDatesRendered) {
9899
+ this.applyDateScroll(scroll);
9900
+ }
9414
9901
  },
9415
9902
 
9416
9903
 
9417
- queryScroll: function() {
9418
- return {};
9904
+ computeInitialDateScroll: function() {
9905
+ return {}; // subclasses must implement
9419
9906
  },
9420
9907
 
9421
9908
 
9422
- hardSetScroll: function(scroll) {
9423
- var _this = this;
9424
- var exec = function() { _this.setScroll(scroll); };
9425
- exec();
9426
- setTimeout(exec, 0); // to surely clear the browser's initial scroll for the DOM
9909
+ queryDateScroll: function() {
9910
+ return {}; // subclasses must implement
9427
9911
  },
9428
9912
 
9429
9913
 
9430
- setScroll: function(scroll) {
9914
+ applyDateScroll: function(scroll) {
9915
+ ; // subclasses must implement
9431
9916
  },
9432
9917
 
9433
9918
 
@@ -9445,165 +9930,27 @@ var View = FC.View = Class.extend(EmitterMixin, ListenerMixin, {
9445
9930
  },
9446
9931
 
9447
9932
 
9448
- // Event Binding/Unbinding
9449
- // -----------------------------------------------------------------------------------------------------------------
9450
-
9451
-
9452
- bindEvents: function() {
9453
- var _this = this;
9454
-
9455
- if (!this.isEventsBound) {
9456
- this.isEventsBound = true;
9457
- this.rejectOn('eventsUnbind', this.requestEvents()).then(function(events) { // TODO: test rejection
9458
- _this.listenTo(_this.calendar, 'eventsReset', _this.setEvents);
9459
- _this.setEvents(events);
9460
- });
9461
- }
9462
- },
9463
-
9464
-
9465
- unbindEvents: function() {
9466
- if (this.isEventsBound) {
9467
- this.isEventsBound = false;
9468
- this.stopListeningTo(this.calendar, 'eventsReset');
9469
- this.unsetEvents();
9470
- this.trigger('eventsUnbind');
9471
- }
9472
- },
9473
-
9474
-
9475
- // Event Setting/Unsetting
9476
- // -----------------------------------------------------------------------------------------------------------------
9477
-
9478
-
9479
- setEvents: function(events) {
9480
- var isReset = this.isEventSet;
9481
-
9482
- this.isEventsSet = true;
9483
- this.handleEvents(events, isReset);
9484
- this.trigger(isReset ? 'eventsReset' : 'eventsSet', events);
9485
- },
9486
-
9487
-
9488
- unsetEvents: function() {
9489
- if (this.isEventsSet) {
9490
- this.isEventsSet = false;
9491
- this.handleEventsUnset();
9492
- this.trigger('eventsUnset');
9493
- }
9494
- },
9495
-
9496
-
9497
- whenEventsSet: function() {
9498
- var _this = this;
9499
-
9500
- if (this.isEventsSet) {
9501
- return Promise.resolve(this.getCurrentEvents());
9502
- }
9503
- else {
9504
- return new Promise(function(resolve) {
9505
- _this.one('eventsSet', resolve);
9506
- });
9507
- }
9508
- },
9509
-
9510
-
9511
- // Event Handling
9512
- // -----------------------------------------------------------------------------------------------------------------
9513
-
9514
-
9515
- handleEvents: function(events, isReset) {
9516
- this.requestEventsRender(events);
9517
- },
9518
-
9519
-
9520
- handleEventsUnset: function() {
9521
- this.requestEventsUnrender();
9522
- },
9523
-
9524
-
9525
- // Event Render Queuing
9526
- // -----------------------------------------------------------------------------------------------------------------
9527
-
9528
-
9529
- // assumes any previous event renders have been cleared already
9530
- requestEventsRender: function(events) {
9531
- var _this = this;
9532
-
9533
- return this.eventRenderQueue.add(function() { // might not return a promise if debounced!? bad
9534
- return _this.executeEventsRender(events);
9535
- });
9536
- },
9537
-
9538
-
9539
- requestEventsUnrender: function() {
9540
- var _this = this;
9541
-
9542
- if (this.isEventsRendered) {
9543
- return this.eventRenderQueue.addQuickly(function() {
9544
- return _this.executeEventsUnrender();
9545
- });
9546
- }
9547
- else {
9548
- return Promise.resolve();
9549
- }
9550
- },
9551
-
9552
-
9553
- requestCurrentEventsRender: function() {
9554
- if (this.isEventsSet) {
9555
- this.requestEventsRender(this.getCurrentEvents());
9556
- }
9557
- else {
9558
- return Promise.reject();
9559
- }
9560
- },
9561
-
9562
-
9563
9933
  // Event High-level Rendering
9564
9934
  // -----------------------------------------------------------------------------------------------------------------
9565
9935
 
9566
9936
 
9567
9937
  executeEventsRender: function(events) {
9568
- var _this = this;
9569
-
9570
- this.captureScroll();
9571
- this.freezeHeight();
9938
+ this.renderEvents(events);
9939
+ this.isEventsRendered = true;
9572
9940
 
9573
- return this.executeEventsUnrender().then(function() {
9574
- _this.renderEvents(events);
9575
-
9576
- _this.thawHeight();
9577
- _this.releaseScroll();
9578
-
9579
- _this.isEventsRendered = true;
9580
- _this.onEventsRender();
9581
- _this.trigger('eventsRender');
9582
- });
9941
+ this.onEventsRender();
9583
9942
  },
9584
9943
 
9585
9944
 
9586
9945
  executeEventsUnrender: function() {
9587
- if (this.isEventsRendered) {
9588
- this.onBeforeEventsUnrender();
9946
+ this.onBeforeEventsUnrender();
9589
9947
 
9590
- this.captureScroll();
9591
- this.freezeHeight();
9592
-
9593
- if (this.destroyEvents) {
9594
- this.destroyEvents(); // TODO: deprecate
9595
- }
9596
-
9597
- this.unrenderEvents();
9598
-
9599
- this.thawHeight();
9600
- this.releaseScroll();
9601
-
9602
- this.isEventsRendered = false;
9603
- this.trigger('eventsUnrender');
9948
+ if (this.destroyEvents) {
9949
+ this.destroyEvents(); // TODO: deprecate
9604
9950
  }
9605
9951
 
9606
- return Promise.resolve(); // always synchronous
9952
+ this.unrenderEvents();
9953
+ this.isEventsRendered = false;
9607
9954
  },
9608
9955
 
9609
9956
 
@@ -9613,6 +9960,8 @@ var View = FC.View = Class.extend(EmitterMixin, ListenerMixin, {
9613
9960
 
9614
9961
  // Signals that all events have been rendered
9615
9962
  onEventsRender: function() {
9963
+ this.applyScreenState();
9964
+
9616
9965
  this.renderedEventSegEach(function(seg) {
9617
9966
  this.publiclyTrigger('eventAfterRender', seg.event, seg.event, seg.el);
9618
9967
  });
@@ -9622,12 +9971,21 @@ var View = FC.View = Class.extend(EmitterMixin, ListenerMixin, {
9622
9971
 
9623
9972
  // Signals that all event elements are about to be removed
9624
9973
  onBeforeEventsUnrender: function() {
9974
+ this.applyScreenState();
9975
+
9625
9976
  this.renderedEventSegEach(function(seg) {
9626
9977
  this.publiclyTrigger('eventDestroy', seg.event, seg.event, seg.el);
9627
9978
  });
9628
9979
  },
9629
9980
 
9630
9981
 
9982
+ applyScreenState: function() {
9983
+ this.thawHeight();
9984
+ this.freezeHeight();
9985
+ this.applyQueuedScroll();
9986
+ },
9987
+
9988
+
9631
9989
  // Event Low-level Rendering
9632
9990
  // -----------------------------------------------------------------------------------------------------------------
9633
9991
 
@@ -9644,23 +10002,6 @@ var View = FC.View = Class.extend(EmitterMixin, ListenerMixin, {
9644
10002
  },
9645
10003
 
9646
10004
 
9647
- // Event Data Access
9648
- // -----------------------------------------------------------------------------------------------------------------
9649
-
9650
-
9651
- requestEvents: function() {
9652
- return this.calendar.requestEvents(
9653
- this.activeRange.start,
9654
- this.activeRange.end
9655
- );
9656
- },
9657
-
9658
-
9659
- getCurrentEvents: function() {
9660
- return this.calendar.getPrunedEventCache();
9661
- },
9662
-
9663
-
9664
10005
  // Event Rendering Utils
9665
10006
  // -----------------------------------------------------------------------------------------------------------------
9666
10007
 
@@ -10065,6 +10406,34 @@ var View = FC.View = Class.extend(EmitterMixin, ListenerMixin, {
10065
10406
 
10066
10407
  });
10067
10408
 
10409
+
10410
+ View.watch('displayingDates', [ 'dateProfile' ], function(deps) {
10411
+ this.requestDateRender(deps.dateProfile);
10412
+ }, function() {
10413
+ this.requestDateUnrender();
10414
+ });
10415
+
10416
+
10417
+ View.watch('initialEvents', [ 'dateProfile' ], function(deps) {
10418
+ return this.fetchInitialEvents(deps.dateProfile);
10419
+ });
10420
+
10421
+
10422
+ View.watch('bindingEvents', [ 'initialEvents' ], function(deps) {
10423
+ this.setEvents(deps.initialEvents);
10424
+ this.bindEventChanges();
10425
+ }, function() {
10426
+ this.unbindEventChanges();
10427
+ this.unsetEvents();
10428
+ });
10429
+
10430
+
10431
+ View.watch('displayingEvents', [ 'displayingDates', 'hasEvents' ], function() {
10432
+ this.requestEventsRender(this.get('currentEvents')); // if there were event mutations after initialEvents
10433
+ }, function() {
10434
+ this.requestEventsUnrender();
10435
+ });
10436
+
10068
10437
  ;;
10069
10438
 
10070
10439
  View.mixin({
@@ -10088,10 +10457,6 @@ View.mixin({
10088
10457
  // how far the current date will move for a prev/next operation
10089
10458
  dateIncrement: null,
10090
10459
 
10091
- // stores the *calendar's* current date after setDate
10092
- // TODO: entirely Calendar's responsibility
10093
- currentDate: null,
10094
-
10095
10460
  minTime: null, // Duration object that denotes the first visible time of any given day
10096
10461
  maxTime: null, // Duration object that denotes the exclusive visible end time of any given day
10097
10462
  usesMinMaxTime: false, // whether minTime/maxTime will affect the activeRange. Views must opt-in.
@@ -10107,19 +10472,13 @@ View.mixin({
10107
10472
  ------------------------------------------------------------------------------------------------------------------*/
10108
10473
 
10109
10474
 
10110
- isSameDateProfile: function(dateProfile) {
10111
- return this.activeRange && isRangesEqual(this.activeRange, dateProfile.activeRange);
10112
- },
10113
-
10114
-
10115
- setDateProfile: function(dateProfile) {
10475
+ setDateProfileForRendering: function(dateProfile) {
10116
10476
  this.currentRange = dateProfile.currentRange;
10117
10477
  this.currentRangeUnit = dateProfile.currentRangeUnit;
10118
10478
  this.renderRange = dateProfile.renderRange;
10119
10479
  this.activeRange = dateProfile.activeRange;
10120
10480
  this.validRange = dateProfile.validRange;
10121
10481
  this.dateIncrement = dateProfile.dateIncrement;
10122
- this.currentDate = dateProfile.date;
10123
10482
  this.minTime = dateProfile.minTime;
10124
10483
  this.maxTime = dateProfile.maxTime;
10125
10484
 
@@ -10675,7 +11034,7 @@ function Toolbar(calendar, toolbarOptions) {
10675
11034
  function render() {
10676
11035
  var sections = toolbarOptions.layout;
10677
11036
 
10678
- tm = calendar.options.theme ? 'ui' : 'fc';
11037
+ tm = calendar.opt('theme') ? 'ui' : 'fc';
10679
11038
 
10680
11039
  if (sections) {
10681
11040
  if (!el) {
@@ -10706,6 +11065,8 @@ function Toolbar(calendar, toolbarOptions) {
10706
11065
  function renderSection(position) {
10707
11066
  var sectionEl = $('<div class="fc-' + position + '"/>');
10708
11067
  var buttonStr = toolbarOptions.layout[position];
11068
+ var calendarCustomButtons = calendar.opt('customButtons') || {};
11069
+ var calendarButtonText = calendar.opt('buttonText') || {};
10709
11070
 
10710
11071
  if (buttonStr) {
10711
11072
  $.each(buttonStr.split(' '), function(i) {
@@ -10730,7 +11091,7 @@ function Toolbar(calendar, toolbarOptions) {
10730
11091
  isOnlyButtons = false;
10731
11092
  }
10732
11093
  else {
10733
- if ((customButtonProps = (calendar.options.customButtons || {})[buttonName])) {
11094
+ if ((customButtonProps = calendarCustomButtons[buttonName])) {
10734
11095
  buttonClick = function(ev) {
10735
11096
  if (customButtonProps.click) {
10736
11097
  customButtonProps.click.call(button[0], ev);
@@ -10752,7 +11113,7 @@ function Toolbar(calendar, toolbarOptions) {
10752
11113
  calendar[buttonName]();
10753
11114
  };
10754
11115
  overrideText = (calendar.overrides.buttonText || {})[buttonName];
10755
- defaultText = calendar.options.buttonText[buttonName]; // everything else is considered default
11116
+ defaultText = calendarButtonText[buttonName]; // everything else is considered default
10756
11117
  }
10757
11118
 
10758
11119
  if (buttonClick) {
@@ -10760,20 +11121,20 @@ function Toolbar(calendar, toolbarOptions) {
10760
11121
  themeIcon =
10761
11122
  customButtonProps ?
10762
11123
  customButtonProps.themeIcon :
10763
- calendar.options.themeButtonIcons[buttonName];
11124
+ calendar.opt('themeButtonIcons')[buttonName];
10764
11125
 
10765
11126
  normalIcon =
10766
11127
  customButtonProps ?
10767
11128
  customButtonProps.icon :
10768
- calendar.options.buttonIcons[buttonName];
11129
+ calendar.opt('buttonIcons')[buttonName];
10769
11130
 
10770
11131
  if (overrideText) {
10771
11132
  innerHtml = htmlEscape(overrideText);
10772
11133
  }
10773
- else if (themeIcon && calendar.options.theme) {
11134
+ else if (themeIcon && calendar.opt('theme')) {
10774
11135
  innerHtml = "<span class='ui-icon ui-icon-" + themeIcon + "'></span>";
10775
11136
  }
10776
- else if (normalIcon && !calendar.options.theme) {
11137
+ else if (normalIcon && !calendar.opt('theme')) {
10777
11138
  innerHtml = "<span class='fc-icon fc-icon-" + normalIcon + "'></span>";
10778
11139
  }
10779
11140
  else {
@@ -10914,24 +11275,31 @@ function Toolbar(calendar, toolbarOptions) {
10914
11275
 
10915
11276
  ;;
10916
11277
 
10917
- var Calendar = FC.Calendar = Class.extend({
11278
+ var Calendar = FC.Calendar = Class.extend(EmitterMixin, {
10918
11279
 
10919
- dirDefaults: null, // option defaults related to LTR or RTL
10920
- localeDefaults: null, // option defaults related to current locale
10921
- overrides: null, // option overrides given to the fullCalendar constructor
10922
- dynamicOverrides: null, // options set with dynamic setter method. higher precedence than view overrides.
10923
- options: null, // all defaults combined with overrides
10924
- viewSpecCache: null, // cache of view definitions
10925
11280
  view: null, // current View object
11281
+ viewsByType: null, // holds all instantiated view instances, current or not
10926
11282
  currentDate: null, // unzoned moment. private (public API should use getDate instead)
10927
- header: null,
10928
- footer: null,
10929
11283
  loadingLevel: 0, // number of simultaneous loading tasks
10930
11284
 
10931
11285
 
10932
- // a lot of this class' OOP logic is scoped within this constructor function,
10933
- // but in the future, write individual methods on the prototype.
10934
- constructor: Calendar_constructor,
11286
+ constructor: function(el, overrides) {
11287
+
11288
+ // declare the current calendar instance relies on GlobalEmitter. needed for garbage collection.
11289
+ // unneeded() is called in destroy.
11290
+ GlobalEmitter.needed();
11291
+
11292
+ this.el = el;
11293
+ this.viewsByType = {};
11294
+ this.viewSpecCache = {};
11295
+
11296
+ this.initOptionsInternals(overrides);
11297
+ this.initMomentInternals(); // needs to happen after options hash initialized
11298
+ this.initCurrentDate();
11299
+
11300
+ EventManager.call(this); // needs options immediately
11301
+ this.initialize();
11302
+ },
10935
11303
 
10936
11304
 
10937
11305
  // Subclasses can override this for initialization logic after the constructor has been called
@@ -10939,199 +11307,35 @@ var Calendar = FC.Calendar = Class.extend({
10939
11307
  },
10940
11308
 
10941
11309
 
10942
- // Computes the flattened options hash for the calendar and assigns to `this.options`.
10943
- // Assumes this.overrides and this.dynamicOverrides have already been initialized.
10944
- populateOptionsHash: function() {
10945
- var locale, localeDefaults;
10946
- var isRTL, dirDefaults;
11310
+ // Public API
11311
+ // -----------------------------------------------------------------------------------------------------------------
10947
11312
 
10948
- locale = firstDefined( // explicit locale option given?
10949
- this.dynamicOverrides.locale,
10950
- this.overrides.locale
10951
- );
10952
- localeDefaults = localeOptionHash[locale];
10953
- if (!localeDefaults) { // explicit locale option not given or invalid?
10954
- locale = Calendar.defaults.locale;
10955
- localeDefaults = localeOptionHash[locale] || {};
10956
- }
10957
11313
 
10958
- isRTL = firstDefined( // based on options computed so far, is direction RTL?
10959
- this.dynamicOverrides.isRTL,
10960
- this.overrides.isRTL,
10961
- localeDefaults.isRTL,
10962
- Calendar.defaults.isRTL
10963
- );
10964
- dirDefaults = isRTL ? Calendar.rtlDefaults : {};
11314
+ getCalendar: function() {
11315
+ return this;
11316
+ },
10965
11317
 
10966
- this.dirDefaults = dirDefaults;
10967
- this.localeDefaults = localeDefaults;
10968
- this.options = mergeOptions([ // merge defaults and overrides. lowest to highest precedence
10969
- Calendar.defaults, // global defaults
10970
- dirDefaults,
10971
- localeDefaults,
10972
- this.overrides,
10973
- this.dynamicOverrides
10974
- ]);
10975
- populateInstanceComputableOptions(this.options); // fill in gaps with computed options
11318
+
11319
+ getView: function() {
11320
+ return this.view;
10976
11321
  },
10977
11322
 
10978
11323
 
10979
- // Gets information about how to create a view. Will use a cache.
10980
- getViewSpec: function(viewType) {
10981
- var cache = this.viewSpecCache;
11324
+ publiclyTrigger: function(name, thisObj) {
11325
+ var args = Array.prototype.slice.call(arguments, 2);
11326
+ var optHandler = this.opt(name);
10982
11327
 
10983
- return cache[viewType] || (cache[viewType] = this.buildViewSpec(viewType));
11328
+ thisObj = thisObj || this.el[0];
11329
+ this.triggerWith(name, thisObj, args); // Emitter's method
11330
+
11331
+ if (optHandler) {
11332
+ return optHandler.apply(thisObj, args);
11333
+ }
10984
11334
  },
10985
11335
 
10986
11336
 
10987
- // Given a duration singular unit, like "week" or "day", finds a matching view spec.
10988
- // Preference is given to views that have corresponding buttons.
10989
- getUnitViewSpec: function(unit) {
10990
- var viewTypes;
10991
- var i;
10992
- var spec;
10993
-
10994
- if ($.inArray(unit, unitsDesc) != -1) {
10995
-
10996
- // put views that have buttons first. there will be duplicates, but oh well
10997
- viewTypes = this.header.getViewsWithButtons(); // TODO: include footer as well?
10998
- $.each(FC.views, function(viewType) { // all views
10999
- viewTypes.push(viewType);
11000
- });
11001
-
11002
- for (i = 0; i < viewTypes.length; i++) {
11003
- spec = this.getViewSpec(viewTypes[i]);
11004
- if (spec) {
11005
- if (spec.singleUnit == unit) {
11006
- return spec;
11007
- }
11008
- }
11009
- }
11010
- }
11011
- },
11012
-
11013
-
11014
- // Builds an object with information on how to create a given view
11015
- buildViewSpec: function(requestedViewType) {
11016
- var viewOverrides = this.overrides.views || {};
11017
- var specChain = []; // for the view. lowest to highest priority
11018
- var defaultsChain = []; // for the view. lowest to highest priority
11019
- var overridesChain = []; // for the view. lowest to highest priority
11020
- var viewType = requestedViewType;
11021
- var spec; // for the view
11022
- var overrides; // for the view
11023
- var durationInput;
11024
- var duration;
11025
- var unit;
11026
-
11027
- // iterate from the specific view definition to a more general one until we hit an actual View class
11028
- while (viewType) {
11029
- spec = fcViews[viewType];
11030
- overrides = viewOverrides[viewType];
11031
- viewType = null; // clear. might repopulate for another iteration
11032
-
11033
- if (typeof spec === 'function') { // TODO: deprecate
11034
- spec = { 'class': spec };
11035
- }
11036
-
11037
- if (spec) {
11038
- specChain.unshift(spec);
11039
- defaultsChain.unshift(spec.defaults || {});
11040
- durationInput = durationInput || spec.duration;
11041
- viewType = viewType || spec.type;
11042
- }
11043
-
11044
- if (overrides) {
11045
- overridesChain.unshift(overrides); // view-specific option hashes have options at zero-level
11046
- durationInput = durationInput || overrides.duration;
11047
- viewType = viewType || overrides.type;
11048
- }
11049
- }
11050
-
11051
- spec = mergeProps(specChain);
11052
- spec.type = requestedViewType;
11053
- if (!spec['class']) {
11054
- return false;
11055
- }
11056
-
11057
- // fall back to top-level `duration` option
11058
- durationInput = durationInput ||
11059
- this.dynamicOverrides.duration ||
11060
- this.overrides.duration;
11061
-
11062
- if (durationInput) {
11063
- duration = moment.duration(durationInput);
11064
-
11065
- if (duration.valueOf()) { // valid?
11066
-
11067
- unit = computeDurationGreatestUnit(duration, durationInput);
11068
-
11069
- spec.duration = duration;
11070
- spec.durationUnit = unit;
11071
-
11072
- // view is a single-unit duration, like "week" or "day"
11073
- // incorporate options for this. lowest priority
11074
- if (duration.as(unit) === 1) {
11075
- spec.singleUnit = unit;
11076
- overridesChain.unshift(viewOverrides[unit] || {});
11077
- }
11078
- }
11079
- }
11080
-
11081
- spec.defaults = mergeOptions(defaultsChain);
11082
- spec.overrides = mergeOptions(overridesChain);
11083
-
11084
- this.buildViewSpecOptions(spec);
11085
- this.buildViewSpecButtonText(spec, requestedViewType);
11086
-
11087
- return spec;
11088
- },
11089
-
11090
-
11091
- // Builds and assigns a view spec's options object from its already-assigned defaults and overrides
11092
- buildViewSpecOptions: function(spec) {
11093
- spec.options = mergeOptions([ // lowest to highest priority
11094
- Calendar.defaults, // global defaults
11095
- spec.defaults, // view's defaults (from ViewSubclass.defaults)
11096
- this.dirDefaults,
11097
- this.localeDefaults, // locale and dir take precedence over view's defaults!
11098
- this.overrides, // calendar's overrides (options given to constructor)
11099
- spec.overrides, // view's overrides (view-specific options)
11100
- this.dynamicOverrides // dynamically set via setter. highest precedence
11101
- ]);
11102
- populateInstanceComputableOptions(spec.options);
11103
- },
11104
-
11105
-
11106
- // Computes and assigns a view spec's buttonText-related options
11107
- buildViewSpecButtonText: function(spec, requestedViewType) {
11108
-
11109
- // given an options object with a possible `buttonText` hash, lookup the buttonText for the
11110
- // requested view, falling back to a generic unit entry like "week" or "day"
11111
- function queryButtonText(options) {
11112
- var buttonText = options.buttonText || {};
11113
- return buttonText[requestedViewType] ||
11114
- // view can decide to look up a certain key
11115
- (spec.buttonTextKey ? buttonText[spec.buttonTextKey] : null) ||
11116
- // a key like "month"
11117
- (spec.singleUnit ? buttonText[spec.singleUnit] : null);
11118
- }
11119
-
11120
- // highest to lowest priority
11121
- spec.buttonTextOverride =
11122
- queryButtonText(this.dynamicOverrides) ||
11123
- queryButtonText(this.overrides) || // constructor-specified buttonText lookup hash takes precedence
11124
- spec.overrides.buttonText; // `buttonText` for view-specific options is a string
11125
-
11126
- // highest to lowest priority. mirrors buildViewSpecOptions
11127
- spec.buttonTextDefault =
11128
- queryButtonText(this.localeDefaults) ||
11129
- queryButtonText(this.dirDefaults) ||
11130
- spec.defaults.buttonText || // a single string. from ViewSubclass.defaults
11131
- queryButtonText(Calendar.defaults) ||
11132
- (spec.duration ? this.humanizeDuration(spec.duration) : null) || // like "3 days"
11133
- requestedViewType; // fall back to given view name
11134
- },
11337
+ // View
11338
+ // -----------------------------------------------------------------------------------------------------------------
11135
11339
 
11136
11340
 
11137
11341
  // Given a view name for a custom view or a standard view, creates a ready-to-go View object
@@ -11148,52 +11352,47 @@ var Calendar = FC.Calendar = Class.extend({
11148
11352
  },
11149
11353
 
11150
11354
 
11151
- // Should be called when any type of async data fetching begins
11152
- pushLoading: function() {
11153
- if (!(this.loadingLevel++)) {
11154
- this.publiclyTrigger('loading', null, true, this.view);
11155
- }
11156
- },
11355
+ changeView: function(viewName, dateOrRange) {
11157
11356
 
11357
+ if (dateOrRange) {
11158
11358
 
11159
- // Should be called when any type of async data fetching completes
11160
- popLoading: function() {
11161
- if (!(--this.loadingLevel)) {
11162
- this.publiclyTrigger('loading', null, false, this.view);
11359
+ if (dateOrRange.start && dateOrRange.end) { // a range
11360
+ this.recordOptionOverrides({ // will not rerender
11361
+ visibleRange: dateOrRange
11362
+ });
11363
+ }
11364
+ else { // a date
11365
+ this.currentDate = this.moment(dateOrRange).stripZone(); // just like gotoDate
11366
+ }
11163
11367
  }
11368
+
11369
+ this.renderView(viewName);
11164
11370
  },
11165
11371
 
11166
11372
 
11167
- // Given arguments to the select method in the API, returns a span (unzoned start/end and other info)
11168
- buildSelectSpan: function(zonedStartInput, zonedEndInput) {
11169
- var start = this.moment(zonedStartInput).stripZone();
11170
- var end;
11373
+ // Forces navigation to a view for the given date.
11374
+ // `viewType` can be a specific view name or a generic one like "week" or "day".
11375
+ zoomTo: function(newDate, viewType) {
11376
+ var spec;
11171
11377
 
11172
- if (zonedEndInput) {
11173
- end = this.moment(zonedEndInput).stripZone();
11174
- }
11175
- else if (start.hasTime()) {
11176
- end = start.clone().add(this.defaultTimedEventDuration);
11177
- }
11178
- else {
11179
- end = start.clone().add(this.defaultAllDayEventDuration);
11180
- }
11378
+ viewType = viewType || 'day'; // day is default zoom
11379
+ spec = this.getViewSpec(viewType) || this.getUnitViewSpec(viewType);
11181
11380
 
11182
- return { start: start, end: end };
11381
+ this.currentDate = newDate.clone();
11382
+ this.renderView(spec ? spec.type : null);
11183
11383
  },
11184
11384
 
11185
11385
 
11186
11386
  // Current Date
11187
- // ------------
11387
+ // -----------------------------------------------------------------------------------------------------------------
11188
11388
 
11189
11389
 
11190
- /*
11191
- Called before initialize()
11192
- */
11193
11390
  initCurrentDate: function() {
11391
+ var defaultDateInput = this.opt('defaultDate');
11392
+
11194
11393
  // compute the initial ambig-timezone date
11195
- if (this.options.defaultDate != null) {
11196
- this.currentDate = this.moment(this.options.defaultDate).stripZone();
11394
+ if (defaultDateInput != null) {
11395
+ this.currentDate = this.moment(defaultDateInput).stripZone();
11197
11396
  }
11198
11397
  else {
11199
11398
  this.currentDate = this.getNow(); // getNow already returns unzoned
@@ -11201,24 +11400,6 @@ var Calendar = FC.Calendar = Class.extend({
11201
11400
  },
11202
11401
 
11203
11402
 
11204
- changeView: function(viewName, dateOrRange) {
11205
-
11206
- if (dateOrRange) {
11207
-
11208
- if (dateOrRange.start && dateOrRange.end) { // a range
11209
- this.recordOptionOverrides({ // will not rerender
11210
- visibleRange: dateOrRange
11211
- });
11212
- }
11213
- else { // a date
11214
- this.currentDate = this.moment(dateOrRange).stripZone(); // just like gotoDate
11215
- }
11216
- }
11217
-
11218
- this.renderView(viewName);
11219
- },
11220
-
11221
-
11222
11403
  prev: function() {
11223
11404
  var prevInfo = this.view.buildPrevDateProfile(this.currentDate);
11224
11405
 
@@ -11275,6 +11456,68 @@ var Calendar = FC.Calendar = Class.extend({
11275
11456
  },
11276
11457
 
11277
11458
 
11459
+ // Loading Triggering
11460
+ // -----------------------------------------------------------------------------------------------------------------
11461
+
11462
+
11463
+ // Should be called when any type of async data fetching begins
11464
+ pushLoading: function() {
11465
+ if (!(this.loadingLevel++)) {
11466
+ this.publiclyTrigger('loading', null, true, this.view);
11467
+ }
11468
+ },
11469
+
11470
+
11471
+ // Should be called when any type of async data fetching completes
11472
+ popLoading: function() {
11473
+ if (!(--this.loadingLevel)) {
11474
+ this.publiclyTrigger('loading', null, false, this.view);
11475
+ }
11476
+ },
11477
+
11478
+
11479
+ // Selection
11480
+ // -----------------------------------------------------------------------------------------------------------------
11481
+
11482
+
11483
+ // this public method receives start/end dates in any format, with any timezone
11484
+ select: function(zonedStartInput, zonedEndInput) {
11485
+ this.view.select(
11486
+ this.buildSelectSpan.apply(this, arguments)
11487
+ );
11488
+ },
11489
+
11490
+
11491
+ unselect: function() { // safe to be called before renderView
11492
+ if (this.view) {
11493
+ this.view.unselect();
11494
+ }
11495
+ },
11496
+
11497
+
11498
+ // Given arguments to the select method in the API, returns a span (unzoned start/end and other info)
11499
+ buildSelectSpan: function(zonedStartInput, zonedEndInput) {
11500
+ var start = this.moment(zonedStartInput).stripZone();
11501
+ var end;
11502
+
11503
+ if (zonedEndInput) {
11504
+ end = this.moment(zonedEndInput).stripZone();
11505
+ }
11506
+ else if (start.hasTime()) {
11507
+ end = start.clone().add(this.defaultTimedEventDuration);
11508
+ }
11509
+ else {
11510
+ end = start.clone().add(this.defaultAllDayEventDuration);
11511
+ }
11512
+
11513
+ return { start: start, end: end };
11514
+ },
11515
+
11516
+
11517
+ // Misc
11518
+ // -----------------------------------------------------------------------------------------------------------------
11519
+
11520
+
11278
11521
  // will return `null` if invalid range
11279
11522
  parseRange: function(rangeInput) {
11280
11523
  var start = null;
@@ -11297,122 +11540,247 @@ var Calendar = FC.Calendar = Class.extend({
11297
11540
  }
11298
11541
 
11299
11542
  return { start: start, end: end };
11300
- }
11543
+ },
11301
11544
 
11302
- });
11303
11545
 
11546
+ rerenderEvents: function() { // API method. destroys old events if previously rendered.
11547
+ if (this.elementVisible()) {
11548
+ this.reportEventChange(); // will re-trasmit events to the view, causing a rerender
11549
+ }
11550
+ }
11304
11551
 
11305
- Calendar.mixin(EmitterMixin);
11552
+ });
11306
11553
 
11554
+ ;;
11555
+ /*
11556
+ Options binding/triggering system.
11557
+ */
11558
+ Calendar.mixin({
11307
11559
 
11308
- function Calendar_constructor(element, overrides) {
11309
- var t = this;
11560
+ dirDefaults: null, // option defaults related to LTR or RTL
11561
+ localeDefaults: null, // option defaults related to current locale
11562
+ overrides: null, // option overrides given to the fullCalendar constructor
11563
+ dynamicOverrides: null, // options set with dynamic setter method. higher precedence than view overrides.
11564
+ optionsModel: null, // all defaults combined with overrides
11310
11565
 
11311
- // declare the current calendar instance relies on GlobalEmitter. needed for garbage collection.
11312
- GlobalEmitter.needed();
11313
11566
 
11567
+ initOptionsInternals: function(overrides) {
11568
+ this.overrides = $.extend({}, overrides); // make a copy
11569
+ this.dynamicOverrides = {};
11570
+ this.optionsModel = new Model();
11314
11571
 
11315
- // Exports
11316
- // -----------------------------------------------------------------------------------
11572
+ this.populateOptionsHash();
11573
+ },
11317
11574
 
11318
- t.render = render;
11319
- t.destroy = destroy;
11320
- t.rerenderEvents = rerenderEvents;
11321
- t.select = select;
11322
- t.unselect = unselect;
11323
- t.zoomTo = zoomTo;
11324
- t.getCalendar = getCalendar;
11325
- t.getView = getView;
11326
- t.option = option; // getter/setter method
11327
- t.recordOptionOverrides = recordOptionOverrides;
11328
- t.publiclyTrigger = publiclyTrigger;
11329
-
11330
-
11331
- // Options
11332
- // -----------------------------------------------------------------------------------
11333
11575
 
11334
- t.dynamicOverrides = {};
11335
- t.viewSpecCache = {};
11336
- t.optionHandlers = {}; // for Calendar.options.js
11337
- t.overrides = $.extend({}, overrides); // make a copy
11576
+ // public getter/setter
11577
+ option: function(name, value) {
11578
+ var newOptionHash;
11338
11579
 
11339
- t.populateOptionsHash(); // sets this.options
11580
+ if (typeof name === 'string') {
11581
+ if (value === undefined) { // getter
11582
+ return this.optionsModel.get(name);
11583
+ }
11584
+ else { // setter for individual option
11585
+ newOptionHash = {};
11586
+ newOptionHash[name] = value;
11587
+ this.setOptions(newOptionHash);
11588
+ }
11589
+ }
11590
+ else if (typeof name === 'object') { // compound setter with object input
11591
+ this.setOptions(name);
11592
+ }
11593
+ },
11340
11594
 
11341
11595
 
11596
+ // private getter
11597
+ opt: function(name) {
11598
+ return this.optionsModel.get(name);
11599
+ },
11342
11600
 
11343
- // Locale-data Internals
11344
- // -----------------------------------------------------------------------------------
11345
- // Apply overrides to the current locale's data
11346
11601
 
11347
- var localeData;
11602
+ setOptions: function(newOptionHash) {
11603
+ var optionCnt = 0;
11604
+ var optionName;
11348
11605
 
11349
- // Called immediately, and when any of the options change.
11350
- // Happens before any internal objects rebuild or rerender, because this is very core.
11351
- t.bindOptions([
11352
- 'locale', 'monthNames', 'monthNamesShort', 'dayNames', 'dayNamesShort', 'firstDay', 'weekNumberCalculation'
11353
- ], function(locale, monthNames, monthNamesShort, dayNames, dayNamesShort, firstDay, weekNumberCalculation) {
11606
+ this.recordOptionOverrides(newOptionHash);
11354
11607
 
11355
- // normalize
11356
- if (weekNumberCalculation === 'iso') {
11357
- weekNumberCalculation = 'ISO'; // normalize
11608
+ for (optionName in newOptionHash) {
11609
+ optionCnt++;
11358
11610
  }
11359
11611
 
11360
- localeData = createObject( // make a cheap copy
11361
- getMomentLocaleData(locale) // will fall back to en
11362
- );
11363
-
11364
- if (monthNames) {
11365
- localeData._months = monthNames;
11366
- }
11367
- if (monthNamesShort) {
11368
- localeData._monthsShort = monthNamesShort;
11369
- }
11370
- if (dayNames) {
11371
- localeData._weekdays = dayNames;
11372
- }
11373
- if (dayNamesShort) {
11374
- localeData._weekdaysShort = dayNamesShort;
11612
+ // special-case handling of single option change.
11613
+ // if only one option change, `optionName` will be its name.
11614
+ if (optionCnt === 1) {
11615
+ if (optionName === 'height' || optionName === 'contentHeight' || optionName === 'aspectRatio') {
11616
+ this.updateSize(true); // true = allow recalculation of height
11617
+ return;
11618
+ }
11619
+ else if (optionName === 'defaultDate') {
11620
+ return; // can't change date this way. use gotoDate instead
11621
+ }
11622
+ else if (optionName === 'businessHours') {
11623
+ if (this.view) {
11624
+ this.view.unrenderBusinessHours();
11625
+ this.view.renderBusinessHours();
11626
+ }
11627
+ return;
11628
+ }
11629
+ else if (optionName === 'timezone') {
11630
+ this.rezoneArrayEventSources();
11631
+ this.refetchEvents();
11632
+ return;
11633
+ }
11375
11634
  }
11376
11635
 
11377
- if (firstDay == null && weekNumberCalculation === 'ISO') {
11378
- firstDay = 1;
11379
- }
11380
- if (firstDay != null) {
11381
- var _week = createObject(localeData._week); // _week: { dow: # }
11382
- _week.dow = firstDay;
11383
- localeData._week = _week;
11384
- }
11636
+ // catch-all. rerender the header and footer and rebuild/rerender the current view
11637
+ this.renderHeader();
11638
+ this.renderFooter();
11385
11639
 
11386
- if ( // whitelist certain kinds of input
11387
- weekNumberCalculation === 'ISO' ||
11388
- weekNumberCalculation === 'local' ||
11389
- typeof weekNumberCalculation === 'function'
11390
- ) {
11391
- localeData._fullCalendar_weekCalc = weekNumberCalculation; // moment-ext will know what to do with it
11640
+ // even non-current views will be affected by this option change. do before rerender
11641
+ // TODO: detangle
11642
+ this.viewsByType = {};
11643
+
11644
+ this.reinitView();
11645
+ },
11646
+
11647
+
11648
+ // Computes the flattened options hash for the calendar and assigns to `this.options`.
11649
+ // Assumes this.overrides and this.dynamicOverrides have already been initialized.
11650
+ populateOptionsHash: function() {
11651
+ var locale, localeDefaults;
11652
+ var isRTL, dirDefaults;
11653
+ var rawOptions;
11654
+
11655
+ locale = firstDefined( // explicit locale option given?
11656
+ this.dynamicOverrides.locale,
11657
+ this.overrides.locale
11658
+ );
11659
+ localeDefaults = localeOptionHash[locale];
11660
+ if (!localeDefaults) { // explicit locale option not given or invalid?
11661
+ locale = Calendar.defaults.locale;
11662
+ localeDefaults = localeOptionHash[locale] || {};
11392
11663
  }
11393
11664
 
11394
- // If the internal current date object already exists, move to new locale.
11395
- // We do NOT need to do this technique for event dates, because this happens when converting to "segments".
11396
- if (t.currentDate) {
11397
- localizeMoment(t.currentDate); // sets to localeData
11665
+ isRTL = firstDefined( // based on options computed so far, is direction RTL?
11666
+ this.dynamicOverrides.isRTL,
11667
+ this.overrides.isRTL,
11668
+ localeDefaults.isRTL,
11669
+ Calendar.defaults.isRTL
11670
+ );
11671
+ dirDefaults = isRTL ? Calendar.rtlDefaults : {};
11672
+
11673
+ this.dirDefaults = dirDefaults;
11674
+ this.localeDefaults = localeDefaults;
11675
+
11676
+ rawOptions = mergeOptions([ // merge defaults and overrides. lowest to highest precedence
11677
+ Calendar.defaults, // global defaults
11678
+ dirDefaults,
11679
+ localeDefaults,
11680
+ this.overrides,
11681
+ this.dynamicOverrides
11682
+ ]);
11683
+ populateInstanceComputableOptions(rawOptions); // fill in gaps with computed options
11684
+
11685
+ this.optionsModel.reset(rawOptions);
11686
+ },
11687
+
11688
+
11689
+ // stores the new options internally, but does not rerender anything.
11690
+ recordOptionOverrides: function(newOptionHash) {
11691
+ var optionName;
11692
+
11693
+ for (optionName in newOptionHash) {
11694
+ this.dynamicOverrides[optionName] = newOptionHash[optionName];
11398
11695
  }
11399
- });
11400
11696
 
11697
+ this.viewSpecCache = {}; // the dynamic override invalidates the options in this cache, so just clear it
11698
+ this.populateOptionsHash(); // this.options needs to be recomputed after the dynamic override
11699
+ }
11401
11700
 
11402
- // Calendar-specific Date Utilities
11403
- // -----------------------------------------------------------------------------------
11701
+ });
11702
+
11703
+ ;;
11704
+
11705
+ Calendar.mixin({
11706
+
11707
+ defaultAllDayEventDuration: null,
11708
+ defaultTimedEventDuration: null,
11709
+ localeData: null,
11710
+
11711
+
11712
+ initMomentInternals: function() {
11713
+ var _this = this;
11404
11714
 
11715
+ this.defaultAllDayEventDuration = moment.duration(this.opt('defaultAllDayEventDuration'));
11716
+ this.defaultTimedEventDuration = moment.duration(this.opt('defaultTimedEventDuration'));
11405
11717
 
11406
- t.defaultAllDayEventDuration = moment.duration(t.options.defaultAllDayEventDuration);
11407
- t.defaultTimedEventDuration = moment.duration(t.options.defaultTimedEventDuration);
11718
+ // Called immediately, and when any of the options change.
11719
+ // Happens before any internal objects rebuild or rerender, because this is very core.
11720
+ this.optionsModel.watch('buildingMomentLocale', [
11721
+ '?locale', '?monthNames', '?monthNamesShort', '?dayNames', '?dayNamesShort',
11722
+ '?firstDay', '?weekNumberCalculation'
11723
+ ], function(opts) {
11724
+ var weekNumberCalculation = opts.weekNumberCalculation;
11725
+ var firstDay = opts.firstDay;
11726
+ var _week;
11727
+
11728
+ // normalize
11729
+ if (weekNumberCalculation === 'iso') {
11730
+ weekNumberCalculation = 'ISO'; // normalize
11731
+ }
11732
+
11733
+ var localeData = createObject( // make a cheap copy
11734
+ getMomentLocaleData(opts.locale) // will fall back to en
11735
+ );
11736
+
11737
+ if (opts.monthNames) {
11738
+ localeData._months = opts.monthNames;
11739
+ }
11740
+ if (opts.monthNamesShort) {
11741
+ localeData._monthsShort = opts.monthNamesShort;
11742
+ }
11743
+ if (opts.dayNames) {
11744
+ localeData._weekdays = opts.dayNames;
11745
+ }
11746
+ if (opts.dayNamesShort) {
11747
+ localeData._weekdaysShort = opts.dayNamesShort;
11748
+ }
11749
+
11750
+ if (firstDay == null && weekNumberCalculation === 'ISO') {
11751
+ firstDay = 1;
11752
+ }
11753
+ if (firstDay != null) {
11754
+ _week = createObject(localeData._week); // _week: { dow: # }
11755
+ _week.dow = firstDay;
11756
+ localeData._week = _week;
11757
+ }
11758
+
11759
+ if ( // whitelist certain kinds of input
11760
+ weekNumberCalculation === 'ISO' ||
11761
+ weekNumberCalculation === 'local' ||
11762
+ typeof weekNumberCalculation === 'function'
11763
+ ) {
11764
+ localeData._fullCalendar_weekCalc = weekNumberCalculation; // moment-ext will know what to do with it
11765
+ }
11766
+
11767
+ _this.localeData = localeData;
11768
+
11769
+ // If the internal current date object already exists, move to new locale.
11770
+ // We do NOT need to do this technique for event dates, because this happens when converting to "segments".
11771
+ if (_this.currentDate) {
11772
+ _this.localizeMoment(_this.currentDate); // sets to localeData
11773
+ }
11774
+ });
11775
+ },
11408
11776
 
11409
11777
 
11410
11778
  // Builds a moment using the settings of the current calendar: timezone and locale.
11411
11779
  // Accepts anything the vanilla moment() constructor accepts.
11412
- t.moment = function() {
11780
+ moment: function() {
11413
11781
  var mom;
11414
11782
 
11415
- if (t.options.timezone === 'local') {
11783
+ if (this.opt('timezone') === 'local') {
11416
11784
  mom = FC.moment.apply(null, arguments);
11417
11785
 
11418
11786
  // Force the moment to be local, because FC.moment doesn't guarantee it.
@@ -11420,40 +11788,39 @@ function Calendar_constructor(element, overrides) {
11420
11788
  mom.local();
11421
11789
  }
11422
11790
  }
11423
- else if (t.options.timezone === 'UTC') {
11791
+ else if (this.opt('timezone') === 'UTC') {
11424
11792
  mom = FC.moment.utc.apply(null, arguments); // process as UTC
11425
11793
  }
11426
11794
  else {
11427
11795
  mom = FC.moment.parseZone.apply(null, arguments); // let the input decide the zone
11428
11796
  }
11429
11797
 
11430
- localizeMoment(mom);
11798
+ this.localizeMoment(mom); // TODO
11431
11799
 
11432
11800
  return mom;
11433
- };
11801
+ },
11434
11802
 
11435
11803
 
11436
11804
  // Updates the given moment's locale settings to the current calendar locale settings.
11437
- function localizeMoment(mom) {
11438
- mom._locale = localeData;
11439
- }
11440
- t.localizeMoment = localizeMoment;
11805
+ localizeMoment: function(mom) {
11806
+ mom._locale = this.localeData;
11807
+ },
11441
11808
 
11442
11809
 
11443
11810
  // Returns a boolean about whether or not the calendar knows how to calculate
11444
11811
  // the timezone offset of arbitrary dates in the current timezone.
11445
- t.getIsAmbigTimezone = function() {
11446
- return t.options.timezone !== 'local' && t.options.timezone !== 'UTC';
11447
- };
11812
+ getIsAmbigTimezone: function() {
11813
+ return this.opt('timezone') !== 'local' && this.opt('timezone') !== 'UTC';
11814
+ },
11448
11815
 
11449
11816
 
11450
11817
  // Returns a copy of the given date in the current timezone. Has no effect on dates without times.
11451
- t.applyTimezone = function(date) {
11818
+ applyTimezone: function(date) {
11452
11819
  if (!date.hasTime()) {
11453
11820
  return date.clone();
11454
11821
  }
11455
11822
 
11456
- var zonedDate = t.moment(date.toArray());
11823
+ var zonedDate = this.moment(date.toArray());
11457
11824
  var timeAdjust = date.time() - zonedDate.time();
11458
11825
  var adjustedZonedDate;
11459
11826
 
@@ -11466,115 +11833,268 @@ function Calendar_constructor(element, overrides) {
11466
11833
  }
11467
11834
 
11468
11835
  return zonedDate;
11469
- };
11836
+ },
11470
11837
 
11471
11838
 
11472
11839
  // Returns a moment for the current date, as defined by the client's computer or from the `now` option.
11473
11840
  // Will return an moment with an ambiguous timezone.
11474
- t.getNow = function() {
11475
- var now = t.options.now;
11841
+ getNow: function() {
11842
+ var now = this.opt('now');
11476
11843
  if (typeof now === 'function') {
11477
11844
  now = now();
11478
11845
  }
11479
- return t.moment(now).stripZone();
11480
- };
11846
+ return this.moment(now).stripZone();
11847
+ },
11848
+
11849
+
11850
+ // Produces a human-readable string for the given duration.
11851
+ // Side-effect: changes the locale of the given duration.
11852
+ humanizeDuration: function(duration) {
11853
+ return duration.locale(this.opt('locale')).humanize();
11854
+ },
11855
+
11856
+
11857
+
11858
+ // Event-Specific Date Utilities. TODO: move
11859
+ // -----------------------------------------------------------------------------------------------------------------
11481
11860
 
11482
11861
 
11483
11862
  // Get an event's normalized end date. If not present, calculate it from the defaults.
11484
- t.getEventEnd = function(event) {
11863
+ getEventEnd: function(event) {
11485
11864
  if (event.end) {
11486
11865
  return event.end.clone();
11487
11866
  }
11488
11867
  else {
11489
- return t.getDefaultEventEnd(event.allDay, event.start);
11868
+ return this.getDefaultEventEnd(event.allDay, event.start);
11490
11869
  }
11491
- };
11870
+ },
11492
11871
 
11493
11872
 
11494
11873
  // Given an event's allDay status and start date, return what its fallback end date should be.
11495
11874
  // TODO: rename to computeDefaultEventEnd
11496
- t.getDefaultEventEnd = function(allDay, zonedStart) {
11875
+ getDefaultEventEnd: function(allDay, zonedStart) {
11497
11876
  var end = zonedStart.clone();
11498
11877
 
11499
11878
  if (allDay) {
11500
- end.stripTime().add(t.defaultAllDayEventDuration);
11879
+ end.stripTime().add(this.defaultAllDayEventDuration);
11501
11880
  }
11502
11881
  else {
11503
- end.add(t.defaultTimedEventDuration);
11882
+ end.add(this.defaultTimedEventDuration);
11504
11883
  }
11505
11884
 
11506
- if (t.getIsAmbigTimezone()) {
11885
+ if (this.getIsAmbigTimezone()) {
11507
11886
  end.stripZone(); // we don't know what the tzo should be
11508
11887
  }
11509
11888
 
11510
11889
  return end;
11511
- };
11890
+ }
11512
11891
 
11892
+ });
11513
11893
 
11514
- // Produces a human-readable string for the given duration.
11515
- // Side-effect: changes the locale of the given duration.
11516
- t.humanizeDuration = function(duration) {
11517
- return duration.locale(t.options.locale).humanize();
11518
- };
11894
+ ;;
11519
11895
 
11896
+ Calendar.mixin({
11520
11897
 
11898
+ viewSpecCache: null, // cache of view definitions (initialized in Calendar.js)
11521
11899
 
11522
- // Imports
11523
- // -----------------------------------------------------------------------------------
11900
+
11901
+ // Gets information about how to create a view. Will use a cache.
11902
+ getViewSpec: function(viewType) {
11903
+ var cache = this.viewSpecCache;
11904
+
11905
+ return cache[viewType] || (cache[viewType] = this.buildViewSpec(viewType));
11906
+ },
11524
11907
 
11525
11908
 
11526
- EventManager.call(t);
11909
+ // Given a duration singular unit, like "week" or "day", finds a matching view spec.
11910
+ // Preference is given to views that have corresponding buttons.
11911
+ getUnitViewSpec: function(unit) {
11912
+ var viewTypes;
11913
+ var i;
11914
+ var spec;
11527
11915
 
11916
+ if ($.inArray(unit, unitsDesc) != -1) {
11528
11917
 
11918
+ // put views that have buttons first. there will be duplicates, but oh well
11919
+ viewTypes = this.header.getViewsWithButtons(); // TODO: include footer as well?
11920
+ $.each(FC.views, function(viewType) { // all views
11921
+ viewTypes.push(viewType);
11922
+ });
11529
11923
 
11530
- // Locals
11531
- // -----------------------------------------------------------------------------------
11924
+ for (i = 0; i < viewTypes.length; i++) {
11925
+ spec = this.getViewSpec(viewTypes[i]);
11926
+ if (spec) {
11927
+ if (spec.singleUnit == unit) {
11928
+ return spec;
11929
+ }
11930
+ }
11931
+ }
11932
+ }
11933
+ },
11934
+
11935
+
11936
+ // Builds an object with information on how to create a given view
11937
+ buildViewSpec: function(requestedViewType) {
11938
+ var viewOverrides = this.overrides.views || {};
11939
+ var specChain = []; // for the view. lowest to highest priority
11940
+ var defaultsChain = []; // for the view. lowest to highest priority
11941
+ var overridesChain = []; // for the view. lowest to highest priority
11942
+ var viewType = requestedViewType;
11943
+ var spec; // for the view
11944
+ var overrides; // for the view
11945
+ var durationInput;
11946
+ var duration;
11947
+ var unit;
11948
+
11949
+ // iterate from the specific view definition to a more general one until we hit an actual View class
11950
+ while (viewType) {
11951
+ spec = fcViews[viewType];
11952
+ overrides = viewOverrides[viewType];
11953
+ viewType = null; // clear. might repopulate for another iteration
11532
11954
 
11955
+ if (typeof spec === 'function') { // TODO: deprecate
11956
+ spec = { 'class': spec };
11957
+ }
11958
+
11959
+ if (spec) {
11960
+ specChain.unshift(spec);
11961
+ defaultsChain.unshift(spec.defaults || {});
11962
+ durationInput = durationInput || spec.duration;
11963
+ viewType = viewType || spec.type;
11964
+ }
11965
+
11966
+ if (overrides) {
11967
+ overridesChain.unshift(overrides); // view-specific option hashes have options at zero-level
11968
+ durationInput = durationInput || overrides.duration;
11969
+ viewType = viewType || overrides.type;
11970
+ }
11971
+ }
11972
+
11973
+ spec = mergeProps(specChain);
11974
+ spec.type = requestedViewType;
11975
+ if (!spec['class']) {
11976
+ return false;
11977
+ }
11978
+
11979
+ // fall back to top-level `duration` option
11980
+ durationInput = durationInput ||
11981
+ this.dynamicOverrides.duration ||
11982
+ this.overrides.duration;
11983
+
11984
+ if (durationInput) {
11985
+ duration = moment.duration(durationInput);
11986
+
11987
+ if (duration.valueOf()) { // valid?
11988
+
11989
+ unit = computeDurationGreatestUnit(duration, durationInput);
11990
+
11991
+ spec.duration = duration;
11992
+ spec.durationUnit = unit;
11993
+
11994
+ // view is a single-unit duration, like "week" or "day"
11995
+ // incorporate options for this. lowest priority
11996
+ if (duration.as(unit) === 1) {
11997
+ spec.singleUnit = unit;
11998
+ overridesChain.unshift(viewOverrides[unit] || {});
11999
+ }
12000
+ }
12001
+ }
12002
+
12003
+ spec.defaults = mergeOptions(defaultsChain);
12004
+ spec.overrides = mergeOptions(overridesChain);
12005
+
12006
+ this.buildViewSpecOptions(spec);
12007
+ this.buildViewSpecButtonText(spec, requestedViewType);
12008
+
12009
+ return spec;
12010
+ },
12011
+
12012
+
12013
+ // Builds and assigns a view spec's options object from its already-assigned defaults and overrides
12014
+ buildViewSpecOptions: function(spec) {
12015
+ spec.options = mergeOptions([ // lowest to highest priority
12016
+ Calendar.defaults, // global defaults
12017
+ spec.defaults, // view's defaults (from ViewSubclass.defaults)
12018
+ this.dirDefaults,
12019
+ this.localeDefaults, // locale and dir take precedence over view's defaults!
12020
+ this.overrides, // calendar's overrides (options given to constructor)
12021
+ spec.overrides, // view's overrides (view-specific options)
12022
+ this.dynamicOverrides // dynamically set via setter. highest precedence
12023
+ ]);
12024
+ populateInstanceComputableOptions(spec.options);
12025
+ },
12026
+
12027
+
12028
+ // Computes and assigns a view spec's buttonText-related options
12029
+ buildViewSpecButtonText: function(spec, requestedViewType) {
12030
+
12031
+ // given an options object with a possible `buttonText` hash, lookup the buttonText for the
12032
+ // requested view, falling back to a generic unit entry like "week" or "day"
12033
+ function queryButtonText(options) {
12034
+ var buttonText = options.buttonText || {};
12035
+ return buttonText[requestedViewType] ||
12036
+ // view can decide to look up a certain key
12037
+ (spec.buttonTextKey ? buttonText[spec.buttonTextKey] : null) ||
12038
+ // a key like "month"
12039
+ (spec.singleUnit ? buttonText[spec.singleUnit] : null);
12040
+ }
12041
+
12042
+ // highest to lowest priority
12043
+ spec.buttonTextOverride =
12044
+ queryButtonText(this.dynamicOverrides) ||
12045
+ queryButtonText(this.overrides) || // constructor-specified buttonText lookup hash takes precedence
12046
+ spec.overrides.buttonText; // `buttonText` for view-specific options is a string
11533
12047
 
11534
- var _element = element[0];
11535
- var toolbarsManager;
11536
- var header;
11537
- var footer;
11538
- var content;
11539
- var tm; // for making theme classes
11540
- var currentView; // NOTE: keep this in sync with this.view
11541
- var viewsByType = {}; // holds all instantiated view instances, current or not
11542
- var suggestedViewHeight;
11543
- var windowResizeProxy; // wraps the windowResize function
11544
- var ignoreWindowResize = 0;
12048
+ // highest to lowest priority. mirrors buildViewSpecOptions
12049
+ spec.buttonTextDefault =
12050
+ queryButtonText(this.localeDefaults) ||
12051
+ queryButtonText(this.dirDefaults) ||
12052
+ spec.defaults.buttonText || // a single string. from ViewSubclass.defaults
12053
+ queryButtonText(Calendar.defaults) ||
12054
+ (spec.duration ? this.humanizeDuration(spec.duration) : null) || // like "3 days"
12055
+ requestedViewType; // fall back to given view name
12056
+ }
11545
12057
 
12058
+ });
11546
12059
 
11547
- this.initCurrentDate();
12060
+ ;;
11548
12061
 
12062
+ Calendar.mixin({
11549
12063
 
11550
- // Main Rendering
11551
- // -----------------------------------------------------------------------------------
12064
+ el: null,
12065
+ contentEl: null,
12066
+ suggestedViewHeight: null,
12067
+ windowResizeProxy: null,
12068
+ ignoreWindowResize: 0,
11552
12069
 
11553
12070
 
11554
- function render() {
11555
- if (!content) {
11556
- initialRender();
12071
+ render: function() {
12072
+ if (!this.contentEl) {
12073
+ this.initialRender();
11557
12074
  }
11558
- else if (elementVisible()) {
12075
+ else if (this.elementVisible()) {
11559
12076
  // mainly for the public API
11560
- calcSize();
11561
- renderView();
12077
+ this.calcSize();
12078
+ this.renderView();
11562
12079
  }
11563
- }
12080
+ },
12081
+
11564
12082
 
12083
+ initialRender: function() {
12084
+ var _this = this;
12085
+ var el = this.el;
11565
12086
 
11566
- function initialRender() {
11567
- element.addClass('fc');
12087
+ el.addClass('fc');
11568
12088
 
11569
12089
  // event delegation for nav links
11570
- element.on('click.fc', 'a[data-goto]', function(ev) {
12090
+ el.on('click.fc', 'a[data-goto]', function(ev) {
11571
12091
  var anchorEl = $(this);
11572
12092
  var gotoOptions = anchorEl.data('goto'); // will automatically parse JSON
11573
- var date = t.moment(gotoOptions.date);
12093
+ var date = _this.moment(gotoOptions.date);
11574
12094
  var viewType = gotoOptions.type;
11575
12095
 
11576
12096
  // property like "navLinkDayClick". might be a string or a function
11577
- var customAction = currentView.opt('navLink' + capitaliseFirstLetter(viewType) + 'Click');
12097
+ var customAction = _this.view.opt('navLink' + capitaliseFirstLetter(viewType) + 'Click');
11578
12098
 
11579
12099
  if (typeof customAction === 'function') {
11580
12100
  customAction(date, ev);
@@ -11583,69 +12103,68 @@ function Calendar_constructor(element, overrides) {
11583
12103
  if (typeof customAction === 'string') {
11584
12104
  viewType = customAction;
11585
12105
  }
11586
- zoomTo(date, viewType);
12106
+ _this.zoomTo(date, viewType);
11587
12107
  }
11588
12108
  });
11589
12109
 
11590
12110
  // called immediately, and upon option change
11591
- t.bindOption('theme', function(theme) {
11592
- tm = theme ? 'ui' : 'fc'; // affects a larger scope
11593
- element.toggleClass('ui-widget', theme);
11594
- element.toggleClass('fc-unthemed', !theme);
12111
+ this.optionsModel.watch('applyingThemeClasses', [ '?theme' ], function(opts) {
12112
+ el.toggleClass('ui-widget', opts.theme);
12113
+ el.toggleClass('fc-unthemed', !opts.theme);
11595
12114
  });
11596
12115
 
11597
12116
  // called immediately, and upon option change.
11598
12117
  // HACK: locale often affects isRTL, so we explicitly listen to that too.
11599
- t.bindOptions([ 'isRTL', 'locale' ], function(isRTL) {
11600
- element.toggleClass('fc-ltr', !isRTL);
11601
- element.toggleClass('fc-rtl', isRTL);
12118
+ this.optionsModel.watch('applyingDirClasses', [ '?isRTL', '?locale' ], function(opts) {
12119
+ el.toggleClass('fc-ltr', !opts.isRTL);
12120
+ el.toggleClass('fc-rtl', opts.isRTL);
11602
12121
  });
11603
12122
 
11604
- content = $("<div class='fc-view-container'/>").prependTo(element);
12123
+ this.contentEl = $("<div class='fc-view-container'/>").prependTo(el);
11605
12124
 
11606
- var toolbars = buildToolbars();
11607
- toolbarsManager = new Iterator(toolbars);
12125
+ this.initToolbars();
12126
+ this.renderHeader();
12127
+ this.renderFooter();
12128
+ this.renderView(this.opt('defaultView'));
11608
12129
 
11609
- header = t.header = toolbars[0];
11610
- footer = t.footer = toolbars[1];
11611
-
11612
- renderHeader();
11613
- renderFooter();
11614
- renderView(t.options.defaultView);
11615
-
11616
- if (t.options.handleWindowResize) {
11617
- windowResizeProxy = debounce(windowResize, t.options.windowResizeDelay); // prevents rapid calls
11618
- $(window).resize(windowResizeProxy);
12130
+ if (this.opt('handleWindowResize')) {
12131
+ $(window).resize(
12132
+ this.windowResizeProxy = debounce( // prevents rapid calls
12133
+ this.windowResize.bind(this),
12134
+ this.opt('windowResizeDelay')
12135
+ )
12136
+ );
11619
12137
  }
11620
- }
12138
+ },
11621
12139
 
11622
12140
 
11623
- function destroy() {
12141
+ destroy: function() {
11624
12142
 
11625
- if (currentView) {
11626
- currentView.removeElement();
12143
+ if (this.view) {
12144
+ this.view.removeElement();
11627
12145
 
11628
- // NOTE: don't null-out currentView/t.view in case API methods are called after destroy.
12146
+ // NOTE: don't null-out this.view in case API methods are called after destroy.
11629
12147
  // It is still the "current" view, just not rendered.
11630
12148
  }
11631
12149
 
11632
- toolbarsManager.proxyCall('removeElement');
11633
- content.remove();
11634
- element.removeClass('fc fc-ltr fc-rtl fc-unthemed ui-widget');
12150
+ this.toolbarsManager.proxyCall('removeElement');
12151
+ this.contentEl.remove();
12152
+ this.el.removeClass('fc fc-ltr fc-rtl fc-unthemed ui-widget');
11635
12153
 
11636
- element.off('.fc'); // unbind nav link handlers
12154
+ this.el.off('.fc'); // unbind nav link handlers
11637
12155
 
11638
- if (windowResizeProxy) {
11639
- $(window).unbind('resize', windowResizeProxy);
12156
+ if (this.windowResizeProxy) {
12157
+ $(window).unbind('resize', this.windowResizeProxy);
12158
+ this.windowResizeProxy = null;
11640
12159
  }
11641
12160
 
11642
12161
  GlobalEmitter.unneeded();
11643
- }
12162
+ },
11644
12163
 
11645
12164
 
11646
- function elementVisible() {
11647
- return element.is(':visible');
11648
- }
12165
+ elementVisible: function() {
12166
+ return this.el.is(':visible');
12167
+ },
11649
12168
 
11650
12169
 
11651
12170
 
@@ -11656,501 +12175,278 @@ function Calendar_constructor(element, overrides) {
11656
12175
  // Renders a view because of a date change, view-type change, or for the first time.
11657
12176
  // If not given a viewType, keep the current view but render different dates.
11658
12177
  // Accepts an optional scroll state to restore to.
11659
- function renderView(viewType, forcedScroll) {
11660
- ignoreWindowResize++;
12178
+ renderView: function(viewType, forcedScroll) {
12179
+
12180
+ this.ignoreWindowResize++;
11661
12181
 
11662
- var needsClearView = currentView && viewType && currentView.type !== viewType;
12182
+ var needsClearView = this.view && viewType && this.view.type !== viewType;
11663
12183
 
11664
12184
  // if viewType is changing, remove the old view's rendering
11665
12185
  if (needsClearView) {
11666
- freezeContentHeight(); // prevent a scroll jump when view element is removed
11667
- clearView();
12186
+ this.freezeContentHeight(); // prevent a scroll jump when view element is removed
12187
+ this.clearView();
11668
12188
  }
11669
12189
 
11670
12190
  // if viewType changed, or the view was never created, create a fresh view
11671
- if (!currentView && viewType) {
11672
- currentView = t.view =
11673
- viewsByType[viewType] ||
11674
- (viewsByType[viewType] = t.instantiateView(viewType));
12191
+ if (!this.view && viewType) {
12192
+ this.view =
12193
+ this.viewsByType[viewType] ||
12194
+ (this.viewsByType[viewType] = this.instantiateView(viewType));
11675
12195
 
11676
- currentView.setElement(
11677
- $("<div class='fc-view fc-" + viewType + "-view' />").appendTo(content)
12196
+ this.view.setElement(
12197
+ $("<div class='fc-view fc-" + viewType + "-view' />").appendTo(this.contentEl)
11678
12198
  );
11679
- toolbarsManager.proxyCall('activateButton', viewType);
12199
+ this.toolbarsManager.proxyCall('activateButton', viewType);
11680
12200
  }
11681
12201
 
11682
- if (currentView) {
11683
-
11684
- if (elementVisible()) {
11685
-
11686
- if (forcedScroll) {
11687
- currentView.captureInitialScroll(forcedScroll);
11688
- }
11689
-
11690
- currentView.setDate(t.currentDate);
12202
+ if (this.view) {
11691
12203
 
11692
- // TODO: make setDate return the revised date.
11693
- // Difficult because of the pseudo-async nature, promises.
11694
- t.currentDate = currentView.currentDate;
12204
+ if (forcedScroll) {
12205
+ this.view.addForcedScroll(forcedScroll);
12206
+ }
11695
12207
 
11696
- if (forcedScroll) {
11697
- currentView.releaseScroll();
11698
- }
12208
+ if (this.elementVisible()) {
12209
+ this.currentDate = this.view.setDate(this.currentDate);
11699
12210
  }
11700
12211
  }
11701
12212
 
11702
12213
  if (needsClearView) {
11703
- thawContentHeight();
12214
+ this.thawContentHeight();
11704
12215
  }
11705
12216
 
11706
- ignoreWindowResize--;
11707
- }
11708
- t.renderView = renderView;
12217
+ this.ignoreWindowResize--;
12218
+ },
11709
12219
 
11710
12220
 
11711
12221
  // Unrenders the current view and reflects this change in the Header.
11712
- // Unregsiters the `currentView`, but does not remove from viewByType hash.
11713
- function clearView() {
11714
- toolbarsManager.proxyCall('deactivateButton', currentView.type);
11715
- currentView.removeElement();
11716
- currentView = t.view = null;
11717
- }
12222
+ // Unregsiters the `view`, but does not remove from viewByType hash.
12223
+ clearView: function() {
12224
+ this.toolbarsManager.proxyCall('deactivateButton', this.view.type);
12225
+ this.view.removeElement();
12226
+ this.view = null;
12227
+ },
11718
12228
 
11719
12229
 
11720
12230
  // Destroys the view, including the view object. Then, re-instantiates it and renders it.
11721
12231
  // Maintains the same scroll state.
11722
12232
  // TODO: maintain any other user-manipulated state.
11723
- function reinitView() {
11724
- ignoreWindowResize++;
11725
- freezeContentHeight();
11726
-
11727
- var viewType = currentView.type;
11728
- var scrollState = currentView.queryScroll();
11729
- clearView();
11730
- calcSize();
11731
- renderView(viewType, scrollState);
12233
+ reinitView: function() {
12234
+ this.ignoreWindowResize++;
12235
+ this.freezeContentHeight();
11732
12236
 
11733
- thawContentHeight();
11734
- ignoreWindowResize--;
11735
- }
12237
+ var viewType = this.view.type;
12238
+ var scrollState = this.view.queryScroll();
12239
+ this.clearView();
12240
+ this.calcSize();
12241
+ this.renderView(viewType, scrollState);
11736
12242
 
12243
+ this.thawContentHeight();
12244
+ this.ignoreWindowResize--;
12245
+ },
11737
12246
 
11738
12247
 
11739
12248
  // Resizing
11740
12249
  // -----------------------------------------------------------------------------------
11741
12250
 
11742
12251
 
11743
- t.getSuggestedViewHeight = function() {
11744
- if (suggestedViewHeight === undefined) {
11745
- calcSize();
12252
+ getSuggestedViewHeight: function() {
12253
+ if (this.suggestedViewHeight === null) {
12254
+ this.calcSize();
11746
12255
  }
11747
- return suggestedViewHeight;
11748
- };
12256
+ return this.suggestedViewHeight;
12257
+ },
11749
12258
 
11750
12259
 
11751
- t.isHeightAuto = function() {
11752
- return t.options.contentHeight === 'auto' || t.options.height === 'auto';
11753
- };
12260
+ isHeightAuto: function() {
12261
+ return this.opt('contentHeight') === 'auto' || this.opt('height') === 'auto';
12262
+ },
11754
12263
 
11755
12264
 
11756
- function updateSize(shouldRecalc) {
11757
- if (elementVisible()) {
12265
+ updateSize: function(shouldRecalc) {
12266
+ if (this.elementVisible()) {
11758
12267
 
11759
12268
  if (shouldRecalc) {
11760
- _calcSize();
12269
+ this._calcSize();
11761
12270
  }
11762
12271
 
11763
- ignoreWindowResize++;
11764
- currentView.updateSize(true); // isResize=true. will poll getSuggestedViewHeight() and isHeightAuto()
11765
- ignoreWindowResize--;
12272
+ this.ignoreWindowResize++;
12273
+ this.view.updateSize(true); // isResize=true. will poll getSuggestedViewHeight() and isHeightAuto()
12274
+ this.ignoreWindowResize--;
11766
12275
 
11767
12276
  return true; // signal success
11768
12277
  }
11769
- }
12278
+ },
11770
12279
 
11771
12280
 
11772
- function calcSize() {
11773
- if (elementVisible()) {
11774
- _calcSize();
12281
+ calcSize: function() {
12282
+ if (this.elementVisible()) {
12283
+ this._calcSize();
11775
12284
  }
11776
- }
12285
+ },
11777
12286
 
11778
12287
 
11779
- function _calcSize() { // assumes elementVisible
11780
- var contentHeightInput = t.options.contentHeight;
11781
- var heightInput = t.options.height;
12288
+ _calcSize: function() { // assumes elementVisible
12289
+ var contentHeightInput = this.opt('contentHeight');
12290
+ var heightInput = this.opt('height');
11782
12291
 
11783
12292
  if (typeof contentHeightInput === 'number') { // exists and not 'auto'
11784
- suggestedViewHeight = contentHeightInput;
12293
+ this.suggestedViewHeight = contentHeightInput;
11785
12294
  }
11786
12295
  else if (typeof contentHeightInput === 'function') { // exists and is a function
11787
- suggestedViewHeight = contentHeightInput();
12296
+ this.suggestedViewHeight = contentHeightInput();
11788
12297
  }
11789
12298
  else if (typeof heightInput === 'number') { // exists and not 'auto'
11790
- suggestedViewHeight = heightInput - queryToolbarsHeight();
12299
+ this.suggestedViewHeight = heightInput - this.queryToolbarsHeight();
11791
12300
  }
11792
12301
  else if (typeof heightInput === 'function') { // exists and is a function
11793
- suggestedViewHeight = heightInput() - queryToolbarsHeight();
12302
+ this.suggestedViewHeight = heightInput() - this.queryToolbarsHeight();
11794
12303
  }
11795
12304
  else if (heightInput === 'parent') { // set to height of parent element
11796
- suggestedViewHeight = element.parent().height() - queryToolbarsHeight();
12305
+ this.suggestedViewHeight = this.el.parent().height() - this.queryToolbarsHeight();
11797
12306
  }
11798
12307
  else {
11799
- suggestedViewHeight = Math.round(content.width() / Math.max(t.options.aspectRatio, .5));
12308
+ this.suggestedViewHeight = Math.round(
12309
+ this.contentEl.width() /
12310
+ Math.max(this.opt('aspectRatio'), .5)
12311
+ );
11800
12312
  }
11801
- }
11802
-
11803
-
11804
- function queryToolbarsHeight() {
11805
- return toolbarsManager.items.reduce(function(accumulator, toolbar) {
11806
- var toolbarHeight = toolbar.el ? toolbar.el.outerHeight(true) : 0; // includes margin
11807
- return accumulator + toolbarHeight;
11808
- }, 0);
11809
- }
12313
+ },
11810
12314
 
11811
12315
 
11812
- function windowResize(ev) {
12316
+ windowResize: function(ev) {
11813
12317
  if (
11814
- !ignoreWindowResize &&
12318
+ !this.ignoreWindowResize &&
11815
12319
  ev.target === window && // so we don't process jqui "resize" events that have bubbled up
11816
- currentView.renderRange // view has already been rendered
12320
+ this.view.renderRange // view has already been rendered
11817
12321
  ) {
11818
- if (updateSize(true)) {
11819
- currentView.publiclyTrigger('windowResize', _element);
12322
+ if (this.updateSize(true)) {
12323
+ this.view.publiclyTrigger('windowResize', this.el[0]);
11820
12324
  }
11821
12325
  }
11822
- }
11823
-
12326
+ },
11824
12327
 
11825
12328
 
11826
- /* Event Rendering
12329
+ /* Height "Freezing"
11827
12330
  -----------------------------------------------------------------------------*/
11828
12331
 
11829
12332
 
11830
- function rerenderEvents() { // API method. destroys old events if previously rendered.
11831
- if (elementVisible()) {
11832
- t.reportEventChange(); // will re-trasmit events to the view, causing a rerender
11833
- }
12333
+ freezeContentHeight: function() {
12334
+ this.contentEl.css({
12335
+ width: '100%',
12336
+ height: this.contentEl.height(),
12337
+ overflow: 'hidden'
12338
+ });
12339
+ },
12340
+
12341
+
12342
+ thawContentHeight: function() {
12343
+ this.contentEl.css({
12344
+ width: '',
12345
+ height: '',
12346
+ overflow: ''
12347
+ });
11834
12348
  }
11835
12349
 
12350
+ });
11836
12351
 
12352
+ ;;
11837
12353
 
11838
- /* Toolbars
11839
- -----------------------------------------------------------------------------*/
12354
+ Calendar.mixin({
11840
12355
 
12356
+ header: null,
12357
+ footer: null,
12358
+ toolbarsManager: null,
11841
12359
 
11842
- function buildToolbars() {
11843
- return [
11844
- new Toolbar(t, computeHeaderOptions()),
11845
- new Toolbar(t, computeFooterOptions())
11846
- ];
11847
- }
12360
+
12361
+ initToolbars: function() {
12362
+ this.header = new Toolbar(this, this.computeHeaderOptions());
12363
+ this.footer = new Toolbar(this, this.computeFooterOptions());
12364
+ this.toolbarsManager = new Iterator([ this.header, this.footer ]);
12365
+ },
11848
12366
 
11849
12367
 
11850
- function computeHeaderOptions() {
12368
+ computeHeaderOptions: function() {
11851
12369
  return {
11852
12370
  extraClasses: 'fc-header-toolbar',
11853
- layout: t.options.header
12371
+ layout: this.opt('header')
11854
12372
  };
11855
- }
12373
+ },
11856
12374
 
11857
12375
 
11858
- function computeFooterOptions() {
12376
+ computeFooterOptions: function() {
11859
12377
  return {
11860
12378
  extraClasses: 'fc-footer-toolbar',
11861
- layout: t.options.footer
12379
+ layout: this.opt('footer')
11862
12380
  };
11863
- }
12381
+ },
11864
12382
 
11865
12383
 
11866
12384
  // can be called repeatedly and Header will rerender
11867
- function renderHeader() {
11868
- header.setToolbarOptions(computeHeaderOptions());
12385
+ renderHeader: function() {
12386
+ var header = this.header;
12387
+
12388
+ header.setToolbarOptions(this.computeHeaderOptions());
11869
12389
  header.render();
12390
+
11870
12391
  if (header.el) {
11871
- element.prepend(header.el);
12392
+ this.el.prepend(header.el);
11872
12393
  }
11873
- }
12394
+ },
11874
12395
 
11875
12396
 
11876
12397
  // can be called repeatedly and Footer will rerender
11877
- function renderFooter() {
11878
- footer.setToolbarOptions(computeFooterOptions());
12398
+ renderFooter: function() {
12399
+ var footer = this.footer;
12400
+
12401
+ footer.setToolbarOptions(this.computeFooterOptions());
11879
12402
  footer.render();
12403
+
11880
12404
  if (footer.el) {
11881
- element.append(footer.el);
12405
+ this.el.append(footer.el);
11882
12406
  }
11883
- }
12407
+ },
11884
12408
 
11885
12409
 
11886
- t.setToolbarsTitle = function(title) {
11887
- toolbarsManager.proxyCall('updateTitle', title);
11888
- };
12410
+ setToolbarsTitle: function(title) {
12411
+ this.toolbarsManager.proxyCall('updateTitle', title);
12412
+ },
11889
12413
 
11890
12414
 
11891
- t.updateToolbarButtons = function() {
11892
- var now = t.getNow();
11893
- var todayInfo = currentView.buildDateProfile(now);
11894
- var prevInfo = currentView.buildPrevDateProfile(t.currentDate);
11895
- var nextInfo = currentView.buildNextDateProfile(t.currentDate);
12415
+ updateToolbarButtons: function() {
12416
+ var now = this.getNow();
12417
+ var view = this.view;
12418
+ var todayInfo = view.buildDateProfile(now);
12419
+ var prevInfo = view.buildPrevDateProfile(this.currentDate);
12420
+ var nextInfo = view.buildNextDateProfile(this.currentDate);
11896
12421
 
11897
- toolbarsManager.proxyCall(
11898
- (todayInfo.isValid && !isDateWithinRange(now, currentView.currentRange)) ?
12422
+ this.toolbarsManager.proxyCall(
12423
+ (todayInfo.isValid && !isDateWithinRange(now, view.currentRange)) ?
11899
12424
  'enableButton' :
11900
12425
  'disableButton',
11901
12426
  'today'
11902
12427
  );
11903
12428
 
11904
- toolbarsManager.proxyCall(
12429
+ this.toolbarsManager.proxyCall(
11905
12430
  prevInfo.isValid ?
11906
12431
  'enableButton' :
11907
12432
  'disableButton',
11908
12433
  'prev'
11909
12434
  );
11910
12435
 
11911
- toolbarsManager.proxyCall(
12436
+ this.toolbarsManager.proxyCall(
11912
12437
  nextInfo.isValid ?
11913
12438
  'enableButton' :
11914
12439
  'disableButton',
11915
12440
  'next'
11916
12441
  );
11917
- };
11918
-
11919
-
11920
-
11921
- /* Selection
11922
- -----------------------------------------------------------------------------*/
11923
-
11924
-
11925
- // this public method receives start/end dates in any format, with any timezone
11926
- function select(zonedStartInput, zonedEndInput) {
11927
- currentView.select(
11928
- t.buildSelectSpan.apply(t, arguments)
11929
- );
11930
- }
11931
-
11932
-
11933
- function unselect() { // safe to be called before renderView
11934
- if (currentView) {
11935
- currentView.unselect();
11936
- }
11937
- }
11938
-
11939
-
11940
- // Forces navigation to a view for the given date.
11941
- // `viewType` can be a specific view name or a generic one like "week" or "day".
11942
- function zoomTo(newDate, viewType) {
11943
- var spec;
11944
-
11945
- viewType = viewType || 'day'; // day is default zoom
11946
- spec = t.getViewSpec(viewType) || t.getUnitViewSpec(viewType);
11947
-
11948
- t.currentDate = newDate.clone();
11949
- renderView(spec ? spec.type : null);
11950
- }
11951
-
11952
-
11953
-
11954
- /* Height "Freezing"
11955
- -----------------------------------------------------------------------------*/
11956
-
11957
-
11958
- t.freezeContentHeight = freezeContentHeight;
11959
- t.thawContentHeight = thawContentHeight;
11960
-
11961
- var freezeContentHeightDepth = 0;
11962
-
11963
-
11964
- function freezeContentHeight() {
11965
- if (!(freezeContentHeightDepth++)) {
11966
- content.css({
11967
- width: '100%',
11968
- height: content.height(),
11969
- overflow: 'hidden'
11970
- });
11971
- }
11972
- }
11973
-
11974
-
11975
- function thawContentHeight() {
11976
- if (!(--freezeContentHeightDepth)) {
11977
- content.css({
11978
- width: '',
11979
- height: '',
11980
- overflow: ''
11981
- });
11982
- }
11983
- }
11984
-
11985
-
11986
-
11987
- /* Misc
11988
- -----------------------------------------------------------------------------*/
11989
-
11990
-
11991
- function getCalendar() {
11992
- return t;
11993
- }
11994
-
11995
-
11996
- function getView() {
11997
- return currentView;
11998
- }
11999
-
12000
-
12001
- function option(name, value) {
12002
- var newOptionHash;
12003
-
12004
- if (typeof name === 'string') {
12005
- if (value === undefined) { // getter
12006
- return t.options[name];
12007
- }
12008
- else { // setter for individual option
12009
- newOptionHash = {};
12010
- newOptionHash[name] = value;
12011
- setOptions(newOptionHash);
12012
- }
12013
- }
12014
- else if (typeof name === 'object') { // compound setter with object input
12015
- setOptions(name);
12016
- }
12017
- }
12018
-
12019
-
12020
- function setOptions(newOptionHash) {
12021
- var optionCnt = 0;
12022
- var optionName;
12023
-
12024
- recordOptionOverrides(newOptionHash);
12025
-
12026
- for (optionName in newOptionHash) {
12027
- optionCnt++;
12028
- }
12029
-
12030
- // special-case handling of single option change.
12031
- // if only one option change, `optionName` will be its name.
12032
- if (optionCnt === 1) {
12033
- if (optionName === 'height' || optionName === 'contentHeight' || optionName === 'aspectRatio') {
12034
- updateSize(true); // true = allow recalculation of height
12035
- return;
12036
- }
12037
- else if (optionName === 'defaultDate') {
12038
- return; // can't change date this way. use gotoDate instead
12039
- }
12040
- else if (optionName === 'businessHours') {
12041
- if (currentView) {
12042
- currentView.unrenderBusinessHours();
12043
- currentView.renderBusinessHours();
12044
- }
12045
- return;
12046
- }
12047
- else if (optionName === 'timezone') {
12048
- t.rezoneArrayEventSources();
12049
- t.refetchEvents();
12050
- return;
12051
- }
12052
- }
12053
-
12054
- // catch-all. rerender the header and footer and rebuild/rerender the current view
12055
- renderHeader();
12056
- renderFooter();
12057
- viewsByType = {}; // even non-current views will be affected by this option change. do before rerender
12058
- reinitView();
12059
- }
12060
-
12061
-
12062
- // stores the new options internally, but does not rerender anything.
12063
- function recordOptionOverrides(newOptionHash) {
12064
- var optionName;
12065
-
12066
- for (optionName in newOptionHash) {
12067
- t.dynamicOverrides[optionName] = newOptionHash[optionName];
12068
- }
12069
-
12070
- t.viewSpecCache = {}; // the dynamic override invalidates the options in this cache, so just clear it
12071
- t.populateOptionsHash(); // this.options needs to be recomputed after the dynamic override
12072
-
12073
- // trigger handlers after this.options has been updated
12074
- for (optionName in newOptionHash) {
12075
- t.triggerOptionHandlers(optionName); // recall bindOption/bindOptions
12076
- }
12077
- }
12078
-
12079
-
12080
- function publiclyTrigger(name, thisObj) {
12081
- var args = Array.prototype.slice.call(arguments, 2);
12082
-
12083
- thisObj = thisObj || _element;
12084
- this.triggerWith(name, thisObj, args); // Emitter's method
12085
-
12086
- if (t.options[name]) {
12087
- return t.options[name].apply(thisObj, args);
12088
- }
12089
- }
12090
-
12091
- t.initialize();
12092
- }
12093
-
12094
- ;;
12095
- /*
12096
- Options binding/triggering system.
12097
- */
12098
- Calendar.mixin({
12099
-
12100
- // A map of option names to arrays of handler objects. Initialized to {} in Calendar.
12101
- // Format for a handler object:
12102
- // {
12103
- // func // callback function to be called upon change
12104
- // names // option names whose values should be given to func
12105
- // }
12106
- optionHandlers: null,
12107
-
12108
- // Calls handlerFunc immediately, and when the given option has changed.
12109
- // handlerFunc will be given the option value.
12110
- bindOption: function(optionName, handlerFunc) {
12111
- this.bindOptions([ optionName ], handlerFunc);
12112
- },
12113
-
12114
- // Calls handlerFunc immediately, and when any of the given options change.
12115
- // handlerFunc will be given each option value as ordered function arguments.
12116
- bindOptions: function(optionNames, handlerFunc) {
12117
- var handlerObj = { func: handlerFunc, names: optionNames };
12118
- var i;
12119
-
12120
- for (i = 0; i < optionNames.length; i++) {
12121
- this.registerOptionHandlerObj(optionNames[i], handlerObj);
12122
- }
12123
-
12124
- this.triggerOptionHandlerObj(handlerObj);
12125
- },
12126
-
12127
- // Puts the given handler object into the internal hash
12128
- registerOptionHandlerObj: function(optionName, handlerObj) {
12129
- (this.optionHandlers[optionName] || (this.optionHandlers[optionName] = []))
12130
- .push(handlerObj);
12131
- },
12132
-
12133
- // Reports that the given option has changed, and calls all appropriate handlers.
12134
- triggerOptionHandlers: function(optionName) {
12135
- var handlerObjs = this.optionHandlers[optionName] || [];
12136
- var i;
12137
-
12138
- for (i = 0; i < handlerObjs.length; i++) {
12139
- this.triggerOptionHandlerObj(handlerObjs[i]);
12140
- }
12141
12442
  },
12142
12443
 
12143
- // Calls the callback for a specific handler object, passing in the appropriate arguments.
12144
- triggerOptionHandlerObj: function(handlerObj) {
12145
- var optionNames = handlerObj.names;
12146
- var optionValues = [];
12147
- var i;
12148
-
12149
- for (i = 0; i < optionNames.length; i++) {
12150
- optionValues.push(this.options[optionNames[i]]);
12151
- }
12152
12444
 
12153
- handlerObj.func.apply(this, optionValues); // maintain the Calendar's `this` context
12445
+ queryToolbarsHeight: function() {
12446
+ return this.toolbarsManager.items.reduce(function(accumulator, toolbar) {
12447
+ var toolbarHeight = toolbar.el ? toolbar.el.outerHeight(true) : 0; // includes margin
12448
+ return accumulator + toolbarHeight;
12449
+ }, 0);
12154
12450
  }
12155
12451
 
12156
12452
  });
@@ -12463,6 +12759,7 @@ var instanceComputableOptions = {
12463
12759
 
12464
12760
  };
12465
12761
 
12762
+ // TODO: make these computable properties in optionsModel
12466
12763
  function populateInstanceComputableOptions(options) {
12467
12764
  $.each(instanceComputableOptions, function(name, func) {
12468
12765
  if (options[name] == null) {
@@ -12533,7 +12830,7 @@ function EventManager() { // assumed to be a calendar
12533
12830
 
12534
12831
 
12535
12832
  $.each(
12536
- (t.options.events ? [ t.options.events ] : []).concat(t.options.eventSources || []),
12833
+ (t.opt('events') ? [ t.opt('events') ] : []).concat(t.opt('eventSources') || []),
12537
12834
  function(i, sourceInput) {
12538
12835
  var source = buildEventSource(sourceInput);
12539
12836
  if (source) {
@@ -12545,7 +12842,7 @@ function EventManager() { // assumed to be a calendar
12545
12842
 
12546
12843
 
12547
12844
  function requestEvents(start, end) {
12548
- if (!t.options.lazyFetching || isFetchNeeded(start, end)) {
12845
+ if (!t.opt('lazyFetching') || isFetchNeeded(start, end)) {
12549
12846
  return fetchEvents(start, end);
12550
12847
  }
12551
12848
  else {
@@ -12584,11 +12881,6 @@ function EventManager() { // assumed to be a calendar
12584
12881
  };
12585
12882
 
12586
12883
 
12587
- t.getPrunedEventCache = function() {
12588
- return prunedCache;
12589
- };
12590
-
12591
-
12592
12884
 
12593
12885
  /* Fetching
12594
12886
  -----------------------------------------------------------------------------*/
@@ -12650,7 +12942,7 @@ function EventManager() { // assumed to be a calendar
12650
12942
  }
12651
12943
 
12652
12944
  if (pendingSourceCnt) {
12653
- return new Promise(function(resolve) {
12945
+ return Promise.construct(function(resolve) {
12654
12946
  t.one('eventsReceived', resolve); // will send prunedCache
12655
12947
  });
12656
12948
  }
@@ -12734,7 +13026,7 @@ function EventManager() { // assumed to be a calendar
12734
13026
  source,
12735
13027
  rangeStart.clone(),
12736
13028
  rangeEnd.clone(),
12737
- t.options.timezone,
13029
+ t.opt('timezone'),
12738
13030
  callback
12739
13031
  );
12740
13032
 
@@ -12757,7 +13049,7 @@ function EventManager() { // assumed to be a calendar
12757
13049
  t, // this, the Calendar object
12758
13050
  rangeStart.clone(),
12759
13051
  rangeEnd.clone(),
12760
- t.options.timezone,
13052
+ t.opt('timezone'),
12761
13053
  function(events) {
12762
13054
  callback(events);
12763
13055
  t.popLoading();
@@ -12792,9 +13084,9 @@ function EventManager() { // assumed to be a calendar
12792
13084
  // and not affect the passed-in object.
12793
13085
  var data = $.extend({}, customData || {});
12794
13086
 
12795
- var startParam = firstDefined(source.startParam, t.options.startParam);
12796
- var endParam = firstDefined(source.endParam, t.options.endParam);
12797
- var timezoneParam = firstDefined(source.timezoneParam, t.options.timezoneParam);
13087
+ var startParam = firstDefined(source.startParam, t.opt('startParam'));
13088
+ var endParam = firstDefined(source.endParam, t.opt('endParam'));
13089
+ var timezoneParam = firstDefined(source.timezoneParam, t.opt('timezoneParam'));
12798
13090
 
12799
13091
  if (startParam) {
12800
13092
  data[startParam] = rangeStart.format();
@@ -12802,8 +13094,8 @@ function EventManager() { // assumed to be a calendar
12802
13094
  if (endParam) {
12803
13095
  data[endParam] = rangeEnd.format();
12804
13096
  }
12805
- if (t.options.timezone && t.options.timezone != 'local') {
12806
- data[timezoneParam] = t.options.timezone;
13097
+ if (t.opt('timezone') && t.opt('timezone') != 'local') {
13098
+ data[timezoneParam] = t.opt('timezone');
12807
13099
  }
12808
13100
 
12809
13101
  t.pushLoading();
@@ -13212,12 +13504,13 @@ function EventManager() { // assumed to be a calendar
13212
13504
  // Will return `false` when input is invalid.
13213
13505
  // `source` is optional
13214
13506
  function buildEventFromInput(input, source) {
13507
+ var calendarEventDataTransform = t.opt('eventDataTransform');
13215
13508
  var out = {};
13216
13509
  var start, end;
13217
13510
  var allDay;
13218
13511
 
13219
- if (t.options.eventDataTransform) {
13220
- input = t.options.eventDataTransform(input);
13512
+ if (calendarEventDataTransform) {
13513
+ input = calendarEventDataTransform(input);
13221
13514
  }
13222
13515
  if (source && source.eventDataTransform) {
13223
13516
  input = source.eventDataTransform(input);
@@ -13283,7 +13576,7 @@ function EventManager() { // assumed to be a calendar
13283
13576
  if (allDay === undefined) { // still undefined? fallback to default
13284
13577
  allDay = firstDefined(
13285
13578
  source ? source.allDayDefault : undefined,
13286
- t.options.allDayDefault
13579
+ t.opt('allDayDefault')
13287
13580
  );
13288
13581
  // still undefined? normalizeEventDates will calculate it
13289
13582
  }
@@ -13320,7 +13613,7 @@ function EventManager() { // assumed to be a calendar
13320
13613
  }
13321
13614
 
13322
13615
  if (!eventProps.end) {
13323
- if (t.options.forceEventDuration) {
13616
+ if (t.opt('forceEventDuration')) {
13324
13617
  eventProps.end = t.getDefaultEventEnd(eventProps.allDay, eventProps.start);
13325
13618
  }
13326
13619
  else {
@@ -13673,21 +13966,22 @@ function backupEventDates(event) {
13673
13966
  // Determines if the given event can be relocated to the given span (unzoned start/end with other misc data)
13674
13967
  Calendar.prototype.isEventSpanAllowed = function(span, event) {
13675
13968
  var source = event.source || {};
13969
+ var eventAllowFunc = this.opt('eventAllow');
13676
13970
 
13677
13971
  var constraint = firstDefined(
13678
13972
  event.constraint,
13679
13973
  source.constraint,
13680
- this.options.eventConstraint
13974
+ this.opt('eventConstraint')
13681
13975
  );
13682
13976
 
13683
13977
  var overlap = firstDefined(
13684
13978
  event.overlap,
13685
13979
  source.overlap,
13686
- this.options.eventOverlap
13980
+ this.opt('eventOverlap')
13687
13981
  );
13688
13982
 
13689
13983
  return this.isSpanAllowed(span, constraint, overlap, event) &&
13690
- (!this.options.eventAllow || this.options.eventAllow(span, event) !== false);
13984
+ (!eventAllowFunc || eventAllowFunc(span, event) !== false);
13691
13985
  };
13692
13986
 
13693
13987
 
@@ -13716,8 +14010,10 @@ Calendar.prototype.isExternalSpanAllowed = function(eventSpan, eventLocation, ev
13716
14010
 
13717
14011
  // Determines the given span (unzoned start/end with other misc data) can be selected.
13718
14012
  Calendar.prototype.isSelectionSpanAllowed = function(span) {
13719
- return this.isSpanAllowed(span, this.options.selectConstraint, this.options.selectOverlap) &&
13720
- (!this.options.selectAllow || this.options.selectAllow(span) !== false);
14013
+ var selectAllowFunc = this.opt('selectAllow');
14014
+
14015
+ return this.isSpanAllowed(span, this.opt('selectConstraint'), this.opt('selectOverlap')) &&
14016
+ (!selectAllowFunc || selectAllowFunc(span) !== false);
13721
14017
  };
13722
14018
 
13723
14019
 
@@ -13841,7 +14137,7 @@ var BUSINESS_HOUR_EVENT_DEFAULTS = {
13841
14137
  // Return events objects for business hours within the current view.
13842
14138
  // Abuse of our event system :(
13843
14139
  Calendar.prototype.getCurrentBusinessHourEvents = function(wholeDay) {
13844
- return this.computeBusinessHourEvents(wholeDay, this.options.businessHours);
14140
+ return this.computeBusinessHourEvents(wholeDay, this.opt('businessHours'));
13845
14141
  };
13846
14142
 
13847
14143
  // Given a raw input value from options, return events objects for business hours within the current view.
@@ -14138,18 +14434,20 @@ var BasicView = FC.BasicView = View.extend({
14138
14434
  ------------------------------------------------------------------------------------------------------------------*/
14139
14435
 
14140
14436
 
14141
- computeInitialScroll: function() {
14437
+ computeInitialDateScroll: function() {
14142
14438
  return { top: 0 };
14143
14439
  },
14144
14440
 
14145
14441
 
14146
- queryScroll: function() {
14442
+ queryDateScroll: function() {
14147
14443
  return { top: this.scroller.getScrollTop() };
14148
14444
  },
14149
14445
 
14150
14446
 
14151
- setScroll: function(scroll) {
14152
- this.scroller.setScrollTop(scroll.top);
14447
+ applyDateScroll: function(scroll) {
14448
+ if (scroll.top !== undefined) {
14449
+ this.scroller.setScrollTop(scroll.top);
14450
+ }
14153
14451
  },
14154
14452
 
14155
14453
 
@@ -14666,7 +14964,7 @@ var AgendaView = FC.AgendaView = View.extend({
14666
14964
 
14667
14965
 
14668
14966
  // Computes the initial pre-configured scroll state prior to allowing the user to change it
14669
- computeInitialScroll: function() {
14967
+ computeInitialDateScroll: function() {
14670
14968
  var scrollTime = moment.duration(this.opt('scrollTime'));
14671
14969
  var top = this.timeGrid.computeTimeTop(scrollTime);
14672
14970
 
@@ -14681,13 +14979,15 @@ var AgendaView = FC.AgendaView = View.extend({
14681
14979
  },
14682
14980
 
14683
14981
 
14684
- queryScroll: function() {
14982
+ queryDateScroll: function() {
14685
14983
  return { top: this.scroller.getScrollTop() };
14686
14984
  },
14687
14985
 
14688
14986
 
14689
- setScroll: function(scroll) {
14690
- this.scroller.setScrollTop(scroll.top);
14987
+ applyDateScroll: function(scroll) {
14988
+ if (scroll.top !== undefined) {
14989
+ this.scroller.setScrollTop(scroll.top);
14990
+ }
14691
14991
  },
14692
14992
 
14693
14993