fullcalendar.io-rails 3.3.1 → 3.4.0

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