diff --git a/Extensible-config.js b/Extensible-config.js index d0adc8bd..b42d1e0b 100644 --- a/Extensible-config.js +++ b/Extensible-config.js @@ -3,15 +3,15 @@ Extensible = { }; /** * ================================================================================================= - * + * * THIS FILE IS FOR *DEV* MODE ONLY, NOT FOR PRODUCTION USE! - * + * * ================================================================================================= - * + * * This is intended as a development mode only convenience so that you can configure all include * paths for all Extensible examples in one place. For production deployment you should configure * your application with your own custom includes and/or Ext.Loader configuration directly. - * + * * ================================================================================================= */ Extensible.Config = { @@ -21,66 +21,69 @@ Extensible.Config = { defaults: { /** * The mode to use for loading framework files. Valid values are: - * + * * - 'release': minified single file (e.g. ext-all.js) * - 'debug': (default) non-minifed single file (e.g. ext-all-debug.js) * - 'dynamic': uses Ext.Loader to load classes individually (e.g., ext.js). NOTE: this * option does not work for IE, which will be defaulted to the 'debug' option. - * + * - 'dynamic-extensible': Loads the Extensible framework dynically and the EXT JS framework from a + * non-minified single file. This loads much faster than the 'dynamic' mode. NOTE: This + * option does not work for IE, which will be defaulted to the 'debug' option. + * * Typically the default of 'debug' is the best trade-off between code readability and * load/execution speed. If you need to step into framework files frequently during * debugging you might switch to 'dynamic' mode -- it is much slower during initial * page load but generally provides a faster and easier debugging experience. - * + * * Note that for debug and release modes to reflect any code or CSS changes made to Extensible * files you must rebuild the framework after each change using the scripts provided under * the `/build` folder (requires Java). If you cannot build the framework and you've made any * changes to Extensible files you should use dynamic mode to ensure that changes are reflected. - * + * * @config {String} mode */ - mode: 'debug', - + mode: 'dynamic-extensible', + /** * The root path to the Ext JS framework (defaults to loading 4.1.0 from the Sencha CDN via * 'http://cdn.sencha.io/ext-4.1.0-gpl/'). Path should be absolute and should end with a '/'. - * + * * Note that the Sencha CDN does not always provide the most current version of Ext JS * available (for example, support subscribers often have access to more up-to-date builds). * If the version you need is not hosted you'll have to download it locally and update this * path accordingly. - * + * * Alternate example values: - * + * * // Older Ext JS versions: * http://cdn.sencha.io/ext-4.0.2/ - * + * * // Direct to cachefly.net, e.g. if sencha.io is down: * http://extjs.cachefly.net/ext-4.1.0-gpl/ - * + * * // A custom absolute path: * http://localhost/extjs/ * http://mydomain/extjs/4.0.2/ - * + * * @config {String} extJsRoot */ extJsRoot: 'http://cdn.sencha.io/ext-4.2.0-gpl/', - + /** * The root path to the Extensible framework (defaults to the current url of this script file, * 'Extensible-config.js', which is shipped in the root folder of Extensible). Path should * be absolute and should end with a '/'. - * + * * Alternate example values: - * + * * // A custom absolute path: * http://localhost/extensible/ * http://mydomain/extensible/1.0.1/ - * + * * @config {String} extensibleRoot */ extensibleRoot: null, // initialized dynamically in getSdkPath() - + /** * True to allow the default browser behavior of caching the Extensible JS and CSS files * after initial load (defaults to true), or false to append a unique cache-buster parameter @@ -88,17 +91,17 @@ Extensible.Config = { * actively changing and debugging Extensible code). If true, the current version number of * Extensible will still be used to force a reload with each new version of the framework, but * after the initial load of each version the cached files will be used. - * + * * This option only applies when using `debug` or `dynamic` modes. In `release` mode the Extensible * version number will be used to ensure that Extensible files are always cached after the initial * load of each release and this option will be ignored. Note that when using `dynamic` mode you * would additionally have to ensure that the Ext.Loader's `disableCaching` option is true in order - * to add the cache buster parameter to each dynamically-loaded class. - * + * to add the cache buster parameter to each dynamically-loaded class. + * * Note that this option does not affect the caching of Ext JS files in any way. If you are * using dynamic loading, the Ext Loader will govern caching, otherwise the default browser * caching will be in effect. - * + * * @config {Boolean} cacheExtensible */ cacheExtensible: true, @@ -106,13 +109,13 @@ Extensible.Config = { /** * Language files to load for the Ext JS and Extensible frameworks. Valid values are ISO language codes of * supported languages. See directory src/locale for a list of supported languages. Examples are: - * + * * - 'en' * - 'en_GB' * - 'de' * - 'fr' * - etc... - * + * * NOTE: This setting will NOT work for Ext versions < 4.1 due to how the locale files were written * in 4.0.x. Because the 4.0.x locale files check for existence of classes by reference rather than * by name, they do not play nicely when loaded asynchronously (Ext may load later, causing runtime @@ -124,16 +127,16 @@ Extensible.Config = { */ language: null }, - + /** * Sets up all configurable properties and writes all includes to the document. */ init: function() { var me = this, config = window.ExtensibleDefaults || {}; - + me.isIE = /msie/.test(navigator.userAgent.toLowerCase()); - + me.mode = config.mode || me.defaults.mode; me.extJsRoot = config.extJsRoot || me.defaults.extJsRoot; me.extensibleRoot = config.extensibleRoot || me.defaults.extensibleRoot || me.getSdkPath(); @@ -143,16 +146,16 @@ Extensible.Config = { me.adjustPaths(); me.writeIncludes(); }, - + // private -- returns the current url to this script file, which is shipped in the SDK root folder getSdkPath: function() { var scripts = document.getElementsByTagName('script'), thisScriptSrc = scripts[scripts.length - 1].src, sdkPath = thisScriptSrc.substring(0, thisScriptSrc.lastIndexOf('/') + 1); - + return sdkPath; }, - + // private -- helper function for ease of deployment adjustPaths: function() { if (this.extensibleRoot.indexOf('ext.ensible.com') > -1) { @@ -160,58 +163,53 @@ Extensible.Config = { this.mode = 'release'; } }, - + includeStylesheet: function(filePath) { document.write(''); }, - + includeScript: function(filePath) { document.write(''); }, - + // private -- write out the CSS and script includes to the document writeIncludes: function() { var me = this, - cacheBuster = '?_dc=' + (me.cacheExtensible ? Extensible.version : (+new Date)), - suffixExt = '', - suffixExtensible = ''; - - switch (me.mode) { - case 'debug': - suffixExt = '-all-debug'; - suffixExtensible = '-all-debug'; - break; - - case 'release': - suffixExt = '-all'; - suffixExtensible = '-all' - // For release we want to refresh the cache on first load, but allow caching - // after that, so use the version number instead of a unique string - cacheBuster = '?_dc=' + Extensible.version; - break; - - default: - // IE does not work in dynamic mode for the Extensible examples currently - // based on how it (mis)handles loading of scripts when mixing includes - // and in-page scripts. Make sure IE always uses the regular debug versions. - if (me.isIE) { - suffixExt = '-all-debug'; - suffixExtensible = '-all-debug'; - } - else { - suffixExt = '-debug'; - suffixExtensible = '-bootstrap'; - } - } - + cacheBuster = '?_dc=' + (me.cacheExtensible ? Extensible.version : (+new Date)); + + // Include style sheets me.includeStylesheet(me.extJsRoot + 'resources/css/ext-all.css'); - me.includeStylesheet(me.extensibleRoot + 'resources/css/extensible-all.css' + cacheBuster); + if (me.mode === 'release') { + me.includeStylesheet(me.extensibleRoot + 'resources/css/extensible-all.css' + cacheBuster); + } else { + me.includeStylesheet(me.extensibleRoot + 'resources/css/calendar.css' + cacheBuster); + me.includeStylesheet(me.extensibleRoot + 'resources/css/calendar-colors.css' + cacheBuster); + me.includeStylesheet(me.extensibleRoot + 'resources/css/recurrence.css' + cacheBuster); + } me.includeStylesheet(me.extensibleRoot + 'examples/examples.css?_dc=' + Extensible.version); - - me.includeScript(me.extJsRoot + 'ext' + suffixExt + '.js'); - me.includeScript(me.extensibleRoot + 'lib/extensible' + suffixExtensible + '.js' + cacheBuster); + + // Include JS files + if (me.mode === 'debug' || me.isIE) { + // IE does not work in dynamic mode for the Extensible examples currently + // based on how it (mis)handles loading of scripts when mixing includes + // and in-page scripts. Make sure IE always uses the regular debug versions. + me.includeScript(me.extJsRoot + 'ext-all-debug.js'); + me.includeScript(me.extensibleRoot + 'lib/extensible-all-debug.js' + cacheBuster); + } else if (me.mode === 'release') { + // For release we want to refresh the cache on first load, but allow caching + // after that, so use the version number instead of a unique string + cacheBuster = '?_dc=' + Extensible.version; + me.includeScript(me.extJsRoot + 'ext-all.js'); + me.includeScript(me.extensibleRoot + 'lib/extensible-all.js' + cacheBuster); + } else { + if (me.mode === 'dynamic-extensible') { + me.includeScript(me.extJsRoot + 'ext-all-debug.js'); + } else { + me.includeScript(me.extJsRoot + 'ext-debug.js'); + } + me.includeScript(me.extensibleRoot + 'lib/extensible-bootstrap.js' + cacheBuster); + } me.includeScript(me.extensibleRoot + 'examples/examples.js?_dc=' + Extensible.version); - if (me.language) { me.includeScript(me.extJsRoot + 'locale/ext-lang-' + me.language + '.js'); me.includeScript(me.extensibleRoot + 'src/locale/extensible-lang-' + me.language + '.js' + cacheBuster); diff --git a/build/resources/extensible.jsb2 b/build/resources/extensible.jsb2 index 36777d8a..a9da86d8 100644 --- a/build/resources/extensible.jsb2 +++ b/build/resources/extensible.jsb2 @@ -38,6 +38,9 @@ },{ "text": "Month.js", "path": "../../src/calendar/template/" + },{ + "text": "AgendaBody.js", + "path": "../../src/calendar/template/" },{ "text": "CalendarScrollManager.js", "path": "../../src/calendar/dd/" @@ -128,6 +131,15 @@ },{ "text": "MultiWeek.js", "path": "../../src/calendar/view/" + },{ + "text": "AgendaHeader.js", + "path": "../../src/calendar/view/" + },{ + "text": "AgendaBody.js", + "path": "../../src/calendar/view/" + },{ + "text": "Agenda.js", + "path": "../../src/calendar/view/" },{ "text": "CalendarPanel.js", "path": "../../src/calendar/" diff --git a/examples/calendar/TestApp/App.js b/examples/calendar/TestApp/App.js index c2b18f5d..97a73440 100644 --- a/examples/calendar/TestApp/App.js +++ b/examples/calendar/TestApp/App.js @@ -1,6 +1,6 @@ Ext.Loader.setConfig({ enabled: true, - //disableCaching: false, + disableCaching: false, paths: { "Extensible": "../../../src", "Extensible.example": "../.." @@ -105,6 +105,7 @@ Ext.define('Extensible.example.calendar.TestApp.App', { //viewStartHour: 6, //viewEndHour: 18, //minEventDisplayMinutes: 15 + startDay: 0, showTime: false }, @@ -119,7 +120,19 @@ Ext.define('Extensible.example.calendar.TestApp.App', { multiWeekViewCfg: { //weekCount: 3 }, - + + agendaViewCfg: { + linkDatesToDayView: true, + dateRangeDefault: '3months' + }, + + listViewCfg: { + linkDatesToDayView: true, + dateRangeDefault: '3months', + simpleList: true, + groupBy: 'month' + }, + // Some optional CalendarPanel configs to experiment with: //readOnly: true, //showDayView: false, @@ -127,6 +140,8 @@ Ext.define('Extensible.example.calendar.TestApp.App', { //showWeekView: false, //showMultiWeekView: false, //showMonthView: false, + showAgendaView: true, + showListView: true, //showNavBar: false, //showTodayText: false, //showTime: false, @@ -135,6 +150,12 @@ Ext.define('Extensible.example.calendar.TestApp.App', { //title: 'My Calendar', // the header of the calendar, could be a subtitle for the app listeners: { + 'datechange': { + fn: function(vw, startDt, viewStart, viewEnd){ + this.updateTitle(viewStart, viewEnd); + }, + scope: this + }, 'eventclick': { fn: function(vw, rec, el){ this.clearMsg(); diff --git a/examples/calendar/basic.js b/examples/calendar/basic.js index d7574900..f4a8c7f2 100644 --- a/examples/calendar/basic.js +++ b/examples/calendar/basic.js @@ -26,7 +26,10 @@ Ext.onReady(function(){ renderTo: 'simple', title: 'Basic Calendar', width: 700, - height: 500 + height: 500, + activeItem: 3, // default to month view + showAgendaView: true, + showListView: true }); // @@ -38,6 +41,8 @@ Ext.onReady(function(){ eventStore: eventStore, renderTo: 'panel', title: 'Calendar with Panel Configs', + showAgendaView: true, + showListView: true, activeItem: 1, // default to week view width: 700, height: 500, diff --git a/examples/calendar/custom-mappings.js b/examples/calendar/custom-mappings.js index e66e8272..9d2e5abd 100644 --- a/examples/calendar/custom-mappings.js +++ b/examples/calendar/custom-mappings.js @@ -81,6 +81,9 @@ Ext.onReady(function(){ calendarStore: calendarStore, renderTo: 'cal', title: 'Custom Event Mappings', + showAgendaView: true, + showListView: true, + activeItem: 3, // default to month view width: 800, height: 700 }); diff --git a/examples/calendar/custom-views.html b/examples/calendar/custom-views.html index edf67f9d..90a293ee 100644 --- a/examples/calendar/custom-views.html +++ b/examples/calendar/custom-views.html @@ -31,7 +31,7 @@ .x-cal-default-ad .ext-cal-evm, .x-cal-default .ext-cal-picker-icon, .x-cal-default-x dl, - .x-calendar-list-menu li em .x-cal-default { + .x-calendar-agenda-menu li em .x-cal-default { background: #59638F; } diff --git a/examples/calendar/data/Events.js b/examples/calendar/data/Events.js index 6dc74aac..6887d2b8 100644 --- a/examples/calendar/data/Events.js +++ b/examples/calendar/data/Events.js @@ -8,7 +8,7 @@ Ext.define('Extensible.example.calendar.data.Events', { s = (s || 0); return Ext.Date.add(today, Ext.Date.SECOND, d + h + m + s); }; - + return { "evts" : [{ "id" : 1001, @@ -24,7 +24,7 @@ Ext.define('Extensible.example.calendar.data.Events', { "start" : makeDate(0, 11, 30), "end" : makeDate(0, 13), "loc" : "Chuy's!", - "url" : "http : //chuys.com", + "url" : "http://chuys.com", "notes" : "Order the queso", "rem" : "15" },{ diff --git a/examples/calendar/doc-types.js b/examples/calendar/doc-types.js index d6416410..46f409a7 100644 --- a/examples/calendar/doc-types.js +++ b/examples/calendar/doc-types.js @@ -75,6 +75,8 @@ Ext.onReady(function(){ }), renderTo: 'cal', title: 'Doctype Tester', + showAgendaView: true, + showListView: true, activeItem: 1, width: 800, height: 700 diff --git a/examples/calendar/localization.js b/examples/calendar/localization.js index 2270d14d..16e58722 100644 --- a/examples/calendar/localization.js +++ b/examples/calendar/localization.js @@ -108,6 +108,13 @@ Ext.onReady(function() { multiDayViewCfg: { dayCount: 5 }, + showAgendaView: true, + showListView: true, + listViewCfg: { + dateRangeDefault: '3months', + groupBy: 'month' + }, + activeItem: 4, // default to month view eventStore: Ext.create('Extensible.calendar.data.MemoryEventStore', { // defined in ../data/Events.js data: Ext.create('Extensible.example.calendar.data.Events') diff --git a/examples/calendar/remote/php/app.php b/examples/calendar/remote/php/app.php new file mode 100644 index 00000000..e69de29b diff --git a/examples/calendar/remote/recurrence.js b/examples/calendar/remote/recurrence.js index 0cca44a7..9c6680f1 100644 --- a/examples/calendar/remote/recurrence.js +++ b/examples/calendar/remote/recurrence.js @@ -1,6 +1,6 @@ Ext.Loader.setConfig({ enabled: true, - //disableCaching: false, + disableCaching: false, paths: { "Extensible": "../../../src", "Extensible.example": "../../" @@ -149,6 +149,20 @@ Ext.onReady(function() { eventStore: eventStore, calendarStore: calendarStore, title: 'Recurrence Calendar', + showAgendaView: true, + showListView: true, + + agendaViewCfg: { + linkDatesToDayView: true, + dateRangeDefault: '3months' + }, + + listViewCfg: { + linkDatesToDayView: true, + dateRangeDefault: '3months', + groupBy: 'week' + }, + // This is the magical config that enables the recurrence edit // widget to appear in the event form. Without it, any existing diff --git a/examples/calendar/remote/remote.js b/examples/calendar/remote/remote.js index df53ccad..54a1d002 100644 --- a/examples/calendar/remote/remote.js +++ b/examples/calendar/remote/remote.js @@ -1,6 +1,6 @@ Ext.Loader.setConfig({ enabled: true, - //disableCaching: false, + disableCaching: false, paths: { "Extensible": "../../../src", "Extensible.example": "../../" @@ -121,7 +121,26 @@ Ext.onReady(function() { region: 'center', // it will be used in a border layout below eventStore: eventStore, calendarStore: calendarStore, - title: 'Remote Calendar' + title: 'Remote Calendar', + showAgendaView: true, + showListView: true, + activeItem: 3, // month view + + // Any generic view options that should be applied to all sub views: + viewConfig: { + startDay: 0 + }, + + agendaViewCfg: { + linkDatesToDayView: true, + dateRangeDefault: '3months' + }, + + listViewCfg: { + linkDatesToDayView: true, + dateRangeDefault: '3months', + groupBy: 'month' + } }); Ext.create('Ext.container.Viewport', { @@ -133,6 +152,7 @@ Ext.onReady(function() { collapsible: true, split: true, autoScroll: true, + showListView: true, contentEl: 'sample-overview' // from remote.html }, calendarPanel diff --git a/examples/calendar/tabpanel.js b/examples/calendar/tabpanel.js index e469fd55..77924c1d 100644 --- a/examples/calendar/tabpanel.js +++ b/examples/calendar/tabpanel.js @@ -33,9 +33,17 @@ Ext.onReady(function(){ width: 700, height: 500, activeItem: 1, + showAgendaView: true, + showListView: true, // this is a good idea since we are in a TabPanel and we don't want // the user switching tabs on us while we are editing an event: - editModal: true + editModal: true, + + listViewCfg: { + dateRangeDefault: '3months', + groupBy: 'week' + } + }; // diff --git a/examples/calendar/window.js b/examples/calendar/window.js index 87d651fd..27c9dd62 100644 --- a/examples/calendar/window.js +++ b/examples/calendar/window.js @@ -26,6 +26,9 @@ Ext.onReady(function(){ items: { // xtype is supported: xtype: 'extensible.calendarpanel', + activeItem: 3, // default to month view + showAgendaView: true, + showListView: true, eventStore: Ext.create('Extensible.calendar.data.MemoryEventStore', { // defined in ../data/Events.js data: Ext.create('Extensible.example.calendar.data.Events') diff --git a/lib/extensible-bootstrap.js b/lib/extensible-bootstrap.js index 913e7893..ff564875 100644 --- a/lib/extensible-bootstrap.js +++ b/lib/extensible-bootstrap.js @@ -243,7 +243,7 @@ Ext.define('Extensible', { * @return {Boolean} True if the date is a week day, else false */ isWeekday: function(dt) { - return dt.getDay() % 6 !== 0; + return !this.isWeekend(dt); }, /** @@ -344,6 +344,14 @@ Ext.define('Extensible', { clearTime: function(dt, clone) { return Ext.Date.clearTime(dt, clone); + }, + + getMonth: function(dt) { + return dt.getMonth(); + }, + + getDate: function(dt) { + return dt.getDate(); } } }); diff --git a/resources/css/calendar.css b/resources/css/calendar.css index d003db7d..7d9722c1 100644 --- a/resources/css/calendar.css +++ b/resources/css/calendar.css @@ -343,9 +343,19 @@ border-left: 1px solid #C3D9FF; cursor: pointer; } +/* Mehran Ziadloo: RTL { */ +/* Original: .ext-cal-day-first { border-left: 0; } +*/ +*[style*="ltr"] .ext-cal-day-first { + border-left: 0; +} +*[style*="rtl"] .ext-cal-day-first { + border-right: 0; +} +/* RTL } */ .ext-cal-ev, .ext-cal-dtitle { @@ -535,8 +545,10 @@ td.ext-cal-dtitle-today div { -moz-border-radius:0; -webkit-border-radius:0; border-radius:0; - position: relative; + position: relative; } +/* Mehran Ziadloo : RTL { */ +/* Original { .ext-cal-ev-spanleft { -moz-border-radius-topright:5px; -moz-border-radius-bottomright:5px; @@ -546,7 +558,7 @@ td.ext-cal-dtitle-today div { border-bottom-right-radius:5px; xpadding-left:5px; } -.ext-cal-ev-spanright { +*[style*='ltr'] .ext-cal-ev-spanright { -moz-border-radius-topleft:5px; -moz-border-radius-bottomleft:5px; -webkit-border-top-left-radius:5px; @@ -555,6 +567,45 @@ td.ext-cal-dtitle-today div { border-bottom-left-radius:5px; xpadding-right:5px; } +*/ +*[style*='ltr'] .ext-cal-ev-spanleft { + -moz-border-radius-topright:5px; + -moz-border-radius-bottomright:5px; + -webkit-border-top-right-radius:5px; + -webkit-border-bottom-right-radius:5px; + border-top-right-radius:5px; + border-bottom-right-radius:5px; + xpadding-left:5px; +} +*[style*='ltr'] .ext-cal-ev-spanright { + -moz-border-radius-topleft:5px; + -moz-border-radius-bottomleft:5px; + -webkit-border-top-left-radius:5px; + -webkit-border-bottom-left-radius:5px; + border-top-left-radius:5px; + border-bottom-left-radius:5px; + xpadding-right:5px; +} +*[style*='rtl'] .ext-cal-ev-spanleft { + -moz-border-radius-topleft:5px; + -moz-border-radius-bottomleft:5px; + -webkit-border-top-left-radius:5px; + -webkit-border-bottom-left-radius:5px; + border-top-left-radius:5px; + border-bottom-left-radius:5px; + xpadding-right:5px; +} +*[style*='rtl'] .ext-cal-ev-spanright { + -moz-border-radius-topright:5px; + -moz-border-radius-bottomright:5px; + -webkit-border-top-right-radius:5px; + -webkit-border-bottom-right-radius:5px; + border-top-right-radius:5px; + border-bottom-right-radius:5px; + xpadding-left:5px; +} +/* RTL } */ + /* IE/ Opera: */ .ext-cal-ev-spanboth .ext-cal-evm { margin: 0; @@ -648,6 +699,8 @@ td.ext-cal-dtitle-today div { /* http://stackoverflow.com/questions/5148041/does-firefox-support-position-relative-on-table-elements */ display: block; } +/* Mehran Ziadloo: RTL { */ +/* Original: .ext-calendar-picker .x-form-text { padding-left: 27px; } @@ -670,6 +723,55 @@ td.ext-cal-dtitle-today div { top: 3px; left: 4px; } +*/ +*[style*="ltr"] .ext-calendar-picker .x-form-text { + padding-left: 27px; +} +*[style*="ltr"] .ext-calendar-picker .x-combo-list-item { + padding-left: 24px; +} +*[style*="ltr"] .ext-cal-picker-icon { + width: 20px; + height: 16px; + margin-right: 5px; + -moz-border-radius:5px; + -webkit-border-radius:5px; + border-radius:5px; + float: left; +} +/* This is the selected icon that always shows: */ +*[style*="ltr"] .ext-cal-picker-mainicon { + float: none; + position: absolute; + top: 3px; + left: 4px; +} + +*[style*="rtl"] .ext-calendar-picker .x-form-text { + padding-right: 27px; +} +*[style*="rtl"] .ext-calendar-picker .x-combo-list-item { + padding-right: 24px; +} +*[style*="rtl"] .ext-cal-picker-icon { + width: 20px; + height: 16px; + margin-left: 5px; + -moz-border-radius:5px; + -webkit-border-radius:5px; + border-radius:5px; + float: right; +} +/* This is the selected icon that always shows: */ +*[style*="rtl"] .ext-cal-picker-mainicon { + float: none; + position: absolute; + top: 3px; + right: 4px; +} + +/* RTL } */ + /* * Event resize handle @@ -755,6 +857,66 @@ td.ext-cal-dtitle-today div { display: none; } +/* ----------------------------------------- + * Agenda view specific styles + */ +.ext-cal-agenda { + border-top: 1px solid #99BBE8; +} + +.ext-cal-agenda-hd { + border-bottom: 1px solid #99BBE8; +} + +.ext-cal-agenda-hd .x-panel-body { + background-color: #F0F4FA !important; +} +.ext-cal-agenda-hd .x-toolbar { + background-color: #F0F4FA !important; + background-image: none !important; +} + +.ext-cal-icon-evt-add { + background-image: url(../images/default/ext/add.gif) !important; +} + +.ext-cal-agenda-bd .ext-cal-evt-agenda td { + padding: 2px 10px; + font-size: 12px; + vertical-align: top; +} + +.ext-cal-agenda-bd .ext-cal-evt-agenda-details td { + padding: 1px 10px 1px 0; +} + +.ext-cal-agenda-bd .ext-cal-day-link { + cursor: pointer; +} + +.ext-cal-agenda-bd hr { + margin:10px 0; + color: #CCC; + background-color:#CCC; + height:1px; + border:0; +} + +/* new styles, adjust for white skin */ +.ext-cal-agenda-bd td.ext-cal-agenda-group-header { + color: #A7C6DF; + font-size: 16px; + line-height: 14px; + font-weight: bold; + padding-top: 10px; + padding-bottom: 0px; +} + +.ext-cal-agenda-bd .ext-cal-evt-agenda td.ext-cal-evt-hours { + padding: 2px 2px; +} + + /******************************************* * * Calendar navigation picker styles @@ -969,9 +1131,11 @@ td.ext-cal-dtitle-today div { .extensible-cal-icon-cal-show { background-image:url(../images/default/silk/calendar_view_month.png) !important; } +/* Brian: Image color_wheel.png does not exist. Can this style be removed? .extensible-cal-icon-cal-colors { background-image:url(../images/default/silk/color_wheel.png) !important; } +*/ /******************************************* * diff --git a/resources/images/default/ext/add.gif b/resources/images/default/ext/add.gif new file mode 100644 index 00000000..93195256 Binary files /dev/null and b/resources/images/default/ext/add.gif differ diff --git a/src/Extensible.js b/src/Extensible.js index 913e7893..ff564875 100644 --- a/src/Extensible.js +++ b/src/Extensible.js @@ -243,7 +243,7 @@ Ext.define('Extensible', { * @return {Boolean} True if the date is a week day, else false */ isWeekday: function(dt) { - return dt.getDay() % 6 !== 0; + return !this.isWeekend(dt); }, /** @@ -344,6 +344,14 @@ Ext.define('Extensible', { clearTime: function(dt, clone) { return Ext.Date.clearTime(dt, clone); + }, + + getMonth: function(dt) { + return dt.getMonth(); + }, + + getDate: function(dt) { + return dt.getDate(); } } }); diff --git a/src/calendar/CalendarPanel.js b/src/calendar/CalendarPanel.js index 002b6130..6d66e191 100644 --- a/src/calendar/CalendarPanel.js +++ b/src/calendar/CalendarPanel.js @@ -14,7 +14,8 @@ Ext.define('Extensible.calendar.CalendarPanel', { 'Extensible.calendar.view.Week', 'Extensible.calendar.view.Month', 'Extensible.calendar.view.MultiDay', - 'Extensible.calendar.view.MultiWeek' + 'Extensible.calendar.view.MultiWeek', + 'Extensible.calendar.view.Agenda' ], /** @@ -58,6 +59,18 @@ Ext.define('Extensible.calendar.CalendarPanel', { * If all other views are hidden, the month view will show by default even if this config is false. */ showMonthView: true, + /** + * @cfg {Boolean} showAgendaView + * True to include the agenda view (and toolbar button), false to hide them (defaults to false). + */ + showAgendaView: false, + /** + * @cfg {Boolean} showListView + * True to include the list view (and toolbar button), false to hide them (defaults to false). The list view + * is an instance of {@link Extensible.calendar.view.Agenda agenda view} that is preconfigured to show a simple list + * of events rather than an agenda style list of events. + */ + showListView: false, /** * @cfg {Boolean} showNavBar * True to display the calendar navigation toolbar, false to hide it (defaults to true). Note that @@ -111,6 +124,11 @@ Ext.define('Extensible.calendar.CalendarPanel', { * Text to use for the 'Jump to:' navigation label. */ jumpToText: 'Jump to:', + /** + * @cfg {String} jumpToDateFormat + * Date format to use for the 'Jump to:' navigation date field. + */ + jumpToDateFormat: 'y/n/j', /** * @cfg {String} goText * Text to use for the 'Go' navigation button. @@ -151,6 +169,16 @@ Ext.define('Extensible.calendar.CalendarPanel', { * Text to use for the 'Month' nav bar button. */ monthText: 'Month', + /** + * @cfg {String} agendaText + * Text to use for the 'Agenda' nav bar button. + */ + agendaText: 'Agenda', + /** + * @cfg {String} listText + * Text to use for the 'List' nav bar button. + */ + listText: 'List', /** * @cfg {Boolean} editModal * True to show the default event editor window modally over the entire page, false to allow user @@ -215,6 +243,17 @@ Ext.define('Extensible.calendar.CalendarPanel', { * A config object that will be applied only to the {@link Extensible.calendar.view.Month MonthView} * managed by this CalendarPanel. */ + /** + * @cfg {Object} agendaViewCfg + * A config object that will be applied only to the {@link Extensible.calendar.view.Agenda agenda view} managed + * by this CalendarPanel. + */ + /** + * @cfg {Object} listViewCfg + * A config object that will be applied only to the {@link Extensible.calendar.view.Agenda list view} managed + * by this CalendarPanel. List view is an instance of {@link Extensible.calendar.view.Agenda agenda view} that is + * preconfigured to show a simple list of events rather than a agenda style list of events. + */ /** * @cfg {Object} editViewCfg * A config object that will be applied only to the {@link Extensible.calendar.form.EventDetails @@ -294,14 +333,26 @@ Ext.define('Extensible.calendar.CalendarPanel', { }); this.viewCount++; } - if(this.showMonthView || this.viewCount === 0) { + if(this.showMonthView) { this.tbar.items.push({ id: this.id+'-tb-month', text: this.monthText, handler: this.onMonthNavClick, scope: this, toggleGroup: this.id+'-tb-views' }); this.viewCount++; this.showMonthView = true; } - + if(this.showAgendaView){ + this.tbar.items.push({ + id: this.id+'-tb-agenda', text: this.agendaText, handler: this.onAgendaNavClick, scope: this, toggleGroup: this.id+'-tb-views' + }); + this.viewCount++; + } + if(this.showListView || this.viewCount === 0){ + this.tbar.items.push({ + id: this.id+'-tb-list', text: this.listText, handler: this.onListNavClick, scope: this, toggleGroup: this.id+'-tb-views' + }); + this.viewCount++; + } + var idx = this.viewCount-1; this.activeItem = (this.activeItem === undefined ? idx : (this.activeItem > idx ? idx : this.activeItem)); @@ -630,7 +681,62 @@ Ext.define('Extensible.calendar.CalendarPanel', { this.initEventRelay(month); this.add(month); } + if(this.showAgendaView){ + var agenda = Ext.applyIf({ + xtype: 'extensible.agendaview', + title: this.agendaText, + listeners: { + 'dayclick': { + fn: function(vw, dt){ + this.showDay(dt); + }, + scope: this + }, + 'datechange': { + fn: function(){ + // AgendaView allows the changing of start and end dates from within in the view. Update + // the nav state this happens. + this.updateNavState(); + }, + scope: this + } + } + }, sharedViewCfg); + + agenda = Ext.apply(Ext.apply(agenda, this.viewConfig), this.agendaViewCfg); + agenda.id = this.id+'-agenda'; + this.initEventRelay(agenda); + this.add(agenda); + } + if(this.showListView){ + var list = Ext.applyIf({ + xtype: 'extensible.agendaview', + title: this.listText, + simpleList: true, + groupBy: 'month', + listeners: { + 'dayclick': { + fn: function(vw, dt){ + this.showDay(dt); + }, + scope: this + }, + 'datechange': { + fn: function(){ + // AgendaView allows the changing of start and end dates from within in the view. Update + // the nav state this happens. + this.updateNavState(); + }, + scope: this + } + } + }, sharedViewCfg); + list = Ext.apply(Ext.apply(list, this.viewConfig), this.listViewCfg); + list.id = this.id+'-list'; + this.initEventRelay(list); + this.add(list); + } this.add(Ext.applyIf({ xtype: 'extensible.eventeditform', id: this.id+'-edit', @@ -900,6 +1006,11 @@ Ext.define('Extensible.calendar.CalendarPanel', { this.fireViewChange(); return this; }, + + // private + showDay: function(dt) { + this.setActiveView(this.id+'-day', dt); + }, showWeek: function(dt) { this.setActiveView(this.id+'-week', dt); @@ -952,7 +1063,17 @@ Ext.define('Extensible.calendar.CalendarPanel', { onMonthNavClick: function() { this.setActiveView(this.id+'-month'); }, - + + // private + onAgendaNavClick: function(){ + this.setActiveView(this.id+'-agenda'); + }, + + // private + onListNavClick: function(){ + this.setActiveView(this.id+'-list'); + }, + /** * Return the calendar view that is currently active, which will be a subclass of * {@link Extensible.calendar.view.AbstractCalendar AbstractCalendar}. diff --git a/src/calendar/form/EventWindow.js b/src/calendar/form/EventWindow.js index f289ab73..99362b3d 100644 --- a/src/calendar/form/EventWindow.js +++ b/src/calendar/form/EventWindow.js @@ -66,6 +66,7 @@ Ext.define('Extensible.calendar.form.EventWindow', { // General configs closeAction: 'hide', + closable: false, modal: false, resizable: false, constrain: true, @@ -77,7 +78,18 @@ Ext.define('Extensible.calendar.form.EventWindow', { formPanelConfig: { border: false }, - + + /** + * Add close tool to panel header. When closing the editor it is important to cleanup the record if dirty. + * Handle it the same way as the cancel button. + */ + tools: [{ + type:'close', + handler: function(evt, el, header){ + header.ownerCt.onCancel(); + } + }], + /** * @cfg {Boolean} allowDefaultAdd * @since 1.6.0 @@ -309,7 +321,7 @@ Ext.define('Extensible.calendar.form.EventWindow', { rec.data[EventMappings.EndDate.name] = end; rec.data[EventMappings.IsAllDay.name] = !!o[EventMappings.IsAllDay.name] || - (start.getDate() !== Extensible.Date.add(end, {millis: 1}).getDate()); + (Extensible.Date.getDate(start) !== Extensible.Date.getDate(Extensible.Date.add(end, {millis: 1}))); rec.data[EventMappings.CalendarId.name] = me.calendarStore ? me.calendarStore.getAt(0).data[Extensible.calendar.data.CalendarMappings.CalendarId.name] : ''; diff --git a/src/calendar/template/AgendaBody.js b/src/calendar/template/AgendaBody.js new file mode 100644 index 00000000..bc934ea2 --- /dev/null +++ b/src/calendar/template/AgendaBody.js @@ -0,0 +1,551 @@ +/** + * @class Extensible.calendar.template.AgendaBody + * @extends Ext.XTemplate + * + *
This class is currently beta code and the API is still subject to change before the next release.
+ * + *This is the template used to render the {@link Extensible.calendar.view.AgendaBody AgendaBody}.
+ * + *This template is automatically bound to the underlying event store by the + * calendar components and expects records of type {@link Extensible.calendar.data.EventModel}.
+ * + * @author Gabriel Sidler, sidler@teamup.com + * @constructor + * @param {Object} config The config object + */ +Ext.define('Extensible.calendar.template.AgendaBody', { + extend: 'Ext.XTemplate', + + requires: [], + + /** + * @cfg {Boolean} linkDatesToDayView + * True to link dates to the {@link Extensible.calendar.view.Day day view}. + */ + linkDatesToDayView: true, + + /** + * @cfg {Boolean} simpleList + *If true, a simple list of events is displayed, else, an agenda-style list is displayed. + * Defaults to false.
+ */ + simpleList: false, + + /** + * @cfg {String} groupBy + *Defines the grouping to be applied to the list of events. This property only has an effect if property + * {@link #simpleList} is true. Supported values are month, week and none. Any other + * values will disable grouping. Default value is none.
+ */ + groupBy: 'none', + + /** + * @cfg {String} dayDateFormat + * The date format for day's date in the list of events (defaults to 'D M j'). + */ + dayDateFormat: 'D M j', + /** + * @cfg {String} hourFormat + * The format for event start and end times (defaults to 'g:ia'). + */ + hourFormat: 'g:ia', + /** + * @cfg {String} allDayText + * Text used to display in times column for all-day events and for events that last the entire day. + */ + allDayText: 'All day', + /** + * @cfg {String} locationText + * Label used for the event location output. + */ + locationText: 'Location', + /** + * @cfg {String} webLinkText + * Label used for the web link output. + */ + webLinkText: 'Web Link', + /** + * @cfg {String} notesText + * Label used for the event notes output. + */ + notesText: 'Notes', + /** + * @cfg {String} noEventsText + * Text used where there are no events for the selected date range. + */ + noEventsText: 'There are no events for the selected date range.', + + /** + * @cfg {String} prevLinkText + * Text used for the previous link. + */ + prevLinkText: 'Previous', + + /** + * @cfg {String} nextLinkText + * Text used for the next link. + */ + nextLinkText: 'Next', + + /** + * @cfg {String} reminderTooltip + * Text used as tooltip for the reminder icon. + */ + reminderTooltip: 'Reminder is activated', + + /** + * @cfg {String} recurringTooltip + * Text used as tooltip for the reminder icon. + */ + recurringTooltip: 'Recurring event', + + /** + * @cfg {String} showEventDetails + * If true, events are displayed with all details, otherwise only a one-line summary is shown. + */ + showEventDetails: false, + /** + * @cfg {Integer} maxNotesLength + * The maximum number of characters shown for the notes text. + */ + maxNotesLength: 100, + /** + * @cfg {String} prevLinkSelector + * The class name applied to the previous link. + */ + prevLinkSelector: 'ext-cal-agenda-bd-prev-link', + /** + * @cfg {String} nextLinkSelector + * The class name applied to the previous link. + */ + nextLinkSelector: 'ext-cal-agenda-bd-next-link', + + + // private + constructor: function(config){ + + Ext.apply(this, config); + + // AgendaBody support two templates, an agenda list template and a simple list template. + if (this.simpleList){ + Extensible.calendar.template.AgendaBody.superclass.constructor.call(this, this.getTemplateForSimpleList()); + } else { + Extensible.calendar.template.AgendaBody.superclass.constructor.call(this, this.getTemplateForAgendaList()); + } + }, + + // private + applyTemplate : function(o){ + if (Ext.getVersion().isLessThan('4.1')) { + return Extensible.calendar.template.AgendaBody.superclass.applyTemplate.call(this, o); + } + else { + return this.applyOut(o, []).join(''); + } + }, + + /** + * Returns the template used for the agenda list. + * @return {Array} A array of strings making up the template. + */ + getTemplateForAgendaList: function() { + return [ + '| {[Ext.Date.format(values.date, \"D M j\")]} | ', + '', + '{[Ext.Date.format(values.date, this.dayDateFormat)]}', + ' | ', + '||||||||||||||
| {[this.getEventTimesMarkupForAgendaList(values, parent.date)]} | ', + '',
+ '{[this.getTitleMarkup(values)]}',
+ '
| ',
+ '||||||||||||||
| ', + this.noEventsText, + ' | |||||||||||||||
| ',
+ ' ', this.prevLinkText, ' | ', this.nextLinkText, ' ', + ' | |||||||||||||||
| {[this.getGroupHeaderMarkup(values)]} | ', + '|||||||||||||||||||
| ', + '{[Ext.Date.format(values.date, this.dayDateFormat)]}', + ' | ', + '|||||||||||||||||||
',
+ '{[this.getTitleMarkup(values)]}',
+ '
| ',
+ '|||||||||||||||||||
| ', + this.noEventsText, + ' | |||||||||||||||||||
| ',
+ ' ', this.prevLinkText, ' | ', this.nextLinkText, ' ', + ' | |||||||||||||||||||
This class is currently beta code and the API is still subject to change before the next release.
+ * + *Agenda view display events as a chronologically sorted list. It supports two types of list:
+ * + *1) Agenda lists: An agenda list is a list where for each day of the view period all events that are taking place + * on that day are listed. For example, an event that lasts seven days is listed seven times, once for each day. + * This view is very similar to the agenda view in Google calendar.
+ * + *2) Simple lists: A simple list is a list where each event is listed once, independent of the duration of the
+ * event. This is suited for event calendars or to present the results of a search for events. Simple list mode is
+ * activated by setting property {@link #simpleList} to true.
+ * Additionally, simple lists support grouping of events by month and week. Grouping is enabled with property
+ * {@link #groupBy}. If grouping is enabled, each group of events starts with a group header displaying the
+ * month or week.
Agenda view supports CRUD operations on events, filtering of events based on calendar and a selectable date + * range. The view can be switched between a summary view and a details view.
+ * + *The view is divided into two main sections: the {@link Extensible.calendar.view.AgendaHeader header} and the + * {@link Extensible.calendar.view.AgendaBody event list}. The header hosts a form and a toolbar that can be + * used to filter events, choose display options, apply action on events, etc. Both header and toolbar are + * easily configurable.
+ * + *Unlike other calendar views, this view is not actually a subclass of {@link Extensible.calendar.view.AbstractCalendar AbstractCalendar}. + * Instead it is a {@link Ext.Container} subclass that internally creates and manages the layouts of + * a {@link Extensible.calendar.view.AgendaHeader AgendaHeader} and a {@link Extensible.calendar.view.AgendaBody AgendaBody}. + * As such this class accepts any config values that are valid for AgendaHeaderView and AgendaBodyView and passes those through + * to the contained views. It also supports the interface required of any calendar view and in turn calls methods + * on the contained views as necessary.
+ * + * @author Gabriel Sidler, sidler@teamup.com + * @constructor + * @param {Object} config The config object + */ +Ext.define('Extensible.calendar.view.Agenda', { + extend: 'Ext.Container', + alias: 'widget.extensible.agendaview', + + requires: [ + 'Extensible.calendar.view.AbstractCalendar', + 'Extensible.calendar.view.AgendaHeader', + 'Extensible.calendar.view.AgendaBody' + ], + + /** + * @cfg {String} hideMode + *How this component should be hidden. Supported values are 'visibility' + * (css visibility), 'offsets' (negative offset position) and 'display' + * (css display).
+ *Note: For calendar views the default is 'offsets' rather than the Ext JS default of + * 'display' in order to preserve scroll position after hiding/showing a scrollable view like Day or Week.
+ */ + hideMode: 'offsets', + + /** + * @cfg {Boolean} simpleList + *If true, a simple list of events is displayed, else, an agenda-style list is displayed. See the introduction + * of this class for more details. Defaults to false.
+ */ + simpleList: false, + + /** + * @cfg {String} groupBy + *Defines the grouping to be applied to the list of events. This property only has an effect if property + * {@link #simpleList} is true. Supported values are month, week and none. Any other + * values will disable grouping. Default value is none.
+ */ + groupBy: 'none', + + /** + * @property ownerCalendarPanel + * @type Extensible.calendar.CalendarPanel + * If this view is hosted inside a {@link Extensible.calendar.CalendarPanel} this property will reference + * it. If the view was created directly outside of a CalendarPanel this property will be undefined. Read-only. + */ + + // private + layout: { + type: 'vbox', + align: 'stretch' + }, + + // private + isAgendaView: true, + + // private + initComponent : function(){ + + // Pass on initial configuration to sub-components + var cfg = Ext.apply({}, this.initialConfig); + + var header = Ext.applyIf({ + xtype: 'extensible.agendaheaderview', + id: this.id+'-hd', + stateful: this.stateful, + stateId: this.id+'-hd', + ownerCalendarView: this, + listeners: { + formchange: {fn: this.onFormChange, scope: this}, + addevent: {fn: this.onAddEvent, scope: this} + } + }, cfg); + + var body = Ext.applyIf({ + xtype: 'extensible.agendabodyview', + id: this.id+'-bd', + simpleList: this.simpleList, + groupBy: this.groupBy, + ownerCalendarPanel: this.ownerCalendarPanel, + ownerCalendarView: this + }, cfg); + + this.items = [header, body]; + this.addCls('ext-cal-agenda ext-cal-ct'); + + this.callParent(arguments); + }, + + // private + afterRender : function(){ + var filterConfig; + + this.callParent(arguments); + + this.header = Ext.getCmp(this.id+'-hd'); + this.body = Ext.getCmp(this.id+'-bd'); + + this.body.on('eventsrendered', this.forceSize, this); + this.on('resize', this.onResize, this); + + filterConfig = this.header.getForm().getFieldValues(); + this.body.setFilterConfig(filterConfig); + }, + + // private + refresh : function(){ + Extensible.log('refresh (AgendaView)'); + if (this.isDestroyed) { + return; + } + // this.header.refresh(); + this.body.refresh(); + }, + + + // private + onFormChange: function(header, form, field) { + var filterConfig = form.getFieldValues(); + + this.body.setFilterConfig(filterConfig); + if (field.getId() == this.id + '-hd-showdetails') { + // Refresh the header form without reloading the events + this.body.refresh(false); + } else { + // Reset start date. This will trigger a reload of the events with the changed filter settings. + this.setStartDate(this.getStartDate()); + } + }, + + // private + onAddEvent: function(hd, bt) { + var M = Extensible.calendar.data.EventMappings, + D = Ext.Date, + data = {}, + // now = new Date(), + now = this.body.getStartDate(), + today = D.clearTime(now, true); + + data[M.StartDate.name] = D.add(today, D.HOUR, now.getHours() + 1); + data[M.EndDate.name] = D.add(today, D.HOUR, now.getHours() + 2); + data[M.IsAllDay.name] = true; + + this.body.showEventEditor(data, bt.getEl()); + }, + + // private + forceSize: function(){ + // The defer call is mainly for good ol' IE, but it doesn't hurt in + // general to make sure that the window resize is good and done first + // so that we can properly calculate sizes. + /* + Ext.defer(function(){ + var ct = this.el.up('.x-panel-body'), + hd = this.el.down('.ext-cal-agenda-header'), + h = ct.getHeight() - hd.getHeight(); + + this.el.down('.ext-cal-body-ct').setHeight(h-1); + }, 1, this); + */ + }, + + // private + onResize : function(){ + this.forceSize(); + Ext.defer(this.refresh, Ext.isIE ? 1 : 0, this); //IE needs the defer + }, + + /* + * We have to "relay" this Component method so that the hidden + * state will be properly reflected when the views' active state changes + */ + doHide: function(){ + this.header.doHide.apply(this, arguments); + this.body.doHide.apply(this, arguments); + }, + + /** + * Returns the start and end boundary dates currently displayed in the view. The method + * returns an object literal that contains the following properties:
+ var bounds = view.getViewBounds();
+ alert('Start: '+bounds.start);
+ alert('End: '+bounds.end);
+
+ * @return {Object} An object literal containing the start and end values
+ */
+ getViewBounds : function(){
+ return this.body.getViewBounds();
+ },
+
+ /**
+ * Returns the start date of the view, as set by {@link #setStartDate}. Note that this may not
+ * be the first date displayed in the rendered calendar -- to get the start and end dates displayed
+ * to the user use {@link #getViewBounds}.
+ * @return {Date} The start date
+ */
+ getStartDate : function(){
+ return this.body.getStartDate();
+ },
+
+ /**
+ * Sets the start date used to calculate the view boundaries to display. The displayed view will be the
+ * earliest and latest dates that match the view requirements and contain the date passed to this function.
+ * @param {Date} dt The date used to calculate the new view boundaries
+ */
+ setStartDate: function(dt){
+ this.body.setStartDate(dt, true);
+ },
+
+ // private
+ renderItems: function(){
+ this.body.renderItems();
+ },
+
+ /**
+ * Returns true if the view is currently displaying today's date, else false.
+ * @return {Boolean} True or false
+ */
+ isToday : function(){
+ return this.body.isToday();
+ },
+
+ /**
+ * Updates the view to contain the passed date
+ * @param {Date} dt The date to display
+ * @return {Date} The new view start date
+ */
+ moveTo : function(dt){
+ var newDt = this.body.moveTo(dt, true);
+ this.header.moveTo(newDt);
+ return newDt;
+ },
+
+ /**
+ * Updates the view to the next consecutive date(s)
+ * @return {Date} The new view start date
+ */
+ moveNext : function(){
+ var newDt = this.body.moveNext(true);
+ this.header.moveTo(newDt);
+ return newDt;
+ },
+
+ /**
+ * Updates the view to the previous consecutive date(s)
+ * @return {Date} The new view start date
+ */
+ movePrev : function(){
+ var newDt = this.body.movePrev(true);
+ this.header.moveTo(newDt);
+ return newDt;
+ },
+
+ /**
+ * Shifts the view by the passed number of days relative to the currently set date
+ * @param {Number} value The number of days (positive or negative) by which to shift the view
+ * @return {Date} The new view start date
+ */
+ moveDays : function(value){
+ var newDt = this.body.moveDays(value, true);
+ this.header.moveTo(newDt);
+ return newDt;
+ },
+
+ /**
+ * Updates the view to show today
+ * @return {Date} Today's date
+ */
+ moveToday : function(){
+ var newDt = this.body.moveToday(true);
+ this.header.moveTo(newDt);
+ return newDt;
+ },
+
+ /**
+ * Show the currently configured event editor view (by default the shared instance of
+ * {@link Extensible.calendar.form.EventWindow EventEditWindow}).
+ * @param {Extensible.calendar.data.EventModel} rec The event record
+ * @param {Ext.Element/HTMLNode} animateTarget The reference element that is being edited. By default this is
+ * used as the target for animating the editor window opening and closing. If this method is being overridden to
+ * supply a custom editor this parameter can be ignored if it does not apply.
+ * @return {Extensible.calendar.view.Day} this
+ */
+ showEventEditor : function(rec, animateTarget){
+ return Extensible.calendar.view.AbstractCalendar.prototype.showEventEditor.apply(this, arguments);
+ },
+
+ /**
+ * Dismiss the currently configured event editor view (by default the shared instance of
+ * {@link Extensible.calendar.form.EventWindow EventEditWindow}, which will be hidden).
+ * @param {String} dismissMethod (optional) The method name to call on the editor that will dismiss it
+ * (defaults to 'hide' which will be called on the default editor window)
+ * @return {Extensible.calendar.view.Day} this
+ */
+ dismissEventEditor : function(dismissMethod){
+ return Extensible.calendar.view.AbstractCalendar.prototype.dismissEventEditor.apply(this, arguments);
+ }
+});
\ No newline at end of file
diff --git a/src/calendar/view/AgendaBody.js b/src/calendar/view/AgendaBody.js
new file mode 100644
index 00000000..d09bda94
--- /dev/null
+++ b/src/calendar/view/AgendaBody.js
@@ -0,0 +1,673 @@
+/**
+ * @class Extensible.calendar.view.AgendaBody
+ * @extends Extensible.calendar.view.AbstractCalendar
+ *
+ * This class is currently beta code and the API is still subject to change before the next release.
+ * + *This is the body area view within the agenda view. Normally you should not need to use this class directly + * -- instead you should use {@link Extensible.calendar.view.Agenda Agenda} view which aggregates this class and the + * {@link Extensible.calendar.view.AgendaHeader AgendaHeader} view into a single unified view + * presented by {@link Extensible.calendar.CalendarPanel CalendarPanel}.
+ * + *This component displays the list of events and supports CRUD operations on events. The layout of the events + * is controlled by template {@link Extensible.calendar.template.AgendaBody}.
+ * + * @author Gabriel Sidler, sidler@teamup.com + * @constructor + * @param {Object} config The config object + */ +Ext.define('Extensible.calendar.view.AgendaBody', { + extend: 'Extensible.calendar.view.AbstractCalendar', + alias: 'widget.extensible.agendabodyview', + + requires: [ + 'Ext.XTemplate', + 'Extensible.calendar.template.AgendaBody' + ], + + /** + * @cfg {Boolean} linkDatesToDayView + * True to link dates to the {@link Extensible.calendar.view.Day day view}. + */ + linkDatesToDayView: true, + + /** + * @cfg {String} dateRangeDefault + * Defines the default value for the date range. Supported values are: day, week, month, + * 3months and year. Defaults to month. + */ + dateRangeDefault: 'month', + + /** + * @cfg {Boolean} simpleList + *If true, a simple list of events is displayed, else, an agenda-style list is displayed. + * Defaults to false.
+ */ + simpleList: false, + + /** + * @cfg {String} groupBy + *Defines the grouping to be applied to the list of events. This property only has an effect if property + * {@link #simpleList} is true. Supported values are month, week and none. Any other + * values will disable grouping. Default value is none.
+ */ + groupBy: 'none', + + /** + * @property ownerCalendarView + * @type {Ext.Container} + * A reference to the calendar view that hosts this view. Read-only. + */ + + // private properties + /* + * Private + * @property filterConfig + * @type {Object} + * An object that contains key/value pairs to be used as the filtering configuration when loading events. + * Use method {@link #setFilterConfig} to set this property. This ensures that the new filter configuration is put + * into operation immediately. + */ + dayLinkSelector: '.ext-cal-day-link', + dayLinkIdDelimiter: 'ext-cal-day-', + prevLinkSelector: 'ext-cal-agenda-bd-prev-link', + nextLinkSelector: 'ext-cal-agenda-bd-next-link', + flex: 1, + autoScroll: true, + padding: '10 0 10 0', + + // private + initComponent : function(){ + + this.filterConfig = { + period: this.dateRangeDefault, + groupby: this.groupBy, + details: false + }; + + this.addEvents({ + /** + * @event dayclick + * Fires after the user clicks on a day date + * @param {Extensible.calendar.view.AgendaBody} this + * @param {Date} dt The date that was clicked on. + */ + dayclick: true + }); + + this.callParent(arguments); + }, + + // Private + renderTemplate : function(){ + var templateParams = this.getTemplateParams(); + + if (this.simpleList){ + templateParams.groups = this.getTemplateEventDataForSimpleList(); + } else { + templateParams.days = this.getTemplateEventDataForAgenda(); + } + if(this.tpl){ + this.tpl.overwrite(this.el, templateParams); + this.lastRenderStart = Ext.Date.clone(this.viewStart); + this.lastRenderEnd = Ext.Date.clone(this.viewEnd); + } + }, + + /** + *Returns the template event data for rendering events as a simple list (property {@link #simpleList} is true). + * Optionally, the events can be grouped by month or week. The grouping is controlled by parameter {@link #groupBy}.
+ * + *Returns an array of group objects containing an array of day objects containing and array of events. + * If grouping is disabled, then the top-level array contains only one group object. The following illustrates the + * returned data structure.
+ *
+[
+ group1,
+ group2,
+ {
+ startDt: ,
+ endDt: ,
+ weekNo: , // Exists only if grouped by week
+ weekYear: , // Exists only if grouped by week
+ days: [
+ day1,
+ day2,
+ {
+ date: date,
+ events: [
+ event1,
+ event2,
+ ....
+ ]
+ },
+ day3,
+ ....
+ ]
+ },
+ group3,
+ ....
+]
+ *
+ *
+ * @return {Array}
+ */
+ getTemplateEventDataForSimpleList : function(){
+ var M = Extensible.calendar.data.EventMappings,
+ events,
+ event,
+ groups = {},
+ groupsArray = [],
+ daysArray,
+ viewBounds = this.getViewBounds(),
+ startDtView = viewBounds.start,
+ endDtView = viewBounds.end,
+ startDtGroup, endDtGroup,
+ startDtEvent, endDtEvent,
+ Dt = Extensible.Date,
+ group,
+ groupKey,
+ dayKey,
+ weekInfo;
+
+ // Loop over all events of within view period.
+ events = this.getEvents();
+ for (var i=0; iReturns the template event data for rendering events in agenda style (property {@link #simpleList} is false).
+ * + *Returns an array of day objects containing an array of events. The following illustrates the returned data structure.
+ *
+[
+ day1,
+ day2,
+ {
+ date: date,
+ events: [
+ event1,
+ event2,
+ ....
+ ]
+ },
+ day3,
+ ....
+]
+ *
+ *
+ * @return {Array}
+ */
+ getTemplateEventDataForAgenda : function(){
+ var M = Extensible.calendar.data.EventMappings,
+ events,
+ event,
+ days = {},
+ daysArray = [],
+ viewBounds = this.getViewBounds(),
+ startDtView = viewBounds.start,
+ endDtView = viewBounds.end,
+ startDtEvent, endDtEvent,
+ currDt,
+ Dt = Extensible.Date;
+
+ // Loop over all events within view period. Single-day events produce one item per event.
+ // Multi-day events produce multiple items, one for each day.
+ events = this.getEvents();
+ for (var i=0; iIn different parts of the world weeks are numbered differently. This function covers the + * three major conventions. The convention used is determined by the configuration of the first + * day of the week.
+ *+ * First day of week Convention Region + * Sunday Week cont. Jan 1 is first week of year USA, Canada, Mexico + * Monday Week cont. first Thur is first week of year Most of Europe (ISO 8601 standard) + * Saturday Week cont. Jan 1 is first week of year Most of the Middle East + *+ * + *
For more information see + * http://en.wikipedia.org/wiki/Week_number#Week_numbering and + * + * http://www.pjh2.de/datetime/weeknumber/wnd.php?l=en#Legend.
+ * + * @param {Date} date The date for which the week information is calculated. + * @return {Object} An object literal with two attributes: weekNo and weekYear. + */ + getWeekInfo : function(date){ + var weekNo, + weekYear, + oneJan; + + // Determine week number + if (this.startDay == 0) { + // Week starts on Sunday + // Code from http://javascript.about.com/library/blweekyear.htm + oneJan = new Date(date.getFullYear(), 0, 1); + weekNo = Math.ceil((((date.getTime() - oneJan.getTime()) / 86400000) + oneJan.getDay() + 1) / 7); + } else if (this.startDay == 6) { + // Week starts on Saturday + oneJan = new Date(date.getFullYear(), 0, 1); + weekNo = Math.ceil((((date.getTime() - oneJan.getTime()) / 86400000) + oneJan.getDay() + 1) / 7); + } else { + // Week starts on Monday + weekNo = parseInt(Ext.Date.format(date, 'W'), 10) + } + + // Determine year to which week belongs. + if (date.getMonth() == 11 && weekNo == 1) { + // Date is at the end of December but week belongs to next year. + weekYear = date.getFullYear() + 1; + } else if (date.getMonth() == 0 && weekNo > 50) { + // Date is at the beginning of January but week belongs to previous year. + weekYear = date.getFullYear() - 1; + } else { + weekYear = date.getFullYear(); + } + + return { + weekNo: weekNo, + weekYear: weekYear + } + }, + + + // private + afterRender : function(){ + if(!this.tpl){ + this.tpl = Ext.create('Extensible.calendar.template.AgendaBody', { + id: this.id, + simpleList: this.simpleList, + groupBy: this.groupBy, + linkDatesToDayView: this.linkDatesToDayView, + defaultEventTitleText: this.defaultEventTitleText, + prevLinkSelector: this.prevLinkSelector, + nextLinkSelector: this.nextLinkSelector + }); + this.tpl.compile(); + } + this.addCls('ext-cal-agenda-bd ext-cal-ct'); + + this.callParent(arguments); + }, + + /** + * Returns an object containing all key/value params to be passed when loading the event store. + * Override this function if you need to pass additional parameters when loading the store. + * @return {Object} An object containing all params to be sent when loading the event store + */ + getStoreParams : function(){ + // This is needed if you require the default start and end dates to be included + var params = this.getStoreDateParams(); + + // Apply filter settings from the header form + Ext.applyIf(params, this.filterConfig); + + // Here is where you can add additional custom params, e.g.: + // params.now = Ext.Date.format(new Date(), this.dateParamFormat); + // params.foo = 'bar'; + // params.number = 123; + + return params; + }, + + // private + refresh : function(reloadData){ + Extensible.log('refresh (AgendaView)'); + if (this.isDestroyed) { + return; + } + this.callParent(arguments); + }, + + /** + * This method is here to fulfill the interface of {@link Extensible.view.AbstractCalendar}. It does not + * do anything except to confirm that events have been rendered. For this view, events are rendered by method + * {@link #renderTemplate}. + */ + renderItems : function(){ + this.fireEvent('eventsrendered', this); + }, + + /** + * Sets the filter configuration to be used when calculating view bounds and loading events. + * @param {Object} filterConfig An object of key/value pairs representing filter conditions. + */ + setFilterConfig: function(filterConfig) { + this.filterConfig = filterConfig; + this.tpl.showEventDetails = this.filterConfig.details ? true: false; + this.groupBy = this.filterConfig.groupby; + this.tpl.groupBy = this.filterConfig.groupby; + }, + + /** + * Helper function that converts a string expressing a date period into an object that can be used as + * parameter for the {@link Extensible.Date#add} function. + * @param {String} period Supported values are: day, week, month, 3months and + * year. If an unknown value is passed for period, then months is used. + * @param {Boolean} subtract If true, then the return object specifies a subtraction operation instead of an + * addition operation. Defaults to false. + */ + getDateAddParam: function(period, subtract) { + subtract = subtract || false; + + if (period == 'day') { + return subtract ? {days: -1} : {days: 1}; + } else if (period == 'week') { + return subtract ? {days: -7} : {days: 7}; + } else if (period == '3months') { + return subtract ? {months: -3} : {months: 3}; + } else if (period == 'year') { + return subtract ? {years: -1} : {years: 1}; + } else { + return subtract ? {months: -1} : {months: 1}; + } + }, + + // private + setViewBounds : function(startDate){ + var me = this, + Dt = Extensible.Date, + start = startDate || me.startDate, + period = me.filterConfig.period || this.dateRangeDefault, + addParam; + + addParam = this.getDateAddParam(period, false); + addParam.seconds = -1; + + me.viewStart = Dt.add(start, {days: 0, clearTime: true}); + me.viewEnd = Dt.add(me.viewStart, addParam); + }, + + // private + getDayEl : function(dt){ + return Ext.get(this.getDayId(dt)); + }, + + // private + getDayId : function(dt){ + if(Ext.isDate(dt)){ + dt = Ext.Date.format(dt, 'Ymd'); + } + return this.id + this.dayElIdDelimiter + dt; + }, + + /** + * Moves the view one period forward. + * @return {Date} The new view start date + */ + moveNext : function(/*private*/reload){ + var me = this, + period = me.filterConfig.period || this.dateRangeDefault, + addParam; + addParam = this.getDateAddParam(period); + return this.moveTo(Extensible.Date.add(this.viewStart, addParam), reload); + }, + + /** + * Moves the view one day backwards. + * @return {Date} The new view start date + */ + movePrev : function(/*private*/reload){ + var me = this, + period = me.filterConfig.period || this.dateRangeDefault, + addParam; + addParam = this.getDateAddParam(period, true); + return this.moveTo(Extensible.Date.add(this.viewStart, addParam), reload); + }, + + /** + * Returns true if the view is currently displaying today's date, else false. + * @return {Boolean} True or false + */ + isToday : function(){ + var today = Ext.Date.clearTime(new Date()).getTime(); + return this.viewStart.getTime() == today; + }, + + // private + onClick : function(e, t){ + var el; + + // Handle click on an existing event + if(Extensible.calendar.view.AgendaBody.superclass.onClick.apply(this, arguments)){ + // The superclass handled the click already so exit + return; + } + + // Handle click on a date. Jump to day view if active. + if(el = e.getTarget(this.dayLinkSelector, 3)){ + var dt = el.id.split(this.dayLinkIdDelimiter)[1]; + this.fireEvent('dayclick', this, Ext.Date.parseDate(dt, 'Ymd')); + } + + // Handle click on next or previous links + // ext-cal-bd-prev-link + if(el = e.getTarget('.' + this.prevLinkSelector, 3)){ + this.ownerCalendarView.movePrev(true); + } + if(el = e.getTarget('.' + this.nextLinkSelector, 3)){ + this.ownerCalendarView.moveNext(true); + } + + }, + + // inherited docs + isActiveView: function() { + var calendarPanel = this.ownerCalendarPanel, + calendarView = this.ownerCalendarView; + return (calendarPanel && calendarView && calendarPanel.getActiveView().id === calendarView.id); + } + +}); \ No newline at end of file diff --git a/src/calendar/view/AgendaHeader.js b/src/calendar/view/AgendaHeader.js new file mode 100644 index 00000000..b9724b93 --- /dev/null +++ b/src/calendar/view/AgendaHeader.js @@ -0,0 +1,479 @@ +/** + * @class Extensible.calendar.view.AgendaHeader + * @extends Ext.form.Panel + * + *This class is currently beta code and the API is still subject to change before the next release.
+ * + *This is the header area container within the {@link Extensible.calendar.view.Agenda Agenda} view. Normally you should + * not need to use this class directly -- instead you should use {@link Extensible.calendar.view.Agenda Agenda} view which + * aggregates this class and the {@link Extensible.calendar.view.AgendaBody AgendaBody} view into a single unified view + * presented by {@link Extensible.calendar.CalendarPanel CalendarPanel}.
+ * + *This header consists of a form and a toolbar. Both can easily be extended or hidden. The header form is intended + * to host filter and display configuration settings while the toolbar is useful to offers actions that can be applied + * to the list of events, for example to add events or print events.
+ * + *To modify or hide the form and the toolbar, override functions {@link #getFormConfig} and {@link #getToolbarConfig}. + * The form field values will be submitted automatically as parameters of requests to load the event store. They can + * be used on the backend to select or filter events.
+ * + * @author Gabriel Sidler, sidler@teamup.com + * @constructor + * @param {Object} config The config object + */ +Ext.define('Extensible.calendar.view.AgendaHeader', { + extend: 'Ext.form.Panel', + alias: 'widget.extensible.agendaheaderview', + + requires: [ + 'Ext.form.ComboBox', + 'Ext.Button', + 'Ext.data.Store', + 'Ext.tip.QuickTipManager' + ], + + /** + * @property ownerCalendarView + * @type Ext.Container + * A reference to the calendar view that hosts this view. Read-only. + */ + + /** + * @cfg {Boolean} simpleList + *If true, a simple list of events is displayed, else, an agenda-style list is displayed. + * Defaults to false.
+ */ + simpleList: false, + + /** + * @cfg {Boolean} readOnly + * True to prevent the view from providing CRUD capabilities, false to enable CRUD (the default). + */ + + /** + * @cfg {String} dateRangeOneDay + * The text used for date range option one day. + */ + dateRangeOneDay: 'One day', + + /** + * @cfg {String} dateRangeOneWeek + * The text used for date range option one week. + */ + dateRangeOneWeek: 'One week', + + /** + * @cfg {String} dateRangeOneMonth + * The text used for date range option one month. + */ + dateRangeOneMonth: 'One month', + + /** + * @cfg {String} dateRangeThreeMonths + * The text used for date range option 3 months. + */ + dateRangeThreeMonths: 'Three months', + + /** + * @cfg {String} dateRangeOneYear + * The text used for date range option one year. + */ + dateRangeOneYear: 'One year', + + /** + * @cfg {String} dateRangeText + * The label text used for the date range field. + */ + dateRangeText: 'Date range', + + /** + * @cfg {String} groupByMonths + * The text used for group by option Month. + */ + groupByMonths: 'Month', + + /** + * @cfg {String} groupByWeek + * The text used for group by option Week. + */ + groupByWeek: 'Week', + + /** + * @cfg {String} groupByNone + * The text used for group by option None. + */ + groupByNone: 'None', + + /** + * @cfg {String} groupByText + * The label text used for the group by field. + */ + groupByText: 'Group by', + + /** + * @cfg {String} showDetailsText + * The label text used for the details field. + */ + showDetailsText: 'Show details', + + /** + * @cfg {String} addBtnText + * The caption used for the add button. + */ + addBtnText: 'Add event', + + /** + * @cfg {String} resetBtnText + * The caption used for the reset button. + */ + resetBtnText: 'Reset', + + /** + * @cfg {String} dateRangeDefault + * Defines the default value for the date range input field. Defaults to month. See + * {@link #getDateRangeOptions} for a list of supported date range default values. + */ + dateRangeDefault: 'month', + + /** + * @cfg {String} groupBy + * Defines the default value for the groupby input field. Defaults to none. See + * {@link #getGroupByOptions} for a list of supported default values. + */ + groupBy: 'none', + + /** + * @cfg {Boolean} showDetailsDefault + * Defines the default value for the checkbox to show details. Defaults to false. + */ + showDetailsDefault: false, + + // private configs + cls: 'ext-cal-agenda-hd', + preventHeader: true, + autoHeight: true, + border: 0, + defaults: { + labelWidth: 100 + }, + + + // private + initComponent : function(){ + var tbItems = this.getToolbarConfig(); + + this.dateRangeOptions = this.getDateRangeOptions(); + this.groupByOptions = this.getGroupByOptions(); + + this.items = this.getFormConfig(); + if (this.items.length == 0) { + this.bodyStyle = {padding: '0px', border: 0}; + } else { + this.bodyStyle = {padding: '10px 10px 5px 10px'}; + } + + if (tbItems.length > 0) { + this.dockedItems = [{ + xtype: 'toolbar', + dock: 'bottom', + ui: 'default', + cls: 'ext-cal-agenda-hd-tb', + items: tbItems + }]; + } + + if (this.items.length == 0 && tbItems.length == 0) { + this.style = {borderBottom: '0px'}; + } + + this.callParent(arguments); + + this.addEvents({ + /** + * @event formchange + * Fires after the filter form changes. + * @param {Extensible.calendar.view.AgendaHeader} this + * @param {Ext.form.Basic} form The filter form. + * @param {Ext.form.field.Field} field Form field that changed. + * @param {Object} newValue New form field value. + * @param {Object} oldValue Old form field value. + * @param {Object} eOpts The options object passed to {@link Ext.util.Observable.addListener}. + */ + formchange: true, + + /** + * @event addevent + * Fires after the user clicks the add event button. + * @param {Extensible.calendar.view.AgendaHeader} this + * @param {Ext.button.Button} button The button clicked. + * @param {Event} event + * @param {Object} eOpts The options object passed to {@link Ext.util.Observable.addListener}. + */ + addevent: true + }); + + }, + + /** + *This function is called by this form panel to obtain the definition of form fields. Override this function to + * modify the form fields displayed by this panel.
+ * @return {Array} An array of Object + */ + getFormConfig: function() { + var formItems = { + xtype: 'fieldcontainer', + labelWidth: 100, + height: 45, + fieldDefaults: { + // padding: 20, + labelAlign: 'top', + width: 150, + margins: '0 20 0 0' + }, + layout: 'hbox', + items: [{ + xtype: 'combo', + id: this.id+'-daterange', + mode: 'local', + value: this.dateRangeDefault, + triggerAction: 'all', + forceSelection: true, + editable: false, + fieldLabel: this.dateRangeText, + name: 'period', + displayField: 'name', + valueField: 'value', + queryMode: 'local', + store: Ext.create('Ext.data.Store', { + fields : ['name', 'value'], + data : this.dateRangeOptions + }), + // This fixes a bug that a blank item is not properly supported. See Sencha forum and source of Ext.view.BoundList. + // http://www.sencha.com/forum/showthread.php?41431-Empty-string-as-ComboBox-entry-text&p=195882 + tpl: 'This function is called by this form panel to obtain the definition of the toolbar content. Override this function to + * modify what goes into the toolbar. If no toolbar is required, return an empty array from this function.
+ * @return {Array} An array of Object. + */ + getToolbarConfig: function() { + var result = []; + if (this.readOnly !== true) { + result.push({ + text: this.addBtnText, + iconCls: 'ext-cal-icon-evt-add', + listeners: { + click: { + fn: this.onAddEvent, + scope: this + } + } + }); + } + result.push( + '->', + { + text : this.resetBtnText, + handler: function() { + this.up('form').getForm().reset(); + } + } + ); + return result; + }, + + /** + *Returns the options available in the date range combo box. Override this function to change the available + * options for the date range select list.
+ *Returns an array of objects where each object has two attributes name and value. The + * attribute name is the display string, the attribute value is the value returned as the + * field value of the combo box. The default configuration is:
+ [
+ {name : 'One Day', value: 'day'},
+ {name : 'One Week', value: 'week'},
+ {name : 'One Month', value: 'month'},
+ {name : '3 Months', value: '3months'},
+ {name : 'One Year', value: 'year'}
+ ]
+
+ * @return {Object}
+ */
+ getDateRangeOptions: function() {
+ return [
+ {name : this.dateRangeOneDay, value: 'day'},
+ {name : this.dateRangeOneWeek, value: 'week'},
+ {name : this.dateRangeOneMonth, value: 'month'},
+ {name : this.dateRangeThreeMonths, value: '3months'},
+ {name : this.dateRangeOneYear, value: 'year'}
+ ];
+ },
+
+ /**
+ * Returns the options available in the group by combo box. Override this function to change the available + * options for the group by select list.
+ *Returns an array of objects where each object has two attributes name and value. The + * attribute name is the display string, the attribute value is the value returned as the + * field value of the combo box. The default configuration is:
+ [
+ {name : 'None', value: 'none'},
+ {name : 'Month', value: 'month'},
+ {name : 'Week', value: 'week'}
+ ]
+
+ * @return {Object}
+ */
+ getGroupByOptions: function() {
+ return [
+ {name : this.groupByNone, value: 'none'},
+ {name : this.groupByMonths, value: 'month'},
+ {name : this.groupByWeek, value: 'week'}
+ ];
+ },
+
+ /* Private
+ * Event handler that is called when the form changes.
+ * @param {Ext.form.field.Field} field
+ * @param {Object} newValue
+ * @param {Object} oldValue
+ * @param {Object} eOpts
+ */
+ onFormChange: function(field, newValue, oldValue, eOpts){
+ this.fireEvent('formchange', this, this.getForm(), field, newValue, oldValue, eOpts);
+ this.saveState();
+ },
+
+ /* Private
+ * Event handler that is called when the user clicks on the add event button.
+ * @param {Extensible.calendar.view.AgendaHeader} this
+ * @param {Ext.button.Button} bt
+ * @param {Event} e
+ * @param {Object} eOpts
+ */
+ onAddEvent: function(bt, e, eOpts){
+ this.fireEvent('addevent', this, bt, e, eOpts);
+ },
+
+ // private
+ afterRender : function(){
+ this.callParent(arguments);
+
+ this.dateRangeField = this.down('#' + this.id + '-daterange');
+ this.dateRangeField.setValue(this.dateRangeDefault);
+ this.dateRangeField.on('change', this.onFormChange, this);
+ this.groupByField = this.down('#' + this.id + '-groupby');
+ if (this.groupByField) {
+ this.groupByField.setValue(this.groupBy);
+ this.groupByField.on('change', this.onFormChange, this);
+ }
+ this.showDetailsCheckbox = this.down('#' + this.id + '-showdetails');
+ this.showDetailsCheckbox.setValue(this.showDetailsDefault);
+ this.showDetailsCheckbox.on('change', this.onFormChange, this);
+ },
+
+ // private
+ refresh : function(reloadData){
+ Extensible.log('refresh (AgendaHeader)');
+ if (this.isDestroyed) {
+ return;
+ }
+ this.callParent(arguments);
+ },
+
+ /**
+ * This method is called by the {@link Extensible.calendar.view.Agenda Agenda} view that hosts this header when the user chooses to
+ * move to a new date. The current implementation does nothing but can be overriden to update the header form if
+ * necessary.
+ * @param {Date} dt The new view start date.
+ */
+ moveTo : function(dt){
+ },
+
+ /**
+ * Returns the state to be persisted in a browser cookie. This implements function getState()
+ * from mixin Ext.state.Stateful.
+ * @return {Object}
+ */
+ getState: function() {
+ var state = {
+ daterange: this.dateRangeField.getValue(),
+ showdetails: this.showDetailsCheckbox.getValue()
+ };
+ if (this.groupByField) {
+ state.groupby = this.groupByField.getValue();
+ }
+ return state;
+ },
+
+ /**
+ * Function is called in the constructor to restore the state. This implements function applyState()
+ * from mixin Ext.state.Stateful.
+ * @param {Object} state See function getState() for the structure of state.
+ */
+ applyState: function(state) {
+ if (state) {
+ if (state.daterange) {
+ var dateRangeValues = this.getDateRangeOptions();
+ for (var i = 0; i < dateRangeValues.length; i++ ) {
+ var option = dateRangeValues[i];
+ if (option.value == state.daterange) {
+ this.dateRangeDefault = state.daterange;
+ break;
+ }
+ }
+ }
+ if (state.showdetails === true || state.showdetails === false) {
+ this.showDetailsDefault = state.showdetails;
+ }
+ if (state.groupby) {
+ var groupByValues = this.getGroupByOptions();
+ for (var i = 0; i < groupByValues.length; i++ ) {
+ var option = groupByValues[i];
+ if (option.value == state.groupby) {
+ this.groupBy = state.groupby;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+});
\ No newline at end of file
diff --git a/src/calendar/view/Day.js b/src/calendar/view/Day.js
index 75b0a551..5efa5613 100644
--- a/src/calendar/view/Day.js
+++ b/src/calendar/view/Day.js
@@ -198,6 +198,9 @@ Ext.define('Extensible.calendar.view.Day', {
refresh: function(reloadData) {
Extensible.log('refresh (DayView)');
+ if (this.isDestroyed) {
+ return;
+ }
if (reloadData === undefined) {
reloadData = false;
}
diff --git a/src/calendar/view/DayBody.js b/src/calendar/view/DayBody.js
index c1ef35a4..910d5cb4 100644
--- a/src/calendar/view/DayBody.js
+++ b/src/calendar/view/DayBody.js
@@ -98,6 +98,9 @@ Ext.define('Extensible.calendar.view.DayBody', {
refresh: function(reloadData) {
Extensible.log('refresh (DayBodyView)');
+ if (this.isDestroyed) {
+ return;
+ }
var top = this.el.getScroll().top;
this.callParent(arguments);
@@ -405,11 +408,17 @@ Ext.define('Extensible.calendar.view.DayBody', {
evtData._height = Math.max(((endMins - startMins) * heightFactor), this.minEventHeight) + evtOffsets.height;
},
+ /**
+ * Render events.
+ * The event layout is based on this article: http://stackoverflow.com/questions/11311410/ and this sample
+ * implementation http://jsbin.com/detefuveta/5/edit?html,js,output *
+ */
renderItems: function() {
var day = 0,
evt,
- evts = [];
-
+ evts = [],
+ M = Extensible.calendar.data.EventMappings;
+
for (; day < this.dayCount; day++) {
var ev = 0,
emptyCells = 0,
@@ -423,7 +432,6 @@ Ext.define('Extensible.calendar.view.DayBody', {
continue;
}
var item = evt.data || evt.event.data,
- M = Extensible.calendar.data.EventMappings,
ad = item[M.IsAllDay.name] === true,
span = this.isEventSpanning(evt.event || evt),
renderAsAllDay = ad || span;
@@ -443,57 +451,116 @@ Ext.define('Extensible.calendar.view.DayBody', {
}
}
- // overlapping event pre-processing loop
+ // Layout events
var i = 0,
j = 0,
- overlapCols = [],
l = evts.length,
- prevDt,
- evt2,
- dt;
-
- for (; i