/* * This calendar application was forked from Ext Calendar Pro * and contributed to Ext JS as an advanced example of what can * be built using and customizing Ext components and templates. * * If you find this example to be useful you should take a look at * the original project, which has more features, more examples and * is maintained on a regular basis: * * http://ext.ensible.com/products/calendar */ Ext.define('Ext.calendar.App', { requires: [ 'Ext.Viewport', 'Ext.layout.container.Border', 'Ext.picker.Date', 'Ext.calendar.util.Date', 'Ext.calendar.CalendarPanel', 'Ext.calendar.data.MemoryCalendarStore', 'Ext.calendar.data.MemoryEventStore', 'Ext.calendar.data.Events', 'Ext.calendar.data.Calendars', 'Ext.calendar.form.EventWindow' ], constructor : function() { // Minor workaround for OSX Lion scrollbars this.checkScrollOffset(); // This is an example calendar store that enables event color-coding this.calendarStore = Ext.create('Ext.calendar.data.MemoryCalendarStore', { data: Ext.calendar.data.Calendars.getData() }); // A sample event store that loads static JSON from a local file. Obviously a real // implementation would likely be loading remote data via an HttpProxy, but the // underlying store functionality is the same. this.eventStore = Ext.create('Ext.calendar.data.MemoryEventStore', { data: Ext.calendar.data.Events.getData() }); // This is the app UI layout code. All of the calendar views are subcomponents of // CalendarPanel, but the app title bar and sidebar/navigation calendar are separate // pieces that are composed in app-specific layout code since they could be omitted // or placed elsewhere within the application. Ext.create('Ext.Viewport', { layout: 'border', renderTo: 'calendar-ct', items: [{ xtype: 'component', id: 'app-header', region: 'north', height: 35, contentEl: 'app-header-content' },{ id: 'app-center', title: '...', // will be updated to the current view's date range region: 'center', layout: 'border', listeners: { 'afterrender': function(){ Ext.getCmp('app-center').header.addCls('app-center-header'); } }, items: [{ xtype: 'container', id:'app-west', region: 'west', width: Ext.themeName === 'neptune' ? 214 : 179, items: [{ xtype: 'datepicker', id: 'app-nav-picker', cls: 'ext-cal-nav-picker', listeners: { 'select': { fn: function(dp, dt){ Ext.getCmp('app-calendar').setStartDate(dt); }, scope: this } } }] },{ xtype: 'calendarpanel', eventStore: this.eventStore, calendarStore: this.calendarStore, border: false, id:'app-calendar', region: 'center', activeItem: 3, // month view monthViewCfg: { showHeader: true, showWeekLinks: true, showWeekNumbers: true }, listeners: { 'eventclick': { fn: function(vw, rec, el){ this.showEditWindow(rec, el); this.clearMsg(); }, scope: this }, 'eventover': function(vw, rec, el){ //console.log('Entered evt rec='+rec.data.Title+', view='+ vw.id +', el='+el.id); }, 'eventout': function(vw, rec, el){ //console.log('Leaving evt rec='+rec.data.Title+', view='+ vw.id +', el='+el.id); }, 'eventadd': { fn: function(cp, rec){ this.showMsg('Event '+ rec.data.Title +' was added'); }, scope: this }, 'eventupdate': { fn: function(cp, rec){ this.showMsg('Event '+ rec.data.Title +' was updated'); }, scope: this }, 'eventcancel': { fn: function(cp, rec){ // edit canceled }, scope: this }, 'viewchange': { fn: function(p, vw, dateInfo){ if(this.editWin){ this.editWin.hide(); } if(dateInfo){ // will be null when switching to the event edit form so ignore Ext.getCmp('app-nav-picker').setValue(dateInfo.activeDate); this.updateTitle(dateInfo.viewStart, dateInfo.viewEnd); } }, scope: this }, 'dayclick': { fn: function(vw, dt, ad, el){ this.showEditWindow({ StartDate: dt, IsAllDay: ad }, el); this.clearMsg(); }, scope: this }, 'rangeselect': { fn: function(win, dates, onComplete){ this.showEditWindow(dates); this.editWin.on('hide', onComplete, this, {single:true}); this.clearMsg(); }, scope: this }, 'eventmove': { fn: function(vw, rec){ var mappings = Ext.calendar.data.EventMappings, time = rec.data[mappings.IsAllDay.name] ? '' : ' \\a\\t g:i a'; rec.commit(); this.showMsg('Event '+ rec.data[mappings.Title.name] +' was moved to '+ Ext.Date.format(rec.data[mappings.StartDate.name], ('F jS'+time))); }, scope: this }, 'eventresize': { fn: function(vw, rec){ rec.commit(); this.showMsg('Event '+ rec.data.Title +' was updated'); }, scope: this }, 'eventdelete': { fn: function(win, rec){ this.eventStore.remove(rec); this.showMsg('Event '+ rec.data.Title +' was deleted'); }, scope: this }, 'initdrag': { fn: function(vw){ if(this.editWin && this.editWin.isVisible()){ this.editWin.hide(); } }, scope: this } } }] }] }); }, // The edit popup window is not part of the CalendarPanel itself -- it is a separate component. // This makes it very easy to swap it out with a different type of window or custom view, or omit // it altogether. Because of this, it's up to the application code to tie the pieces together. // Note that this function is called from various event handlers in the CalendarPanel above. showEditWindow : function(rec, animateTarget){ if(!this.editWin){ this.editWin = Ext.create('Ext.calendar.form.EventWindow', { calendarStore: this.calendarStore, listeners: { 'eventadd': { fn: function(win, rec){ win.hide(); rec.data.IsNew = false; this.eventStore.add(rec); this.eventStore.sync(); this.showMsg('Event '+ rec.data.Title +' was added'); }, scope: this }, 'eventupdate': { fn: function(win, rec){ win.hide(); rec.commit(); this.eventStore.sync(); this.showMsg('Event '+ rec.data.Title +' was updated'); }, scope: this }, 'eventdelete': { fn: function(win, rec){ this.eventStore.remove(rec); this.eventStore.sync(); win.hide(); this.showMsg('Event '+ rec.data.Title +' was deleted'); }, scope: this }, 'editdetails': { fn: function(win, rec){ win.hide(); Ext.getCmp('app-calendar').showEditForm(rec); } } } }); } this.editWin.show(rec, animateTarget); }, // The CalendarPanel itself supports the standard Panel title config, but that title // only spans the calendar views. For a title that spans the entire width of the app // we added a title to the layout's outer center region that is app-specific. This code // updates that outer title based on the currently-selected view range anytime the view changes. updateTitle: function(startDt, endDt){ var p = Ext.getCmp('app-center'), fmt = Ext.Date.format; if(Ext.Date.clearTime(startDt).getTime() == Ext.Date.clearTime(endDt).getTime()){ p.setTitle(fmt(startDt, 'F j, Y')); } else if(startDt.getFullYear() == endDt.getFullYear()){ if(startDt.getMonth() == endDt.getMonth()){ p.setTitle(fmt(startDt, 'F j') + ' - ' + fmt(endDt, 'j, Y')); } else{ p.setTitle(fmt(startDt, 'F j') + ' - ' + fmt(endDt, 'F j, Y')); } } else{ p.setTitle(fmt(startDt, 'F j, Y') + ' - ' + fmt(endDt, 'F j, Y')); } }, // This is an application-specific way to communicate CalendarPanel event messages back to the user. // This could be replaced with a function to do "toast" style messages, growl messages, etc. This will // vary based on application requirements, which is why it's not baked into the CalendarPanel. showMsg: function(msg){ Ext.fly('app-msg').update(msg).removeCls('x-hidden'); }, clearMsg: function(){ Ext.fly('app-msg').update('').addCls('x-hidden'); }, // OSX Lion introduced dynamic scrollbars that do not take up space in the // body. Since certain aspects of the layout are calculated and rely on // scrollbar width, we add a special class if needed so that we can apply // static style rules rather than recalculate sizes on each resize. checkScrollOffset: function() { var scrollbarWidth = Ext.getScrollbarSize ? Ext.getScrollbarSize().width : Ext.getScrollBarWidth(); // We check for less than 3 because the Ext scrollbar measurement gets // slightly padded (not sure the reason), so it's never returned as 0. if (scrollbarWidth < 3) { Ext.getBody().addCls('x-no-scrollbar'); } if (Ext.isWindows) { Ext.getBody().addCls('x-win'); } } }, function() { /* * A few Ext overrides needed to work around issues in the calendar */ Ext.form.Basic.override({ reset: function() { var me = this; // This causes field events to be ignored. This is a problem for the // DateTimeField since it relies on handling the all-day checkbox state // changes to refresh its layout. In general, this batching is really not // needed -- it was an artifact of pre-4.0 performance issues and can be removed. //me.batchLayouts(function() { me.getFields().each(function(f) { f.reset(); }); //}); return me; } }); // Currently MemoryProxy really only functions for read-only data. Since we want // to simulate CRUD transactions we have to at the very least allow them to be // marked as completed and successful, otherwise they will never filter back to the // UI components correctly. Ext.data.MemoryProxy.override({ updateOperation: function(operation, callback, scope) { operation.setCompleted(); operation.setSuccessful(); Ext.callback(callback, scope || this, [operation]); }, create: function() { this.updateOperation.apply(this, arguments); }, update: function() { this.updateOperation.apply(this, arguments); }, destroy: function() { this.updateOperation.apply(this, arguments); } }); });