App.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  1. /*
  2. * This calendar application was forked from Ext Calendar Pro
  3. * and contributed to Ext JS as an advanced example of what can
  4. * be built using and customizing Ext components and templates.
  5. *
  6. * If you find this example to be useful you should take a look at
  7. * the original project, which has more features, more examples and
  8. * is maintained on a regular basis:
  9. *
  10. * http://ext.ensible.com/products/calendar
  11. */
  12. Ext.define('Ext.calendar.App', {
  13. requires: [
  14. 'Ext.Viewport',
  15. 'Ext.layout.container.Border',
  16. 'Ext.picker.Date',
  17. 'Ext.calendar.util.Date',
  18. 'Ext.calendar.CalendarPanel',
  19. 'Ext.calendar.data.MemoryCalendarStore',
  20. 'Ext.calendar.data.MemoryEventStore',
  21. 'Ext.calendar.data.Events',
  22. 'Ext.calendar.data.Calendars',
  23. 'Ext.calendar.form.EventWindow'
  24. ],
  25. constructor : function() {
  26. // Minor workaround for OSX Lion scrollbars
  27. this.checkScrollOffset();
  28. // This is an example calendar store that enables event color-coding
  29. this.calendarStore = Ext.create('Ext.calendar.data.MemoryCalendarStore', {
  30. data: Ext.calendar.data.Calendars.getData()
  31. });
  32. // A sample event store that loads static JSON from a local file. Obviously a real
  33. // implementation would likely be loading remote data via an HttpProxy, but the
  34. // underlying store functionality is the same.
  35. this.eventStore = Ext.create('Ext.calendar.data.MemoryEventStore', {
  36. data: Ext.calendar.data.Events.getData()
  37. });
  38. // This is the app UI layout code. All of the calendar views are subcomponents of
  39. // CalendarPanel, but the app title bar and sidebar/navigation calendar are separate
  40. // pieces that are composed in app-specific layout code since they could be omitted
  41. // or placed elsewhere within the application.
  42. Ext.create('Ext.Viewport', {
  43. layout: 'border',
  44. renderTo: 'calendar-ct',
  45. items: [{
  46. id: 'app-header',
  47. region: 'north',
  48. height: 35,
  49. border: false,
  50. contentEl: 'app-header-content'
  51. },{
  52. id: 'app-center',
  53. title: '...', // will be updated to the current view's date range
  54. region: 'center',
  55. layout: 'border',
  56. listeners: {
  57. 'afterrender': function(){
  58. Ext.getCmp('app-center').header.addCls('app-center-header');
  59. }
  60. },
  61. items: [{
  62. id:'app-west',
  63. region: 'west',
  64. width: 179,
  65. border: false,
  66. items: [{
  67. xtype: 'datepicker',
  68. id: 'app-nav-picker',
  69. cls: 'ext-cal-nav-picker',
  70. listeners: {
  71. 'select': {
  72. fn: function(dp, dt){
  73. Ext.getCmp('app-calendar').setStartDate(dt);
  74. },
  75. scope: this
  76. }
  77. }
  78. }]
  79. },{
  80. xtype: 'calendarpanel',
  81. eventStore: this.eventStore,
  82. calendarStore: this.calendarStore,
  83. border: false,
  84. id:'app-calendar',
  85. region: 'center',
  86. activeItem: 3, // month view
  87. monthViewCfg: {
  88. showHeader: true,
  89. showWeekLinks: true,
  90. showWeekNumbers: true
  91. },
  92. listeners: {
  93. 'eventclick': {
  94. fn: function(vw, rec, el){
  95. this.showEditWindow(rec, el);
  96. this.clearMsg();
  97. },
  98. scope: this
  99. },
  100. 'eventover': function(vw, rec, el){
  101. //console.log('Entered evt rec='+rec.data.Title+', view='+ vw.id +', el='+el.id);
  102. },
  103. 'eventout': function(vw, rec, el){
  104. //console.log('Leaving evt rec='+rec.data.Title+', view='+ vw.id +', el='+el.id);
  105. },
  106. 'eventadd': {
  107. fn: function(cp, rec){
  108. this.showMsg('Event '+ rec.data.Title +' was added');
  109. },
  110. scope: this
  111. },
  112. 'eventupdate': {
  113. fn: function(cp, rec){
  114. this.showMsg('Event '+ rec.data.Title +' was updated');
  115. },
  116. scope: this
  117. },
  118. 'eventcancel': {
  119. fn: function(cp, rec){
  120. // edit canceled
  121. },
  122. scope: this
  123. },
  124. 'viewchange': {
  125. fn: function(p, vw, dateInfo){
  126. if(this.editWin){
  127. this.editWin.hide();
  128. }
  129. if(dateInfo){
  130. // will be null when switching to the event edit form so ignore
  131. Ext.getCmp('app-nav-picker').setValue(dateInfo.activeDate);
  132. this.updateTitle(dateInfo.viewStart, dateInfo.viewEnd);
  133. }
  134. },
  135. scope: this
  136. },
  137. 'dayclick': {
  138. fn: function(vw, dt, ad, el){
  139. this.showEditWindow({
  140. StartDate: dt,
  141. IsAllDay: ad
  142. }, el);
  143. this.clearMsg();
  144. },
  145. scope: this
  146. },
  147. 'rangeselect': {
  148. fn: function(win, dates, onComplete){
  149. this.showEditWindow(dates);
  150. this.editWin.on('hide', onComplete, this, {single:true});
  151. this.clearMsg();
  152. },
  153. scope: this
  154. },
  155. 'eventmove': {
  156. fn: function(vw, rec){
  157. var mappings = Ext.calendar.data.EventMappings,
  158. time = rec.data[mappings.IsAllDay.name] ? '' : ' \\a\\t g:i a';
  159. rec.commit();
  160. this.showMsg('Event '+ rec.data[mappings.Title.name] +' was moved to '+
  161. Ext.Date.format(rec.data[mappings.StartDate.name], ('F jS'+time)));
  162. },
  163. scope: this
  164. },
  165. 'eventresize': {
  166. fn: function(vw, rec){
  167. rec.commit();
  168. this.showMsg('Event '+ rec.data.Title +' was updated');
  169. },
  170. scope: this
  171. },
  172. 'eventdelete': {
  173. fn: function(win, rec){
  174. this.eventStore.remove(rec);
  175. this.showMsg('Event '+ rec.data.Title +' was deleted');
  176. },
  177. scope: this
  178. },
  179. 'initdrag': {
  180. fn: function(vw){
  181. if(this.editWin && this.editWin.isVisible()){
  182. this.editWin.hide();
  183. }
  184. },
  185. scope: this
  186. }
  187. }
  188. }]
  189. }]
  190. });
  191. },
  192. // The edit popup window is not part of the CalendarPanel itself -- it is a separate component.
  193. // This makes it very easy to swap it out with a different type of window or custom view, or omit
  194. // it altogether. Because of this, it's up to the application code to tie the pieces together.
  195. // Note that this function is called from various event handlers in the CalendarPanel above.
  196. showEditWindow : function(rec, animateTarget){
  197. if(!this.editWin){
  198. this.editWin = Ext.create('Ext.calendar.form.EventWindow', {
  199. calendarStore: this.calendarStore,
  200. listeners: {
  201. 'eventadd': {
  202. fn: function(win, rec){
  203. win.hide();
  204. rec.data.IsNew = false;
  205. this.eventStore.add(rec);
  206. this.eventStore.sync();
  207. this.showMsg('Event '+ rec.data.Title +' was added');
  208. },
  209. scope: this
  210. },
  211. 'eventupdate': {
  212. fn: function(win, rec){
  213. win.hide();
  214. rec.commit();
  215. this.eventStore.sync();
  216. this.showMsg('Event '+ rec.data.Title +' was updated');
  217. },
  218. scope: this
  219. },
  220. 'eventdelete': {
  221. fn: function(win, rec){
  222. this.eventStore.remove(rec);
  223. this.eventStore.sync();
  224. win.hide();
  225. this.showMsg('Event '+ rec.data.Title +' was deleted');
  226. },
  227. scope: this
  228. },
  229. 'editdetails': {
  230. fn: function(win, rec){
  231. win.hide();
  232. Ext.getCmp('app-calendar').showEditForm(rec);
  233. }
  234. }
  235. }
  236. });
  237. }
  238. this.editWin.show(rec, animateTarget);
  239. },
  240. // The CalendarPanel itself supports the standard Panel title config, but that title
  241. // only spans the calendar views. For a title that spans the entire width of the app
  242. // we added a title to the layout's outer center region that is app-specific. This code
  243. // updates that outer title based on the currently-selected view range anytime the view changes.
  244. updateTitle: function(startDt, endDt){
  245. var p = Ext.getCmp('app-center'),
  246. fmt = Ext.Date.format;
  247. if(Ext.Date.clearTime(startDt).getTime() == Ext.Date.clearTime(endDt).getTime()){
  248. p.setTitle(fmt(startDt, 'F j, Y'));
  249. }
  250. else if(startDt.getFullYear() == endDt.getFullYear()){
  251. if(startDt.getMonth() == endDt.getMonth()){
  252. p.setTitle(fmt(startDt, 'F j') + ' - ' + fmt(endDt, 'j, Y'));
  253. }
  254. else{
  255. p.setTitle(fmt(startDt, 'F j') + ' - ' + fmt(endDt, 'F j, Y'));
  256. }
  257. }
  258. else{
  259. p.setTitle(fmt(startDt, 'F j, Y') + ' - ' + fmt(endDt, 'F j, Y'));
  260. }
  261. },
  262. // This is an application-specific way to communicate CalendarPanel event messages back to the user.
  263. // This could be replaced with a function to do "toast" style messages, growl messages, etc. This will
  264. // vary based on application requirements, which is why it's not baked into the CalendarPanel.
  265. showMsg: function(msg){
  266. Ext.fly('app-msg').update(msg).removeCls('x-hidden');
  267. },
  268. clearMsg: function(){
  269. Ext.fly('app-msg').update('').addCls('x-hidden');
  270. },
  271. // OSX Lion introduced dynamic scrollbars that do not take up space in the
  272. // body. Since certain aspects of the layout are calculated and rely on
  273. // scrollbar width, we add a special class if needed so that we can apply
  274. // static style rules rather than recalculate sizes on each resize.
  275. checkScrollOffset: function() {
  276. var scrollbarWidth = Ext.getScrollbarSize ? Ext.getScrollbarSize().width : Ext.getScrollBarWidth();
  277. // We check for less than 3 because the Ext scrollbar measurement gets
  278. // slightly padded (not sure the reason), so it's never returned as 0.
  279. if (scrollbarWidth < 3) {
  280. Ext.getBody().addCls('x-no-scrollbar');
  281. }
  282. if (Ext.isWindows) {
  283. Ext.getBody().addCls('x-win');
  284. }
  285. }
  286. },
  287. function() {
  288. /*
  289. * A few Ext overrides needed to work around issues in the calendar
  290. */
  291. Ext.form.Basic.override({
  292. reset: function() {
  293. var me = this;
  294. // This causes field events to be ignored. This is a problem for the
  295. // DateTimeField since it relies on handling the all-day checkbox state
  296. // changes to refresh its layout. In general, this batching is really not
  297. // needed -- it was an artifact of pre-4.0 performance issues and can be removed.
  298. //me.batchLayouts(function() {
  299. me.getFields().each(function(f) {
  300. f.reset();
  301. });
  302. //});
  303. return me;
  304. }
  305. });
  306. // Currently MemoryProxy really only functions for read-only data. Since we want
  307. // to simulate CRUD transactions we have to at the very least allow them to be
  308. // marked as completed and successful, otherwise they will never filter back to the
  309. // UI components correctly.
  310. Ext.data.MemoryProxy.override({
  311. updateOperation: function(operation, callback, scope) {
  312. operation.setCompleted();
  313. operation.setSuccessful();
  314. Ext.callback(callback, scope || me, [operation]);
  315. },
  316. create: function() {
  317. this.updateOperation.apply(this, arguments);
  318. },
  319. update: function() {
  320. this.updateOperation.apply(this, arguments);
  321. },
  322. destroy: function() {
  323. this.updateOperation.apply(this, arguments);
  324. }
  325. });
  326. });