diff --git a/Extensible-config.js b/Extensible-config.js index 2aa58c01..2086a574 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,26 +21,29 @@ 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', - + /** * The root path to the Ext JS framework (defaults to loading 4.2.0 from the Sencha CDN via * `http://cdn.sencha.com/ext/gpl/4.2.0/`). Path should be absolute and should end with a '/'. @@ -61,23 +64,23 @@ Extensible.Config = { * * @config {String} extJsRoot */ - extJsRoot: 'http://cdn.sencha.com/ext/gpl/4.2.0/', + extJsRoot: 'http://cdn.sencha.com/ext/gpl/5.1.0/', /** * 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 @@ -85,17 +88,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, @@ -103,13 +106,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 @@ -119,37 +122,43 @@ Extensible.Config = { * work consistently with all Ext 4.x versions (just uses the Ext default English strings). As long * as you are using 4.1+ feel free to enable this by setting the value to any supported locale code. */ - language: null + language: null, + + /** + * Name of theme used. Supported values are: 'neptune', nepture-touch', 'crisp', 'crisp-touch'. + */ + theme: 'neptune' }, - + /** * 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(); me.cacheExtensible = config.cacheExtensible || me.defaults.cacheExtensible; me.language = config.language || me.defaults.language; + me.theme = config.theme || me.defaults.theme; 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) { @@ -157,58 +166,54 @@ 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 + '/build/packages/ext-theme-' + me.theme + '/build/resources/ext-theme-' + me.theme + '-all.css'); + 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' + 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 + 'build/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 + 'build/ext-all.js'); + me.includeScript(me.extensibleRoot + 'lib/extensible-all.js' + cacheBuster); + } else { + if (me.mode === 'dynamic-extensible') { + me.includeScript(me.extJsRoot + 'build/ext-all-debug.js'); + } else { + me.includeScript(me.extJsRoot + 'build/ext-debug.js'); + } + me.includeScript(me.extensibleRoot + 'lib/extensible-bootstrap.js' + cacheBuster); } - - me.includeStylesheet(me.extJsRoot + 'resources/css/ext-all.css'); - me.includeStylesheet(me.extensibleRoot + 'resources/css/extensible-all.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); 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..21382730 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": "../.." @@ -40,7 +40,11 @@ Ext.define('Extensible.example.calendar.TestApp.App', { // of MemoryEventStore to see how automatic store messaging is implemented. autoMsg: false }); - + + // Make the calendar stateful. This is optional. If set, the application will remember hidden + // calendars in the calendar list panel. + Ext.state.Manager.setProvider(new Ext.state.CookieProvider()); + // 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 @@ -105,6 +109,7 @@ Ext.define('Extensible.example.calendar.TestApp.App', { //viewStartHour: 6, //viewEndHour: 18, //minEventDisplayMinutes: 15 + startDay: 0, showTime: false }, @@ -119,7 +124,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 +144,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 +154,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/data/EventsCustom.js b/examples/calendar/data/EventsCustom.js index 2713bee4..58317c19 100644 --- a/examples/calendar/data/EventsCustom.js +++ b/examples/calendar/data/EventsCustom.js @@ -16,7 +16,7 @@ Ext.define('Extensible.example.calendar.data.EventsCustom', { "evt_id" : "A-1001", "cal_id" : "C1", "evt_title" : "Vacation", - "start_dt" : makeDate(-20, 10), + "start_dt" : makeDate(-20, 10), "end_dt" : makeDate(-10, 15), "full_desc" : "Have fun", "created_by" : "Brian" @@ -24,7 +24,7 @@ Ext.define('Extensible.example.calendar.data.EventsCustom', { "evt_id" : "A-1002", "cal_id" : "C2", "evt_title" : "Lunch with Matt", - "start_dt" : makeDate(0, 11, 30), + "start_dt" : makeDate(0, 11, 30), "end_dt" : makeDate(0, 13), "location" : "Chuy's!", "link_url" :"http://chuys.com", @@ -36,14 +36,14 @@ Ext.define('Extensible.example.calendar.data.EventsCustom', { "evt_id" : "A-1003", "cal_id" : "C3", "evt_title" : "Project due", - "start_dt" : makeDate(0, 15), + "start_dt" : makeDate(0, 15), "end_dt" : makeDate(0, 15), "created_by" : "Brian" },{ "evt_id" : "A-1004", "cal_id" : "C1", "evt_title" : "Sarah's birthday", - "start_dt" : Ext.Date.clone(today), + "start_dt" : Ext.Date.clone(today), "end_dt" : Ext.Date.clone(today), "full_desc" : "Need to get a gift", "all_day" : true, @@ -52,7 +52,7 @@ Ext.define('Extensible.example.calendar.data.EventsCustom', { "evt_id" : "A-1005", "cal_id" : "C2", "evt_title" : "A long one...", - "start_dt" : makeDate(-12), + "start_dt" : makeDate(-12), "end_dt" : makeDate(10, 0, 0, -1), "all_day" : true, "created_by" : "Brian", @@ -61,7 +61,7 @@ Ext.define('Extensible.example.calendar.data.EventsCustom', { "evt_id" : "A-1006", "cal_id" : "C3", "evt_title" : "School holiday", - "start_dt" : makeDate(5), + "start_dt" : makeDate(5), "end_dt" : makeDate(5), "all_day" : true, "reminder" : "2880", @@ -70,7 +70,7 @@ Ext.define('Extensible.example.calendar.data.EventsCustom', { "evt_id" : "A-1007", "cal_id" : "C1", "evt_title" : "Haircut", - "start_dt" : makeDate(0, 9), + "start_dt" : makeDate(0, 9), "end_dt" : makeDate(0, 9, 0, 30), "full_desc" : "Get cash on the way", "created_by" : "Brian" @@ -78,7 +78,7 @@ Ext.define('Extensible.example.calendar.data.EventsCustom', { "evt_id" : "A-1008", "cal_id" : "C3", "evt_title" : "An old event", - "start_dt" : makeDate(-30), + "start_dt" : makeDate(-30), "end_dt" : makeDate(-28), "all_day" : true, "created_by" : "Brian" @@ -86,7 +86,7 @@ Ext.define('Extensible.example.calendar.data.EventsCustom', { "evt_id" : "A-1009", "cal_id" : "C2", "evt_title" : "Board meeting", - "start_dt" : makeDate(-2, 13), + "start_dt" : makeDate(-2, 13), "end_dt" : makeDate(-2, 18), "location" : "ABC Inc.", "reminder" : "60", @@ -95,7 +95,7 @@ Ext.define('Extensible.example.calendar.data.EventsCustom', { "evt_id" : "A-1010", "cal_id" : "C3", "evt_title" : "Jenny's final exams", - "start_dt" : makeDate(-2), + "start_dt" : makeDate(-2), "end_dt" : makeDate(3, 0, 0, -1), "all_day" : true, "created_by" : "Brian" @@ -103,7 +103,7 @@ Ext.define('Extensible.example.calendar.data.EventsCustom', { "evt_id" : "A-1011", "cal_id" : "C1", "evt_title" : "Movie night", - "start_dt" : makeDate(2, 19), + "start_dt" : makeDate(2, 19), "end_dt" : makeDate(2, 23), "full_desc" : "Don't forget the tickets!", "reminder" : "60", @@ -112,14 +112,14 @@ Ext.define('Extensible.example.calendar.data.EventsCustom', { "evt_id" : "A-1012", "cal_id" : "C4", "evt_title" : "Gina's basketball tournament", - "start_dt" : makeDate(8, 8), + "start_dt" : makeDate(8, 8), "end_dt" : makeDate(10, 17), "created_by" : "Brian" },{ "evt_id" : "A-1013", "cal_id" : "C4", "evt_title" : "Toby's soccer game", - "start_dt" : makeDate(5, 10), + "start_dt" : makeDate(5, 10), "end_dt" : makeDate(5, 12), "created_by" : "Brian" }] diff --git a/examples/calendar/doc-types.js b/examples/calendar/doc-types.js index d6416410..8eb2c152 100644 --- a/examples/calendar/doc-types.js +++ b/examples/calendar/doc-types.js @@ -49,7 +49,6 @@ Ext.onReady(function(){ listeners: { 'select': { fn: function(cbo, rec){ - rec = rec[0]; window.location = 'doc-types.php?doctype='+rec.data.name+'&dtd='+rec.data.dtd; } } @@ -75,6 +74,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..2d2f9ae7 100644 --- a/examples/calendar/localization.js +++ b/examples/calendar/localization.js @@ -60,7 +60,6 @@ Ext.onReady(function() { listeners: { 'select': { fn: function(cbo, rec){ - rec = rec[0]; calendarPanel.getEl().mask('Loading '+rec.data.desc+'...'); loadLocale(rec.data.code); locale = rec.data.desc; @@ -108,6 +107,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..ce07ffcc 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": "../../" @@ -49,17 +49,17 @@ Ext.onReady(function() { // the recurrence-specific data mappings. Typically RRule and Duration are the only // values that need to be persisted and returned with events, and they are the only ones // mapped to columns in the MySQL database: - RRule: {name: 'RRule', mapping: 'rrule', type: 'string', useNull: true}, - Duration: {name: 'Duration', mapping: 'duration', defaultValue: -1, useNull: true, type: 'int'}, + RRule: {name: 'RRule', mapping: 'rrule', type: 'string', allowNull: true}, + Duration: {name: 'Duration', mapping: 'duration', defaultValue: -1, allowNull: true, type: 'int'}, // These additional values are required for processing recurring events properly, // but are either calculated or used only during editing. They still must be mapped // to whatever the server expects, but typically aren't persisted in the DB. For additional // details see the comments in src/calendar/data/EventMappings. - OriginalEventId: {name: 'OriginalEventId', mapping: 'origid', type: 'string', useNull: true}, - RSeriesStartDate: {name: 'RSeriesStartDate', mapping: 'rsstart', type: 'date', dateFormat: 'c', useNull: true}, - RInstanceStartDate: {name: 'RInstanceStartDate', mapping: 'ristart', type: 'date', dateFormat: 'c', useNull: true}, - REditMode: {name: 'REditMode', mapping: 'redit', type: 'string', useNull: true} + OriginalEventId: {name: 'OriginalEventId', mapping: 'origid', type: 'string', allowNull: true}, + RSeriesStartDate: {name: 'RSeriesStartDate', mapping: 'rsstart', type: 'date', dateFormat: 'c', allowNull: true}, + RInstanceStartDate: {name: 'RInstanceStartDate', mapping: 'ristart', type: 'date', dateFormat: 'c', allowNull: true}, + REditMode: {name: 'REditMode', mapping: 'redit', type: 'string', allowNull: true} }; Extensible.calendar.data.EventModel.reconfigure(); @@ -73,7 +73,7 @@ Ext.onReady(function() { reader: { type: 'json', - root: 'calendars' + rootProperty: 'calendars' } } }); @@ -100,11 +100,54 @@ Ext.onReady(function() { }, reader: { type: 'json', - root: 'data' + rootProperty: 'data', + transform: { + fn: function(data) { + // Manipulate raw data object: start and end date are strings; Convert to Date() + Ext.iterate(data.data, function(event, key){ + var startDate = (event['start']) ? new Date(event['start'].replace(/-/g , "/")) : new Date(); + event[Extensible.calendar.data.EventMappings.StartDate.mapping] = startDate; + + var endDate = (event['end']) ? new Date(event['end'].replace(/-/g , "/")) : new Date(); + event[Extensible.calendar.data.EventMappings.EndDate.mapping] = endDate; + + if (event['rsstart'] && event['rsstart'] != ''){ + var rsstartDt = new Date(event['rsstart'].replace(/-/g , "/")); + event[Extensible.calendar.data.EventMappings.RSeriesStartDate.mapping] = rsstartDt; + } + + if (event['ristart'] && event['ristart'] != ''){ + var ristartDt = new Date(event['ristart'].replace(/-/g , "/")); + event[Extensible.calendar.data.EventMappings.RInstanceStartDate.mapping] = ristartDt; + } + + // MySQL returns an int. Convert it to boolean, otherwise all events will be marked as all-day. + var allDay = (event['all_day'] == 1) ? true: false; + event[Extensible.calendar.data.EventMappings.IsAllDay.mapping] = allDay; + }); + + return data; + }, + scope: this + } }, writer: { type: 'json', - nameProperty: 'mapping' + nameProperty: 'mapping', + writeAllFields: true, // send all fields to server + transform: { + fn: function(data, request) { + var postData = {}; + + // Remove mapped fields from data sent to server and keep only the ones required in php script + Ext.iterate(Extensible.calendar.data.EventMappings, function(key, value){ + postData[value.mapping] = data[value.name] ? data[value.name] : null; + }); + + return postData; + }, + scope: this + } } }, @@ -114,8 +157,18 @@ Ext.onReady(function() { // NOT that your changes were actually persisted correctly in the back end. The 'write' event is the best // option for generically messaging after CRUD persistence has succeeded. listeners: { - 'write': function(store, operation) { - var title = Ext.value(operation.records[0].data[Extensible.calendar.data.EventMappings.Title.name], '(No title)'); + write: function(store, operation) { + var record, title; + + if ('Ext.data.operation.Destroy' == Ext.getClass(operation).getName()){ + record = operation.getRequest().getJsonData(); + title = record[Extensible.calendar.data.EventMappings.Title.mapping] || '(No title)'; + } else { + var records = operation.getRecords(), + record = records[0], + title = record.get(Extensible.calendar.data.EventMappings.Title.name) || '(No title)'; + } + switch(operation.action){ case 'create': Extensible.example.msg('Add', 'Added "' + title + '"'); @@ -149,6 +202,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..5d2a8162 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": "../../" @@ -46,7 +46,7 @@ Ext.onReady(function() { Reminder: {name: 'Reminder', mapping: 'reminder'} }; Extensible.calendar.data.EventModel.reconfigure(); - + // Calendars are loaded remotely from a static JSON file var calendarStore = Ext.create('Extensible.calendar.data.MemoryCalendarStore', { autoLoad: true, @@ -57,7 +57,7 @@ Ext.onReady(function() { reader: { type: 'json', - root: 'calendars' + rootProperty: 'calendars' } } }); @@ -84,11 +84,44 @@ Ext.onReady(function() { }, reader: { type: 'json', - root: 'data' + rootProperty: 'data', + transform: { + fn: function(data) { + // Manipulate raw data object: start and end date are strings; Convert to Date() + Ext.iterate(data.data, function(event,key){ + var startDate = (event['start']) ? new Date(event['start'].replace(/-/g , "/")) : new Date(); + event[Extensible.calendar.data.EventMappings.StartDate.mapping] = startDate; + + var endDate = (event['end']) ? new Date(event['end'].replace(/-/g , "/")) : new Date(); + event[Extensible.calendar.data.EventMappings.EndDate.mapping] = endDate; + + // MySQL returns it an int. Convert it to boolean, otherwise all events will be marked as all-day. + var allDay = (event['all_day'] == 1) ? true: false; + event[Extensible.calendar.data.EventMappings.IsAllDay.mapping] = allDay; + }); + + return data; + }, + scope: this + } }, writer: { type: 'json', - nameProperty: 'mapping' + nameProperty: 'mapping', + writeAllFields: true, // send all fields to server + transform: { + fn: function(data, request) { + var postData = {}; + + // Remove mapped fields from data sent to server and keep only the ones required in php script + Ext.iterate(Extensible.calendar.data.EventMappings, function(key, value){ + postData[value.mapping] = data[value.name] ? data[value.name] : null; + }); + + return postData; + }, + scope: this + } } }, @@ -98,8 +131,18 @@ Ext.onReady(function() { // NOT that your changes were actually persisted correctly in the back end. The 'write' event is the best // option for generically messaging after CRUD persistence has succeeded. listeners: { - 'write': function(store, operation) { - var title = Ext.value(operation.records[0].data[Extensible.calendar.data.EventMappings.Title.name], '(No title)'); + write: function(store, operation) { + var record, title; + + if ('Ext.data.operation.Destroy' == Ext.getClass(operation).getName()){ + record = operation.getRequest().getJsonData(); + title = record[Extensible.calendar.data.EventMappings.Title.mapping] || '(No title)'; + } else { + var records = operation.getRecords(), + record = records[0], + title = record.get(Extensible.calendar.data.EventMappings.Title.name) || '(No title)'; + } + switch(operation.action){ case 'create': Extensible.example.msg('Add', 'Added "' + title + '"'); @@ -121,7 +164,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 +195,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..c50b6006 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') @@ -36,7 +39,7 @@ Ext.onReady(function(){ this.calendarWin.show(); }; - Ext.fly('cal-win').on('click', showWindow, this); + Ext.get('cal-win').on('click', showWindow, this); showWindow(); }); diff --git a/examples/examples.js b/examples/examples.js index 310ecd16..6807b30a 100644 --- a/examples/examples.js +++ b/examples/examples.js @@ -7,8 +7,8 @@ Ext.define('Extensible.example', { this.msgCt.alignTo(document, 't-t'); var s = Ext.String.format.apply(String, Array.prototype.slice.call(arguments, 1)); var m = Ext.core.DomHelper.append(this.msgCt, {html:'

' + title + '

' + s + '

'}, true); - - m.slideIn('t').pause(3000).ghost('t', {remove:true}); + + m.slideIn('t').ghost('t', {remove:true, duration: 3000}); }, insertExamplesMenuLink: function() { diff --git a/examples/server/php/api/events-common.php b/examples/server/php/api/events-common.php index 9a76a2ca..c1cfaca8 100644 --- a/examples/server/php/api/events-common.php +++ b/examples/server/php/api/events-common.php @@ -26,7 +26,7 @@ // Set the app_id to allow each example to reuse this API with its own data. // In a real application this would not be needed. - $app_id = $event['app_id'] = isset($_REQUEST['app_id']) ? strtolower($_REQUEST['app_id']) : null; + $app_id = $event['app_id'] = isset($_REQUEST['app_id']) ? strtolower($_REQUEST['app_id']) : 'remote'; // The demos support simulating server failure for testing purposes $fail = isset($_REQUEST['fail']) ? TRUE : FALSE; diff --git a/examples/server/php/api/events-recurrence.php b/examples/server/php/api/events-recurrence.php index e1f3c36e..6b0d7143 100644 --- a/examples/server/php/api/events-recurrence.php +++ b/examples/server/php/api/events-recurrence.php @@ -257,15 +257,15 @@ function calculateEndDate($event) { $max_date = new DateTime('9999-12-31'); $recurrence = new When(); $recurrence->rrule($rrule); - if (isset($recurrence->end_date) && $recurrence->end_date < $max_date) { // The RRULE includes an explicit end date, so use that - $end = $recurrence->end_date->format($date_format).'Z'; + $recurrence->end_date->setTimezone(new DateTimeZone('UTC')); + $end = $recurrence->end_date->format($date_format); } else if (isset($recurrence->count) && $recurrence->count > 0) { // The RRULE has a limit, so calculate the end date based on the instance count $count = 0; - $newEnd; + $newEnd = null; $rdates = $recurrence->recur($event[$mappings['start_date']])->rrule($rrule); while ($rdate = $rdates->next()) { @@ -276,11 +276,14 @@ function calculateEndDate($event) { } // The 'minutes' portion should match Extensible.calendar.data.EventModel.resolution: $newEnd->modify('+'.$event[$mappings['duration']].' minutes'); - $end = $newEnd->format($date_format).'Z'; + $newEnd->setTimezone(new DateTimeZone('UTC')); + $end = $newEnd->format($date_format); } else { // The RRULE does not specify an end date or count, so default to max date - $end = date($date_format, PHP_INT_MAX).'Z'; + $newEnd = new DateTime(); + $newEnd->setTimestamp(PHP_INT_MAX); + $end = $newEnd->format($date_format); } } return $end; @@ -428,7 +431,7 @@ function deleteEvent($event) { */ function updateEvent($event) { global $db, $mappings, $date_format; - + $editMode = $event[$mappings['recur_edit_mode']]; if ($editMode) { diff --git a/resources/css/calendar.css b/resources/css/calendar.css index d003db7d..9db93d6e 100644 --- a/resources/css/calendar.css +++ b/resources/css/calendar.css @@ -485,7 +485,6 @@ td.ext-cal-dtitle-today div { } .ext-cal-evt dl { margin: 0; - border: 0 1px; overflow: hidden; border-width: 0 1px; border-style: solid; @@ -755,6 +754,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 @@ -763,14 +822,7 @@ td.ext-cal-dtitle-today div { .ext-cal-nav-picker { border-style: none none solid; border-color: #99BBE8; -} -.ext-cal-nav-picker .x-datepicker-header { - background: #D3E1F1 url(../images/default/ext/toolbar-bg.gif) repeat-x; -} -.ext-cal-nav-picker .x-datepicker-month, -.ext-cal-nav-picker .x-datepicker-prev, -.ext-cal-nav-picker .x-datepicker-next { - background: transparent; + max-width: 179px; } .ext-cal-nav-picker .x-datepicker-prev a { background-image: url(../images/default/ext/page-prev.gif); @@ -786,10 +838,6 @@ td.ext-cal-dtitle-today div { font-weight: bold; font-family: arial,tahoma,verdana,helvetica; } -.ext-cal-nav-picker .x-datepicker-month .x-btn-split-right { - background-image: url(../images/default/ext/button-arrow.gif) !important; - background-position: right 4px; -} .ext-cal-nav-picker .x-datepicker-inner { border-top: 1px solid #BBCCFF; } @@ -969,9 +1017,6 @@ td.ext-cal-dtitle-today div { .extensible-cal-icon-cal-show { background-image:url(../images/default/silk/calendar_view_month.png) !important; } -.extensible-cal-icon-cal-colors { - background-image:url(../images/default/silk/color_wheel.png) !important; -} /******************************************* * @@ -1007,4 +1052,28 @@ td.ext-cal-dtitle-today div { height: 14px; width: 14px; line-height: 10px; +} + + + +/******************************************** EXTJS 5 **********************************************/ +.x-monthpicker-months { + width: 90px; +} + +.x-monthpicker-months .x-monthpicker-item { + width: 44px; +} + +.x-monthpicker-years { + width: 89px; +} + + +.x-monthpicker-yearnav-button-ct { + width: 44px; +} + +.x-monthpicker-years .x-monthpicker-item { + width: 44px; } \ No newline at end of file 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/calendar/CalendarPanel.js b/src/calendar/CalendarPanel.js index 002b6130..461e6abd 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 @@ -151,6 +164,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 +238,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 +328,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)); @@ -312,238 +358,238 @@ Ext.define('Extensible.calendar.CalendarPanel', { this.callParent(arguments); - this.addEvents({ - /** - * @event eventadd - * Fires after a new event is added to the underlying store - * @param {Extensible.calendar.CalendarPanel} this - * @param {Extensible.calendar.data.EventModel} rec The new - * {@link Extensible.calendar.data.EventModel record} that was added - */ - eventadd: true, - /** - * @event eventupdate - * Fires after an existing event is updated - * @param {Extensible.calendar.CalendarPanel} this - * @param {Extensible.calendar.data.EventModel} rec The new - * {@link Extensible.calendar.data.EventModel record} that was updated - */ - eventupdate: true, - /** - * @event beforeeventdelete - * Fires before an event is deleted by the user. This is a cancelable event, so returning - * false from a handler will cancel the delete operation. - * @param {Extensible.calendar.CalendarPanel} this - * @param {Extensible.calendar.data.EventModel} rec The {@link Extensible.calendar.data.EventModel record} - * for the event that was deleted - * @param {Ext.Element} el The target element - */ - beforeeventdelete: true, - /** - * @event eventdelete - * Fires after an event is deleted by the user. - * @param {Extensible.calendar.CalendarPanel} this - * @param {Extensible.calendar.data.EventModel} rec The - * {@link Extensible.calendar.data.EventModel record} for the event that was deleted - * @param {Ext.Element} el The target element - */ - eventdelete: true, - /** - * @event eventcancel - * Fires after an event add/edit operation is canceled by the user and no store update took place - * @param {Extensible.calendar.CalendarPanel} this - * @param {Extensible.calendar.data.EventModel} rec The new - * {@link Extensible.calendar.data.EventModel record} that was canceled - */ - eventcancel: true, - /** - * @event viewchange - * Fires after a different calendar view is activated (but not when the event edit form is activated) - * @param {Extensible.calendar.CalendarPanel} this - * @param {Extensible.CalendarView} view The view being activated (any valid - * {@link Extensible.calendar.view.AbstractCalendar CalendarView} subclass) - * @param {Object} info Extra information about the newly activated view. This is a plain object - * with following properties: - * - * * **activeDate** - * * The currently selected date - * * **viewStart** - * * The first date in the new view range - * * **viewEnd** - * * The last date in the new view range - */ - viewchange: true, - /** - * @event editdetails - * Fires when the user selects the option to edit the selected event in the detailed edit form - * (by default, an instance of {@link Extensible.calendar.form.EventDetails}). Handling code - * should hide the active event editor and transfer the current event record to the appropriate - * instance of the detailed form by showing it and calling - * {@link Extensible.calendar.form.EventDetails#loadRecord loadRecord}. - * @param {Extensible.calendar.CalendarPanel} this The CalendarPanel - * @param {Extensible.calendar.view.AbstractCalendar} view The currently active - * {@link Extensible.calendar.view.AbstractCalendar CalendarView} subclass - * @param {Extensible.calendar.data.EventModel} rec The - * {@link Extensible.calendar.data.EventModel record} that is currently being edited - * @param {Ext.Element} el The target element - */ - editdetails: true - - - // - // NOTE: CalendarPanel also relays the following events from contained views as if - // they originated from this: - // - - /** - * @event eventsrendered - * Fires after events are finished rendering in the view - * @param {Extensible.calendar.CalendarPanel} this - */ - /** - * @event eventclick - * Fires after the user clicks on an event element. - * - * **NOTE:** This version of eventclick differs from the same - * event fired directly by {@link Extensible.calendar.view.AbstractCalendar CalendarView} - * subclasses in that it provides a default implementation (showing the default edit window) - * and is also cancelable (if a handler returns false the edit window will not be - * shown). This event when fired from a view class is simply a notification that an event was - * clicked and has no default behavior. - * @param {Extensible.calendar.CalendarPanel} this - * @param {Extensible.calendar.data.EventModel} rec The - * {@link Extensible.calendar.data.EventModel record} for the event that was clicked on - * @param {HTMLNode} el The DOM node that was clicked on - */ - /** - * @event rangeselect - * Fires after the user drags on the calendar to select a range of dates/times in which to - * create an event - * @param {Extensible.calendar.CalendarPanel} this - * @param {Object} dates An object containing the start (StartDate property) and end (EndDate - * property) dates selected - * @param {Function} callback A callback function that MUST be called after the event handling - * is complete so that the view is properly cleaned up (shim elements are persisted in - * the view while the user is prompted to handle the range selection). The callback is - * already created in the proper scope, so it simply needs to be executed as a standard - * function call (e.g., callback()). - */ - /** - * @event eventover - * Fires anytime the mouse is over an event element - * @param {Extensible.calendar.CalendarPanel} this - * @param {Extensible.calendar.data.EventModel} rec The - * {@link Extensible.calendar.data.EventModel record} for the event that the cursor is over - * @param {HTMLNode} el The DOM node that is being moused over - */ - /** - * @event eventout - * Fires anytime the mouse exits an event element - * @param {Extensible.calendar.CalendarPanel} this - * @param {Extensible.calendar.data.EventModel} rec The - * {@link Extensible.calendar.data.EventModel record} for the event that the cursor exited - * @param {HTMLNode} el The DOM node that was exited - */ - /** - * @event beforedatechange - * Fires before the start date of the view changes, giving you an opportunity to save state or - * anything else you may need to do prior to the UI view changing. This is a cancelable event, so - * returning false from a handler will cancel both the view change and the setting of the start date. - * @param {Extensible.calendar.CalendarPanel} this - * @param {Date} startDate The current start date of the view (as explained in {@link #getStartDate} - * @param {Date} newStartDate The new start date that will be set when the view changes - * @param {Date} viewStart The first displayed date in the current view - * @param {Date} viewEnd The last displayed date in the current view - */ - /** - * @event dayclick - * Fires after the user clicks within a day/week view container and not on an event element - * @param {Extensible.calendar.CalendarPanel} this - * @param {Date} dt The date/time that was clicked on - * @param {Boolean} allday True if the day clicked on represents an all-day box, else false. - * @param {Ext.Element} el The Element that was clicked on - */ - /** - * @event datechange - * Fires after the start date of the view changes - * @param {Extensible.calendar.CalendarPanel} this - * @param {Date} startDate The start date of the view (as explained in {@link #getStartDate} - * @param {Date} viewStart The first displayed date in the view - * @param {Date} viewEnd The last displayed date in the view - */ - /** - * @event beforeeventmove - * Fires before an event element is dragged by the user and dropped in a new position. This is - * a cancelable event, so returning false from a handler will cancel the move operation. - * @param {Extensible.calendar.CalendarPanel} this - * @param {Extensible.calendar.data.EventModel} rec The - * {@link Extensible.calendar.data.EventModel record} for the event that will be moved - */ - /** - * @event eventmove - * Fires after an event element is dragged by the user and dropped in a new position - * @param {Extensible.calendar.CalendarPanel} this - * @param {Extensible.calendar.data.EventModel} rec The - * {@link Extensible.calendar.data.EventModel record} for the event that was moved with - * updated start and end dates - */ - /** - * @event initdrag - * Fires when a drag operation is initiated in the view - * @param {Extensible.calendar.CalendarPanel} this - */ - /** - * @event dayover - * Fires while the mouse is over a day element - * @param {Extensible.calendar.CalendarPanel} this - * @param {Date} dt The date that is being moused over - * @param {Ext.Element} el The day Element that is being moused over - */ - /** - * @event dayout - * Fires when the mouse exits a day element - * @param {Extensible.calendar.CalendarPanel} this - * @param {Date} dt The date that is exited - * @param {Ext.Element} el The day Element that is exited - */ - /** - * @event beforeeventresize - * Fires after the user drags the resize handle of an event to resize it, but before the - * resize operation is carried out. This is a cancelable event, so returning false from a - * handler will cancel the resize operation. **NOTE:** This event is only fired - * from views that support event resizing. - * @param {Extensible.calendar.CalendarPanel} this - * @param {Extensible.calendar.data.EventModel} rec The - * {@link Extensible.calendar.data.EventModel record} for the event that was resized - * containing the updated start and end dates - */ - /** - * @event eventresize - * Fires after the user drags the resize handle of an event and the resize operation is - * complete. **NOTE:** This event is only fired from views that support event resizing. - * @param {Extensible.calendar.CalendarPanel} this - * @param {Extensible.calendar.data.EventModel} rec The - * {@link Extensible.calendar.data.EventModel record} for the event that was resized - * containing the updated start and end dates - */ - /** - * @event eventexception - * Fires after an event has been processed via an Ext proxy and returned with an exception. This - * could be because of a server error, or because the data returned success: false. - * - * The view provides default handling via the overrideable - * {@link Extensible.calendar.view.AbstractCalendar#notifyOnException notifyOnException} method. If - * any function handling this event returns false, the notifyOnException method will not be called. - * - * Note that only Server proxy and subclasses (including Ajax proxy) will raise this event. - * - * @param {Extensible.calendar.CalendarPanel} this - * @param {Object} response The raw response object returned from the server - * @param {Ext.data.Operation} operation The operation that was processed - * @since 1.6.0 - */ - }); - + //this.addEvents({ + // /** + // * @event eventadd + // * Fires after a new event is added to the underlying store + // * @param {Extensible.calendar.CalendarPanel} this + // * @param {Extensible.calendar.data.EventModel} rec The new + // * {@link Extensible.calendar.data.EventModel record} that was added + // */ + // eventadd: true, + // /** + // * @event eventupdate + // * Fires after an existing event is updated + // * @param {Extensible.calendar.CalendarPanel} this + // * @param {Extensible.calendar.data.EventModel} rec The new + // * {@link Extensible.calendar.data.EventModel record} that was updated + // */ + // eventupdate: true, + // /** + // * @event beforeeventdelete + // * Fires before an event is deleted by the user. This is a cancelable event, so returning + // * false from a handler will cancel the delete operation. + // * @param {Extensible.calendar.CalendarPanel} this + // * @param {Extensible.calendar.data.EventModel} rec The {@link Extensible.calendar.data.EventModel record} + // * for the event that was deleted + // * @param {Ext.Element} el The target element + // */ + // beforeeventdelete: true, + // /** + // * @event eventdelete + // * Fires after an event is deleted by the user. + // * @param {Extensible.calendar.CalendarPanel} this + // * @param {Extensible.calendar.data.EventModel} rec The + // * {@link Extensible.calendar.data.EventModel record} for the event that was deleted + // * @param {Ext.Element} el The target element + // */ + // eventdelete: true, + // /** + // * @event eventcancel + // * Fires after an event add/edit operation is canceled by the user and no store update took place + // * @param {Extensible.calendar.CalendarPanel} this + // * @param {Extensible.calendar.data.EventModel} rec The new + // * {@link Extensible.calendar.data.EventModel record} that was canceled + // */ + // eventcancel: true, + // /** + // * @event viewchange + // * Fires after a different calendar view is activated (but not when the event edit form is activated) + // * @param {Extensible.calendar.CalendarPanel} this + // * @param {Extensible.CalendarView} view The view being activated (any valid + // * {@link Extensible.calendar.view.AbstractCalendar CalendarView} subclass) + // * @param {Object} info Extra information about the newly activated view. This is a plain object + // * with following properties: + // * + // * * **activeDate** + // * * The currently selected date + // * * **viewStart** + // * * The first date in the new view range + // * * **viewEnd** + // * * The last date in the new view range + // */ + // viewchange: true, + // /** + // * @event editdetails + // * Fires when the user selects the option to edit the selected event in the detailed edit form + // * (by default, an instance of {@link Extensible.calendar.form.EventDetails}). Handling code + // * should hide the active event editor and transfer the current event record to the appropriate + // * instance of the detailed form by showing it and calling + // * {@link Extensible.calendar.form.EventDetails#loadRecord loadRecord}. + // * @param {Extensible.calendar.CalendarPanel} this The CalendarPanel + // * @param {Extensible.calendar.view.AbstractCalendar} view The currently active + // * {@link Extensible.calendar.view.AbstractCalendar CalendarView} subclass + // * @param {Extensible.calendar.data.EventModel} rec The + // * {@link Extensible.calendar.data.EventModel record} that is currently being edited + // * @param {Ext.Element} el The target element + // */ + // editdetails: true + // + // + // // + // // NOTE: CalendarPanel also relays the following events from contained views as if + // // they originated from this: + // // + // + // /** + // * @event eventsrendered + // * Fires after events are finished rendering in the view + // * @param {Extensible.calendar.CalendarPanel} this + // */ + // /** + // * @event eventclick + // * Fires after the user clicks on an event element. + // * + // * **NOTE:** This version of eventclick differs from the same + // * event fired directly by {@link Extensible.calendar.view.AbstractCalendar CalendarView} + // * subclasses in that it provides a default implementation (showing the default edit window) + // * and is also cancelable (if a handler returns false the edit window will not be + // * shown). This event when fired from a view class is simply a notification that an event was + // * clicked and has no default behavior. + // * @param {Extensible.calendar.CalendarPanel} this + // * @param {Extensible.calendar.data.EventModel} rec The + // * {@link Extensible.calendar.data.EventModel record} for the event that was clicked on + // * @param {HTMLNode} el The DOM node that was clicked on + // */ + // /** + // * @event rangeselect + // * Fires after the user drags on the calendar to select a range of dates/times in which to + // * create an event + // * @param {Extensible.calendar.CalendarPanel} this + // * @param {Object} dates An object containing the start (StartDate property) and end (EndDate + // * property) dates selected + // * @param {Function} callback A callback function that MUST be called after the event handling + // * is complete so that the view is properly cleaned up (shim elements are persisted in + // * the view while the user is prompted to handle the range selection). The callback is + // * already created in the proper scope, so it simply needs to be executed as a standard + // * function call (e.g., callback()). + // */ + // /** + // * @event eventover + // * Fires anytime the mouse is over an event element + // * @param {Extensible.calendar.CalendarPanel} this + // * @param {Extensible.calendar.data.EventModel} rec The + // * {@link Extensible.calendar.data.EventModel record} for the event that the cursor is over + // * @param {HTMLNode} el The DOM node that is being moused over + // */ + // /** + // * @event eventout + // * Fires anytime the mouse exits an event element + // * @param {Extensible.calendar.CalendarPanel} this + // * @param {Extensible.calendar.data.EventModel} rec The + // * {@link Extensible.calendar.data.EventModel record} for the event that the cursor exited + // * @param {HTMLNode} el The DOM node that was exited + // */ + // /** + // * @event beforedatechange + // * Fires before the start date of the view changes, giving you an opportunity to save state or + // * anything else you may need to do prior to the UI view changing. This is a cancelable event, so + // * returning false from a handler will cancel both the view change and the setting of the start date. + // * @param {Extensible.calendar.CalendarPanel} this + // * @param {Date} startDate The current start date of the view (as explained in {@link #getStartDate} + // * @param {Date} newStartDate The new start date that will be set when the view changes + // * @param {Date} viewStart The first displayed date in the current view + // * @param {Date} viewEnd The last displayed date in the current view + // */ + // /** + // * @event dayclick + // * Fires after the user clicks within a day/week view container and not on an event element + // * @param {Extensible.calendar.CalendarPanel} this + // * @param {Date} dt The date/time that was clicked on + // * @param {Boolean} allday True if the day clicked on represents an all-day box, else false. + // * @param {Ext.Element} el The Element that was clicked on + // */ + // /** + // * @event datechange + // * Fires after the start date of the view changes + // * @param {Extensible.calendar.CalendarPanel} this + // * @param {Date} startDate The start date of the view (as explained in {@link #getStartDate} + // * @param {Date} viewStart The first displayed date in the view + // * @param {Date} viewEnd The last displayed date in the view + // */ + // /** + // * @event beforeeventmove + // * Fires before an event element is dragged by the user and dropped in a new position. This is + // * a cancelable event, so returning false from a handler will cancel the move operation. + // * @param {Extensible.calendar.CalendarPanel} this + // * @param {Extensible.calendar.data.EventModel} rec The + // * {@link Extensible.calendar.data.EventModel record} for the event that will be moved + // */ + // /** + // * @event eventmove + // * Fires after an event element is dragged by the user and dropped in a new position + // * @param {Extensible.calendar.CalendarPanel} this + // * @param {Extensible.calendar.data.EventModel} rec The + // * {@link Extensible.calendar.data.EventModel record} for the event that was moved with + // * updated start and end dates + // */ + // /** + // * @event initdrag + // * Fires when a drag operation is initiated in the view + // * @param {Extensible.calendar.CalendarPanel} this + // */ + // /** + // * @event dayover + // * Fires while the mouse is over a day element + // * @param {Extensible.calendar.CalendarPanel} this + // * @param {Date} dt The date that is being moused over + // * @param {Ext.Element} el The day Element that is being moused over + // */ + // /** + // * @event dayout + // * Fires when the mouse exits a day element + // * @param {Extensible.calendar.CalendarPanel} this + // * @param {Date} dt The date that is exited + // * @param {Ext.Element} el The day Element that is exited + // */ + // /** + // * @event beforeeventresize + // * Fires after the user drags the resize handle of an event to resize it, but before the + // * resize operation is carried out. This is a cancelable event, so returning false from a + // * handler will cancel the resize operation. **NOTE:** This event is only fired + // * from views that support event resizing. + // * @param {Extensible.calendar.CalendarPanel} this + // * @param {Extensible.calendar.data.EventModel} rec The + // * {@link Extensible.calendar.data.EventModel record} for the event that was resized + // * containing the updated start and end dates + // */ + // /** + // * @event eventresize + // * Fires after the user drags the resize handle of an event and the resize operation is + // * complete. **NOTE:** This event is only fired from views that support event resizing. + // * @param {Extensible.calendar.CalendarPanel} this + // * @param {Extensible.calendar.data.EventModel} rec The + // * {@link Extensible.calendar.data.EventModel record} for the event that was resized + // * containing the updated start and end dates + // */ + // /** + // * @event eventexception + // * Fires after an event has been processed via an Ext proxy and returned with an exception. This + // * could be because of a server error, or because the data returned success: false. + // * + // * The view provides default handling via the overrideable + // * {@link Extensible.calendar.view.AbstractCalendar#notifyOnException notifyOnException} method. If + // * any function handling this event returns false, the notifyOnException method will not be called. + // * + // * Note that only Server proxy and subclasses (including Ajax proxy) will raise this event. + // * + // * @param {Extensible.calendar.CalendarPanel} this + // * @param {Object} response The raw response object returned from the server + // * @param {Ext.data.Operation} operation The operation that was processed + // * @since 1.6.0 + // */ + //}); + this.addCls('x-cal-panel'); if(this.eventStore) { @@ -630,7 +676,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', @@ -720,8 +821,8 @@ Ext.define('Extensible.calendar.CalendarPanel', { }, onWrite: function(store, operation) { - var rec = operation.records[0]; - + var records = operation.getRecords(), rec = records[0]; + switch(operation.action) { case 'create': this.onStoreAdd(store, rec); @@ -827,7 +928,6 @@ Ext.define('Extensible.calendar.CalendarPanel', { // Activate the new view and refresh the layout layout.setActiveItem(id || me.activeItem); - me.doComponentLayout(); me.activeView = layout.getActiveItem(); if (id !== editViewId) { @@ -900,6 +1000,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 +1057,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/data/EventMappings.js b/src/calendar/data/EventMappings.js index e3493f39..99cbefe4 100644 --- a/src/calendar/data/EventMappings.js +++ b/src/calendar/data/EventMappings.js @@ -77,54 +77,54 @@ Ext.ns('Extensible.calendar.data'); // @define Extensible.calendar.data.EventMappings Extensible.calendar.data.EventMappings = { EventId: { - name: 'EventId', + name: 'id', mapping: 'id', type: 'string' }, CalendarId: { - name: 'CalendarId', + name: 'cid', mapping: 'cid', type: 'string' }, Title: { - name: 'Title', + name: 'title', mapping: 'title', type: 'string' }, StartDate: { - name: 'StartDate', + name: 'start', mapping: 'start', type: 'date', dateFormat: 'c' }, EndDate: { - name: 'EndDate', + name: 'end', mapping: 'end', type: 'date', dateFormat: 'c' }, Location: { - name: 'Location', + name: 'loc', mapping: 'loc', type: 'string' }, Notes: { - name: 'Notes', + name: 'notes', mapping: 'notes', type: 'string' }, Url: { - name: 'Url', + name: 'url', mapping: 'url', type: 'string' }, IsAllDay: { - name: 'IsAllDay', + name: 'ad', mapping: 'ad', type: 'boolean' }, Reminder: { - name: 'Reminder', + name: 'rem', mapping: 'rem', type: 'string' }, @@ -145,10 +145,10 @@ Extensible.calendar.data.EventMappings = { // choose to provide a custom implementation, but out of the box only // the iCal RRULE format is handled by the components. RRule: { - name: 'RRule', + name: 'rrule', mapping: 'rrule', type: 'string', - useNull: true + allowNull: true }, // When using recurrence, the standard EndDate value will be the end date @@ -159,10 +159,10 @@ Extensible.calendar.data.EventMappings = { // recurrence so that the end date of each event instance can be // properly calculated. Duration: { - name: 'Duration', + name: 'duration', mapping: 'duration', defaultValue: -1, // the standard int default of 0 is actually a valid duration - useNull: true, // Without this, the null returned from the server is coerced to 0 + allowNull: true, // Without this, the null returned from the server is coerced to 0 type: 'int' }, @@ -173,19 +173,19 @@ Extensible.calendar.data.EventMappings = { // typically these will be generated from the RRULE pattern, not real events // that exist in the DB. OriginalEventId: { - name: 'OriginalEventId', + name: 'origid', mapping: 'origid', type: 'string', - useNull: true + allowNull: true }, // The start date for the recurring series. RSeriesStartDate: { - name: 'RSeriesStartDate', + name: 'rsstart', mapping: 'rsstart', type: 'date', dateFormat: 'c', - useNull: true + allowNull: true }, // If the start date of a recurring event instance is changed and then saved @@ -195,11 +195,11 @@ Extensible.calendar.data.EventMappings = { // the updated start date, you need a way to pass the original unedited start date // to be used as the exception date, which is what this instance start date is for. RInstanceStartDate: { - name: 'RInstanceStartDate', + name: 'ristart', mapping: 'ristart', type: 'date', dateFormat: 'c', - useNull: true + allowNull: true }, // Recurrence edit mode ('single', 'future' or 'all'). This is transient data @@ -207,9 +207,9 @@ Extensible.calendar.data.EventMappings = { // display purposes), but it's kept on the record for ease of transmission to // the server, and because multiple batched events could have different edit modes. REditMode: { - name: 'REditMode', + name: 'redit', mapping: 'redit', type: 'string', - useNull: true + allowNull: true } }; \ No newline at end of file diff --git a/src/calendar/data/EventModel.js b/src/calendar/data/EventModel.js index 9527601d..36f43525 100644 --- a/src/calendar/data/EventModel.js +++ b/src/calendar/data/EventModel.js @@ -34,7 +34,7 @@ Ext.define('Extensible.calendar.data.EventModel', { mappingClass: 'Extensible.calendar.data.EventMappings', - mappingIdProperty: 'EventId', + mappingIdProperty: 'id', // Experimental, not currently used: // associations: [{ diff --git a/src/calendar/data/MemoryCalendarStore.js b/src/calendar/data/MemoryCalendarStore.js index 90fa5560..2beee123 100644 --- a/src/calendar/data/MemoryCalendarStore.js +++ b/src/calendar/data/MemoryCalendarStore.js @@ -18,7 +18,7 @@ Ext.define('Extensible.calendar.data.MemoryCalendarStore', { type: 'memory', reader: { type: 'json', - root: 'calendars' + rootProperty: 'calendars' }, writer: { type: 'json' @@ -35,7 +35,7 @@ Ext.define('Extensible.calendar.data.MemoryCalendarStore', { this.idProperty = this.idProperty || Extensible.calendar.data.CalendarMappings.CalendarId.name || 'id'; - this.fields = Extensible.calendar.data.CalendarModel.prototype.fields.getRange(); + this.fields = Extensible.calendar.data.CalendarModel.prototype.fields; this.callParent(arguments); } diff --git a/src/calendar/data/MemoryEventStore.js b/src/calendar/data/MemoryEventStore.js index abf44084..076a98c7 100644 --- a/src/calendar/data/MemoryEventStore.js +++ b/src/calendar/data/MemoryEventStore.js @@ -28,7 +28,7 @@ Ext.define('Extensible.calendar.data.MemoryEventStore', { type: 'memory', reader: { type: 'json', - root: 'evts' + rootProperty: 'evts' }, writer: { type: 'json' @@ -53,7 +53,7 @@ Ext.define('Extensible.calendar.data.MemoryEventStore', { this.idProperty = this.idProperty || Extensible.calendar.data.EventMappings.EventId.mapping || 'id'; - this.fields = Extensible.calendar.data.EventModel.prototype.fields.getRange(); + this.fields = Extensible.calendar.data.EventModel.prototype.fields; // By default this shared example store will monitor its own CRUD events and // automatically show a page-level message for each event. This is simply a shortcut @@ -76,24 +76,9 @@ Ext.define('Extensible.calendar.data.MemoryEventStore', { } this.autoMsg = config.autoMsg; - this.onCreateRecords = Ext.Function.createInterceptor(this.onCreateRecords, this.interceptCreateRecords); this.initRecs(); }, - // private - override to make sure that any records added in-memory - // still get a unique PK assigned at the data level - interceptCreateRecords: function(records, operation, success) { - if (success) { - var i = 0, - rec, - len = records.length; - - for (; i < len; i++) { - records[i].data[Extensible.calendar.data.EventMappings.EventId.name] = this.idSeed++; - } - } - }, - // If the store started with preloaded inline data, we have to make sure the records are set up // properly as valid "saved" records otherwise they may get "added" on initial edit. initRecs: function() { @@ -108,19 +93,19 @@ Ext.define('Extensible.calendar.data.MemoryEventStore', { var me = this; if (Extensible.example && Extensible.example.msg) { - var success = operation.wasSuccessful(), - rec = operation.records[0], - title = rec.data[Extensible.calendar.data.EventMappings.Title.name]; + var records = 'Ext.data.operation.Destroy' == Ext.getClass(operation).getName()? operation.getResultSet().getRecords() : operation.getRecords(), + record = records[0], + title = record.get(Extensible.calendar.data.EventMappings.Title.mapping) || '(No title)'; switch (operation.action) { case 'create': - Extensible.example.msg('Add', 'Added "' + Ext.value(title, '(No title)') + '"'); + Extensible.example.msg('Add', 'Added "' + title + '"'); break; case 'update': - Extensible.example.msg('Update', 'Updated "' + Ext.value(title, '(No title)') + '"'); + Extensible.example.msg('Update', 'Updated "' + title + '"'); break; case 'destroy': - Extensible.example.msg('Delete', 'Deleted "' + Ext.value(title, '(No title)') + '"'); + Extensible.example.msg('Delete', 'Deleted "' + title + '"'); break; } } @@ -152,5 +137,52 @@ Ext.define('Extensible.calendar.data.MemoryEventStore', { me.loading = false; me.fireEvent('load', me, records, successful); + }, + listeners: { + add: { + fn: function(store, records) { + var record = records[0], + id = this.idSeed++; + + record.phantom = false; + record.data[Extensible.calendar.data.EventMappings.EventId.name] = id; + + var operation = Ext.create('Ext.data.operation.Create',{ + success: true, + complete: true, + request: Ext.create('Ext.data.Request', { jsonData: record }), + records: [record] + }); + + store.fireAction('write', [store, operation], function(){}); + } + }, + update: { + fn: function(store, record){ + var operation = Ext.create('Ext.data.operation.Update',{ + success: true, + complete: true, + request: Ext.create('Ext.data.Request', { jsonData: record }), + records: [record] + }); + + store.fireAction('write', [store, operation], function(){}); + } + }, + remove: { + fn: function(store, records){ + var record = records[0]; + + var operation = Ext.create('Ext.data.operation.Destroy',{ + success: true, + complete: true, + request: Ext.create('Ext.data.Request', { jsonData: record }), + _resultSet: Ext.create('Ext.data.ResultSet', { records: [record]}) + }); + + store.fireAction('write', [store, operation], function(){}); + + } + } } }); \ No newline at end of file diff --git a/src/calendar/dd/DropZone.js b/src/calendar/dd/DropZone.js index 9d7a932d..0cc17e3d 100644 --- a/src/calendar/dd/DropZone.js +++ b/src/calendar/dd/DropZone.js @@ -20,8 +20,8 @@ Ext.define('Extensible.calendar.dd.DropZone', { getTargetFromEvent: function(e) { var dragOffset = this.dragOffset || 0, - y = e.getPageY() - dragOffset, - d = this.view.getDayAt(e.getPageX(), y); + y = e.getY() - dragOffset, + d = this.view.getDayAt(e.getX(), y); return d.el ? d: null; }, diff --git a/src/calendar/dd/StatusProxy.js b/src/calendar/dd/StatusProxy.js index 42b7d204..02423baa 100644 --- a/src/calendar/dd/StatusProxy.js +++ b/src/calendar/dd/StatusProxy.js @@ -21,10 +21,10 @@ Ext.define('Extensible.calendar.dd.StatusProxy', { // Overridden to add a separate message element inside the ghost area. // Applies only to Ext 4.1 and above, see notes in constructor renderTpl: [ - '
', - '
', - '
', - '
', + '' + + '
'+ + '' + + '' + '
' ], @@ -82,11 +82,17 @@ Ext.define('Extensible.calendar.dd.StatusProxy', { * @protected */ update: function(html) { - this.callParent(arguments); - - // If available, set the ghosted event el to autoHeight for visual consistency - var el = this.ghost.dom.firstChild; - if(el) { + var el = this.getGhost().dom; + if (typeof html == "string") { + this.getGhost().setHtml(html); + } else { + this.getGhost().setHtml(''); + html.style.margin = "0"; + this.getGhost().dom.appendChild(html); + } + if (el) { + Ext.fly(el).setStyle('float', 'none'); + // If available, set the ghosted event el to autoHeight for visual consistency Ext.fly(el).setHeight('auto'); } }, @@ -96,6 +102,6 @@ Ext.define('Extensible.calendar.dd.StatusProxy', { * @param {String} msg The new status message */ updateMsg: function(msg) { - this.message.update(msg); + this.update(msg); } }); \ No newline at end of file diff --git a/src/calendar/form/EventDetails.js b/src/calendar/form/EventDetails.js index 5022aa84..aa770e5e 100644 --- a/src/calendar/form/EventDetails.js +++ b/src/calendar/form/EventDetails.js @@ -108,42 +108,42 @@ Ext.define('Extensible.calendar.form.EventDetails', { layout: 'column', initComponent: function() { - - this.addEvents({ - /** - * @event eventadd - * Fires after a new event is added - * @param {Extensible.calendar.form.EventDetails} this - * @param {Extensible.calendar.data.EventModel} rec The new {@link Extensible.calendar.data.EventModel - * record} that was added - */ - eventadd: true, - /** - * @event eventupdate - * Fires after an existing event is updated - * @param {Extensible.calendar.form.EventDetails} this - * @param {Extensible.calendar.data.EventModel} rec The new {@link Extensible.calendar.data.EventModel - * record} that was updated - */ - eventupdate: true, - /** - * @event eventdelete - * Fires after an event is deleted - * @param {Extensible.calendar.form.EventDetails} this - * @param {Extensible.calendar.data.EventModel} rec The new {@link Extensible.calendar.data.EventModel - * record} that was deleted - */ - eventdelete: true, - /** - * @event eventcancel - * Fires after an event add/edit operation is canceled by the user and no store update took place - * @param {Extensible.calendar.form.EventDetails} this - * @param {Extensible.calendar.data.EventModel} rec The new {@link Extensible.calendar.data.EventModel - * record} that was canceled - */ - eventcancel: true - }); - + + //this.addEvents({ + // /** + // * @event eventadd + // * Fires after a new event is added + // * @param {Extensible.calendar.form.EventDetails} this + // * @param {Extensible.calendar.data.EventModel} rec The new {@link Extensible.calendar.data.EventModel + // * record} that was added + // */ + // eventadd: true, + // /** + // * @event eventupdate + // * Fires after an existing event is updated + // * @param {Extensible.calendar.form.EventDetails} this + // * @param {Extensible.calendar.data.EventModel} rec The new {@link Extensible.calendar.data.EventModel + // * record} that was updated + // */ + // eventupdate: true, + // /** + // * @event eventdelete + // * Fires after an event is deleted + // * @param {Extensible.calendar.form.EventDetails} this + // * @param {Extensible.calendar.data.EventModel} rec The new {@link Extensible.calendar.data.EventModel + // * record} that was deleted + // */ + // eventdelete: true, + // /** + // * @event eventcancel + // * Fires after an event add/edit operation is canceled by the user and no store update took place + // * @param {Extensible.calendar.form.EventDetails} this + // * @param {Extensible.calendar.data.EventModel} rec The new {@link Extensible.calendar.data.EventModel + // * record} that was canceled + // */ + // eventcancel: true + //}); + this.titleField = Ext.create('Ext.form.field.Text', { fieldLabel: this.titleLabelText, name: Extensible.calendar.data.EventMappings.Title.name, @@ -320,7 +320,7 @@ Ext.define('Extensible.calendar.form.EventDetails', { name, obj = {}; - fields.each(function(f) { + Ext.each(fields, function(f) { name = f.name; if (name in values) { obj[name] = values[name]; diff --git a/src/calendar/form/EventWindow.js b/src/calendar/form/EventWindow.js index f289ab73..879c3251 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 @@ -95,57 +107,57 @@ Ext.define('Extensible.calendar.form.EventWindow', { allowDefaultAdd: true, initComponent: function() { - this.addEvents({ - /** - * @event eventadd - * Fires after a new event is added - * @param {Extensible.calendar.form.EventWindow} this - * @param {Extensible.calendar.data.EventModel} rec The new {@link Extensible.calendar.data.EventModel - * record} that was added - * @param {Ext.Element} el The target element - */ - eventadd: true, - /** - * @event eventupdate - * Fires after an existing event is updated - * @param {Extensible.calendar.form.EventWindow} this - * @param {Extensible.calendar.data.EventModel} rec The new {@link Extensible.calendar.data.EventModel - * record} that was updated - * @param {Ext.Element} el The target element - */ - eventupdate: true, - /** - * @event eventdelete - * Fires after an event is deleted - * @param {Extensible.calendar.form.EventWindow} this - * @param {Extensible.calendar.data.EventModel} rec The new {@link Extensible.calendar.data.EventModel - * record} that was deleted - * @param {Ext.Element} el The target element - */ - eventdelete: true, - /** - * @event eventcancel - * Fires after an event add/edit operation is canceled by the user and no store update took place - * @param {Extensible.calendar.form.EventWindow} this - * @param {Extensible.calendar.data.EventModel} rec The new {@link Extensible.calendar.data.EventModel - * record} that was canceled - * @param {Ext.Element} el The target element - */ - eventcancel: true, - /** - * @event editdetails - * Fires when the user selects the option in this window to continue editing in the detailed edit form - * (by default, an instance of {@link Extensible.calendar.form.EventDetails}. Handling code should hide - * this window and transfer the current event record to the appropriate instance of the detailed form by - * showing it and calling {@link Extensible.calendar.form.EventDetails#loadRecord loadRecord}. - * @param {Extensible.calendar.form.EventWindow} this - * @param {Extensible.calendar.data.EventModel} rec The {@link Extensible.calendar.data.EventModel record} - * that is currently being edited - * @param {Ext.Element} el The target element - */ - editdetails: true - }); - + //this.addEvents({ + // /** + // * @event eventadd + // * Fires after a new event is added + // * @param {Extensible.calendar.form.EventWindow} this + // * @param {Extensible.calendar.data.EventModel} rec The new {@link Extensible.calendar.data.EventModel + // * record} that was added + // * @param {Ext.Element} el The target element + // */ + // eventadd: true, + // /** + // * @event eventupdate + // * Fires after an existing event is updated + // * @param {Extensible.calendar.form.EventWindow} this + // * @param {Extensible.calendar.data.EventModel} rec The new {@link Extensible.calendar.data.EventModel + // * record} that was updated + // * @param {Ext.Element} el The target element + // */ + // eventupdate: true, + // /** + // * @event eventdelete + // * Fires after an event is deleted + // * @param {Extensible.calendar.form.EventWindow} this + // * @param {Extensible.calendar.data.EventModel} rec The new {@link Extensible.calendar.data.EventModel + // * record} that was deleted + // * @param {Ext.Element} el The target element + // */ + // eventdelete: true, + // /** + // * @event eventcancel + // * Fires after an event add/edit operation is canceled by the user and no store update took place + // * @param {Extensible.calendar.form.EventWindow} this + // * @param {Extensible.calendar.data.EventModel} rec The new {@link Extensible.calendar.data.EventModel + // * record} that was canceled + // * @param {Ext.Element} el The target element + // */ + // eventcancel: true, + // /** + // * @event editdetails + // * Fires when the user selects the option in this window to continue editing in the detailed edit form + // * (by default, an instance of {@link Extensible.calendar.form.EventDetails}. Handling code should hide + // * this window and transfer the current event record to the appropriate instance of the detailed form by + // * showing it and calling {@link Extensible.calendar.form.EventDetails#loadRecord loadRecord}. + // * @param {Extensible.calendar.form.EventWindow} this + // * @param {Extensible.calendar.data.EventModel} rec The {@link Extensible.calendar.data.EventModel record} + // * that is currently being edited + // * @param {Ext.Element} el The target element + // */ + // editdetails: true + //}); + this.fbar = this.getFooterBarConfig(); this.callParent(arguments); @@ -351,9 +363,9 @@ Ext.define('Extensible.calendar.form.EventWindow', { }, cleanup: function(hide) { - if (this.activeRecord) { - this.activeRecord.reject(); - } + //if (this.activeRecord) { + // this.activeRecord.reject(); + //} delete this.activeRecord; if (hide===true) { @@ -371,7 +383,7 @@ Ext.define('Extensible.calendar.form.EventWindow', { obj = {}, modified; - fields.each(function(f) { + Ext.each(fields, function(f) { name = f.name; if (name in values) { obj[name] = values[name]; @@ -427,7 +439,7 @@ Ext.define('Extensible.calendar.form.EventWindow', { me.onCancel(); return; } - + if (me.activeRecord.phantom) { me.fireEvent('eventadd', me, me.activeRecord, me.animateTarget); } diff --git a/src/calendar/gadget/CalendarListMenu.js b/src/calendar/gadget/CalendarListMenu.js index 9f30114c..6e68c865 100644 --- a/src/calendar/gadget/CalendarListMenu.js +++ b/src/calendar/gadget/CalendarListMenu.js @@ -62,13 +62,13 @@ Ext.define('Extensible.calendar.gadget.CalendarListMenu', { */ initComponent: function() { - this.addEvents( - 'showcalendar', - 'hidecalendar', - 'radiocalendar', - 'colorchange' - ); - + //this.addEvents( + // 'showcalendar', + // 'hidecalendar', + // 'radiocalendar', + // 'colorchange' + //); + Ext.apply(this, { plain: true, items: [{ @@ -82,7 +82,7 @@ Ext.define('Extensible.calendar.gadget.CalendarListMenu', { }] }); - this.addClass('x-calendar-list-menu'); + this.addCls('x-calendar-list-menu'); this.callParent(arguments); }, diff --git a/src/calendar/gadget/CalendarListPanel.js b/src/calendar/gadget/CalendarListPanel.js index 456ec153..640ea16b 100644 --- a/src/calendar/gadget/CalendarListPanel.js +++ b/src/calendar/gadget/CalendarListPanel.js @@ -18,7 +18,15 @@ Ext.define('Extensible.calendar.gadget.CalendarListPanel', { layout: 'fit', menuSelector: 'em', width: 100, // this should be overridden by this container's layout - + + /** + * @cfg {bool} stateful + * If set to true, the object will remember the state of hidden and displayed calendars. + * Note that this works only, if a persistence provider has been passed to the state + * manager E.g. Ext.state.Manager.setProvider(new Ext.state.CookieProvider()); + */ + stateful: true, + /** * @cfg {Ext.data.Store} store * A {@link Ext.data.Store store} containing records of type {@link Extensible.calendar.data.CalendarModel CalendarRecord}. @@ -29,6 +37,10 @@ Ext.define('Extensible.calendar.gadget.CalendarListPanel', { initComponent: function() { this.addCls('x-calendar-list'); this.callParent(arguments); + + if (this.stateful){ + this.stateId = this.id + '-StateId'; + } }, afterRender: function(ct, position) { @@ -124,34 +136,39 @@ Ext.define('Extensible.calendar.gadget.CalendarListPanel', { } this.getListTemplate().overwrite(this.body, data); }, - + getColorCls: function(colorId) { return 'x-cal-'+colorId+'-ad'; }, - toggleCalendar: function(id, commit) { + toggleCalendar: function(id, commit, saveState) { var rec = this.store.findRecord(Extensible.calendar.data.CalendarMappings.CalendarId.name, id), CM = Extensible.calendar.data.CalendarMappings, isHidden = rec.data[CM.IsHidden.name]; - + rec.set(CM.IsHidden.name, !isHidden); if(commit !== false) { rec.commit(); } + + // Saves the state of the calendars to the persistence store + if (saveState !== false) { + this.saveState(); + } }, - showCalendar: function(id, commit) { + showCalendar: function(id, commit, saveState) { var rec = this.store.findRecord(Extensible.calendar.data.CalendarMappings.CalendarId.name, id); - if(rec.data[Extensible.calendar.data.CalendarMappings.IsHidden.name] === true) { - this.toggleCalendar(id, commit); + if(rec && rec.data[Extensible.calendar.data.CalendarMappings.IsHidden.name] === true) { + this.toggleCalendar(id, commit, saveState); } }, - hideCalendar: function(id, commit) { + hideCalendar: function(id, commit, saveState) { var rec = this.store.findRecord(Extensible.calendar.data.CalendarMappings.CalendarId.name, id); - if(rec.data[Extensible.calendar.data.CalendarMappings.IsHidden.name] !== true) { - this.toggleCalendar(id, commit); + if(rec && rec.data[Extensible.calendar.data.CalendarMappings.IsHidden.name] !== true) { + this.toggleCalendar(id, commit, saveState); } }, @@ -160,11 +177,11 @@ Ext.define('Extensible.calendar.gadget.CalendarListPanel', { calendarId = Extensible.calendar.data.CalendarMappings.CalendarId.name, recs = this.store.getRange(), len = recs.length; - + for (; i < len; i++) { recId = recs[i].data[calendarId]; // make a truthy check so that either numeric or string ids can match - if(recId === id) { + if(recId == id) { this.showCalendar(recId, false); } else{ @@ -234,5 +251,39 @@ Ext.define('Extensible.calendar.gadget.CalendarListPanel', { } this.menu.setCalendar(id, colorId); this.menu.showAt(xy); + }, + + /** + * 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 = [], + CM = Extensible.calendar.data.CalendarMappings, + recs = this.store.getRange(), + len = recs.length, + i = 0; + + for(; i < len; i++){ + // Check and save only the ids of hidden calendars + if (recs[i].data[CM.IsHidden.name]){ + state.push(recs[i].data[CM.CalendarId.name]); + } + } + + 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) { + for (key in state) { + this.hideCalendar(state[key], false, false); + } } + }); \ No newline at end of file diff --git a/src/calendar/menu/Event.js b/src/calendar/menu/Event.js index 15de3b7c..6f480d2e 100644 --- a/src/calendar/menu/Event.js +++ b/src/calendar/menu/Event.js @@ -73,58 +73,58 @@ Ext.define('Extensible.calendar.menu.Event', { ownerCalendarPanel: {}, initComponent: function() { - this.addEvents( - /** - * @event editdetails - * Fires when the user selects the option to edit the event details - * (by default, in an instance of {@link Extensible.calendar.form.EventDetails}. Handling code should - * transfer the current event record to the appropriate instance of the detailed form by showing - * the form and calling {@link Extensible.calendar.form.EventDetails#loadRecord loadRecord}. - * @param {Extensible.calendar.menu.Event} this - * @param {Extensible.calendar.data.EventModel} rec The {@link Extensible.calendar.data.EventModel - * record} that is currently being edited - * @param {Ext.Element} el The element associated with this context menu - */ - 'editdetails', - /** - * @event eventdelete - * Fires after the user selectes the option to delete an event. Note that this menu does not actually - * delete the event from the data store. This is simply a notification that the menu option was - * selected -- it is the responsibility of handling code to perform the deletion and any clean - * up required. - * @param {Extensible.calendar.menu.Event} this - * @param {Extensible.calendar.data.EventModel} rec The {@link Extensible.calendar.data.EventModel - * record} for the event to be deleted - * @param {Ext.Element} el The element associated with this context menu - */ - 'eventdelete', - /** - * @event eventmove - * Fires after the user selects a date in the calendar picker under the "move event" menu option. - * Note that this menu does not actually update the event in the data store. This is simply a - * notification that the menu option was selected -- it is the responsibility of handling code - * to perform the move action and any clean up required. - * @param {Extensible.calendar.menu.Event} this - * @param {Extensible.calendar.data.EventModel} rec The {@link Extensible.calendar.data.EventModel - * record} for the event to be moved - * @param {Date} dt The new start date for the event (the existing event start time will be preserved) - */ - 'eventmove', - /** - * @event eventcopy - * Fires after the user selects a date in the calendar picker under the "copy event" menu option. - * Note that this menu does not actually update the event in the data store. This is simply a - * notification that the menu option was selected -- it is the responsibility of handling code - * to perform the copy action. - * @param {Extensible.calendar.menu.Event} this - * @param {Extensible.calendar.data.EventModel} rec The {@link Extensible.calendar.data.EventModel - * record} for the event to be copied - * @param {Date} dt The start date for the event copy (the existing event start time will - * be preserved) - */ - 'eventcopy' - ); - + //this.addEvents( + // /** + // * @event editdetails + // * Fires when the user selects the option to edit the event details + // * (by default, in an instance of {@link Extensible.calendar.form.EventDetails}. Handling code should + // * transfer the current event record to the appropriate instance of the detailed form by showing + // * the form and calling {@link Extensible.calendar.form.EventDetails#loadRecord loadRecord}. + // * @param {Extensible.calendar.menu.Event} this + // * @param {Extensible.calendar.data.EventModel} rec The {@link Extensible.calendar.data.EventModel + // * record} that is currently being edited + // * @param {Ext.Element} el The element associated with this context menu + // */ + // 'editdetails', + // /** + // * @event eventdelete + // * Fires after the user selectes the option to delete an event. Note that this menu does not actually + // * delete the event from the data store. This is simply a notification that the menu option was + // * selected -- it is the responsibility of handling code to perform the deletion and any clean + // * up required. + // * @param {Extensible.calendar.menu.Event} this + // * @param {Extensible.calendar.data.EventModel} rec The {@link Extensible.calendar.data.EventModel + // * record} for the event to be deleted + // * @param {Ext.Element} el The element associated with this context menu + // */ + // 'eventdelete', + // /** + // * @event eventmove + // * Fires after the user selects a date in the calendar picker under the "move event" menu option. + // * Note that this menu does not actually update the event in the data store. This is simply a + // * notification that the menu option was selected -- it is the responsibility of handling code + // * to perform the move action and any clean up required. + // * @param {Extensible.calendar.menu.Event} this + // * @param {Extensible.calendar.data.EventModel} rec The {@link Extensible.calendar.data.EventModel + // * record} for the event to be moved + // * @param {Date} dt The new start date for the event (the existing event start time will be preserved) + // */ + // 'eventmove', + // /** + // * @event eventcopy + // * Fires after the user selects a date in the calendar picker under the "copy event" menu option. + // * Note that this menu does not actually update the event in the data store. This is simply a + // * notification that the menu option was selected -- it is the responsibility of handling code + // * to perform the copy action. + // * @param {Extensible.calendar.menu.Event} this + // * @param {Extensible.calendar.data.EventModel} rec The {@link Extensible.calendar.data.EventModel + // * record} for the event to be copied + // * @param {Date} dt The start date for the event copy (the existing event start time will + // * be preserved) + // */ + // 'eventcopy' + //); + this.buildMenu(); this.callParent(arguments); }, 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 [ + '', + '', + '', + '', + // '', + '', + '', // events is a MixedCollection + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '
{[Ext.Date.format(values.date, \"D M j\")]}', + '{[Ext.Date.format(values.date, this.dayDateFormat)]}', + '
{[this.getEventTimesMarkupForAgendaList(values, parent.date)]}', + '{[this.getTitleMarkup(values)]}', + '', + // Display event with all details + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '
', this.locationText, ':{[values.data[Extensible.calendar.data.EventMappings.Location.name]]}
', this.webLinkText, ':{[this.getWebLinkMarkup(values, true)]}
', this.notesText, ':{[this.getNotesMarkup(values)]}
', + '
', + '

', + this.noEventsText, + '
' + ]; + }, + + /** + * Returns the template used for the simple list. + * @return {Array} A array of strings making up the template. + */ + getTemplateForSimpleList: function() { + return [ + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', // events is a MixedCollection + '', + '', + '', + '{[this.getEventTimesMarkupForSimpleList(values, parent.date)]}', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '
{[this.getGroupHeaderMarkup(values)]}

', + '{[Ext.Date.format(values.date, this.dayDateFormat)]}', + '
', + '{[this.getTitleMarkup(values)]}', + '', + // Display event with all details + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '
', this.locationText, ':{[values.data[Extensible.calendar.data.EventMappings.Location.name]]}
', this.webLinkText, ':{[this.getWebLinkMarkup(values, true)]}
', this.notesText, ':{[this.getNotesMarkup(values)]}
', + '
', + '

', + this.noEventsText, + '
' + ]; + }, + + /** + * Returns event start and end times formatted for output on the agenda list. See also {@link #getEventTimesMarkupForSimpleList}. + * @param {Extensible.calendar.data.EventModel} evt + * @param {Date} dt The date for which to produce output. + * @return {String} + */ + getEventTimesMarkupForAgendaList: function(evt, dt) { + var result, + M = Extensible.calendar.data.EventMappings, + currentDt = Ext.Date.clearTime(dt), + startDt = Ext.Date.clearTime(evt.data[M.StartDate.name], true), + endDt = Ext.Date.clearTime(evt.data[M.EndDate.name], true), + startDtTime = evt.data[M.StartDate.name], + endDtTime = evt.data[M.EndDate.name]; + + // There are five cases to consider: + // Case Output example + // ---------------------------------------------------------------+--------------- + // 1) Event is all-day event All day + // 2) Event is not all-day event + // 2.1) Start time and end time are on the current day 8:00am - 11:00am + // 2.2) Start time on current date, end time on later date 8:00 >> + // 2.3) Start time on earlier date, end time on current date >> 11:00am + // 2.4) Start time on earlier date, end time on later day All day + if (evt.data[M.IsAllDay.name]) { + result = this.allDayText; // Case 1 + } else { + if (Extensible.Date.compare(currentDt, startDt) == 0) { + if (Extensible.Date.compare(currentDt, endDt) == 0) { + result = Ext.Date.format(startDtTime, this.hourFormat) + ' - ' + Ext.Date.format(endDtTime, this.hourFormat); // Case 2.1 + } else { + result = Ext.Date.format(startDtTime, this.hourFormat) + ' »'; // Case 2.2 + } + } else { + if (Extensible.Date.compare(currentDt, endDt) == 0) { + result = '» ' + Ext.Date.format(endDtTime, this.hourFormat); // Case 2.3 + } else { + result = this.allDayText; // Case 2.4 + } + } + } + return result; + }, + + /** + * Returns event start and end times formatted for output on the simple list. See also {@link #getEventTimesMarkupForAgendaList}. + * @param {Extensible.calendar.data.EventModel} evt + * @param {Date} dt The date for which to produce output. + * @return {String} + */ + getEventTimesMarkupForSimpleList: function(evt, dt) { + var result, + M = Extensible.calendar.data.EventMappings, + currentDt = Ext.Date.clearTime(dt), + startDt = Ext.Date.clearTime(evt.data[M.StartDate.name], true), + endDt = Ext.Date.clearTime(evt.data[M.EndDate.name], true), + startDtTime = evt.data[M.StartDate.name], + endDtTime = evt.data[M.EndDate.name], + startHourStr = '', + untilStr = '-', + endDtStr = '', + endHourStr = ''; + + // This function generates HTML output that contains the following information: + // - Event start hour + // - Event end date + // - Event end hour + // Note that the event start date is not part of the output because the start date is displayed once for + // all events on the same day. + // + // There are several cases to consider: + // 1) All-day event that starts and ends on the current day. + // 2) All-day event that starts on the current day and ends on a later day. + // 3) Non-all-day event that starts and ends on the current day. + // 4) Non-all-day event that starts on the current day and ends on a later day. + // + // Generated values for the four cases are: + // Evt start hour | Evt end date | Evt end hour + // 1) All day | | + // 2) All day | Mon May 18 | + // 3) 8:00am | 5:00pm | + // 4) 8:00am | Mon May 18 | 5:00pm + + if (evt.data[M.IsAllDay.name]) { + if (startDt.getTime() == endDt.getTime()) { + // Case 1 + startHourStr = this.allDayText; + untilStr = ''; + } else { + // Case 2 + startHourStr = this.allDayText; + endDtStr = Ext.Date.format(endDt, this.dayDateFormat); + } + } else { + if (startDt.getTime() == endDt.getTime()) { + // Case 3 + startHourStr = Ext.Date.format(startDtTime, this.hourFormat); + endDtStr = Ext.Date.format(endDtTime, this.hourFormat); + } else { + // Case 4 + startHourStr = Ext.Date.format(startDtTime, this.hourFormat); + endDtStr = Ext.Date.format(endDt, this.dayDateFormat); + endHourStr = Ext.Date.format(endDtTime, this.hourFormat); + } + } + + result = [ + '', startHourStr, '', untilStr, '', + '', endDtStr, '', endHourStr, '']; + return result.join(''); + }, + + /** + * Returns the markup for the event title. + * @param {Extensible.calendar.data.EventModel} evt + * @return {String} + */ + getTitleMarkup: function(evt) { + var result, + M = Extensible.calendar.data.EventMappings, + title = evt.data[M.Title.name]; + result = [ + '', + !title || title.length == 0 ? this.defaultEventTitleText : title, + this.getReminderFlagMarkup(evt), + this.getRecurrenceFlagMarkup(evt), + '' + ]; + if (evt.data[M.Location.name] && evt.data[M.Location.name] != '' && !this.showEventDetails) { + result.push( + ' - ', + evt.data[M.Location.name] + ); + } + return result.join(''); + }, + + /** + * Returns the markup for the reminder flag, if a reminder is active. Otherwise an empty string is returned. + * @param {Extensible.calendar.data.EventModel} evt + * @return {String} + */ + getReminderFlagMarkup: function(evt) { + var M = Extensible.calendar.data.EventMappings; + return evt.data[M.Reminder.name] && evt.data[M.Reminder.name] != '' ? ' ' : ''; + }, + + /** + * Returns the markup for the recurrence flag, if recurrence is active. Otherwise an empty string is returned. + * @param {Extensible.calendar.data.EventModel} evt + * @return {String} + */ + getRecurrenceFlagMarkup: function(evt) { + var M = Extensible.calendar.data.EventMappings; + return evt.data[M.RRule.name] && evt.data[M.RRule.name] != '' ? ' ' : ''; + }, + + /** + * Returns the markup for the web link. If no web link is defined, an empty string is returned. + * @param {Extensible.calendar.data.EventModel} evt + * @param {Boolean} removeProtocol If true the 'http://' string is removed from the web link. This can be useful + * to display the web link in a user friendly way. If the web link is missing the protocol string and this + * parameter is false, then the protocol string is prepended. Defaults to false. + * @return {String} + */ + getWebLinkMarkup: function(evt, removeProtocol) { + var M = Extensible.calendar.data.EventMappings, + l = evt.data[M.Url.name]; + if (l && l != "") { + if (l.indexOf('http://') == 0) { + l = l.substr(7); + } + if (removeProtocol) { + return l; + } else { + return 'http://' + l; + } + } else { + return ''; + } + }, + + /** + * Returns the markup for the event notes. If no event notes are defined, an empty string is returned. The notes + * are limited to the number of characters specified by configuration option {@link #maxNotesLength}. + * @param {Extensible.calendar.data.EventModel} evt + * @return {String} + */ + getNotesMarkup: function(evt) { + var M = Extensible.calendar.data.EventMappings, + n = evt.data[M.Notes.name]; + return n.length > this.maxNotesLength ? n.substring(0, this.maxNotesLength-3) + '...' : n; + }, + + + /** + * Returns the markup for an event group header. The type of group header returned depends on the configured + * event grouping (see {@link #groupBy}). For example: + * Monthly grouping: June 2012 + * Weekly grouping: Week 23: Mon Jun 3 - Sun Jun 10 + * No grouping: Empty string + * @param {Object} group + * @return {String} + */ + getGroupHeaderMarkup: function(group) { + var result; + + if (this.groupBy == 'month') { + result = [Ext.Date.format(group.startDt, "F Y")]; + } else if (this.groupBy == 'week') { + if (Ext.Date.clearTime(group.startDt, true).getTime() == Ext.Date.clearTime(group.endDt, true).getTime()) { + // This is a partical week with only one day left. Don't show date range, just current date. + result = ['Week ', group.weekNo, ': ', Ext.Date.format(group.startDt, this.dayDateFormat)]; + } else { + result = ['Week ', group.weekNo, ': ', Ext.Date.format(group.startDt, this.dayDateFormat), ' - ', Ext.Date.format(group.endDt, this.dayDateFormat)]; + } + } else { + result = ['']; + } + return result.join(''); + }, + + /** + * Returns true if passed event has notes, false otherwise. This is a small helper function for the template. + * @param {Extensible.calendar.data.EventModel} An event record. + * @return {Boolean} + */ + eventHasNotes: function(evt) { + var n = evt.data[Extensible.calendar.data.EventMappings.Notes.name]; + return n && n != ""; + }, + + /** + * Returns true if passed event has a location assigned, false otherwise. This is a small helper function for the template. + * @param {Extensible.calendar.data.EventModel} An event record. + * @return {Boolean} + */ + eventHasLocation: function(evt) { + var l = evt.data[Extensible.calendar.data.EventMappings.Location.name]; + return l && l != ""; + }, + + /** + * Returns true if passed event has a link assigned, false otherwise. This is a small helper function for the template. + * @param {Extensible.calendar.data.EventModel} An event record. + * @return {Boolean} + */ + eventHasLink: function(evt) { + var url = evt.data[Extensible.calendar.data.EventMappings.Url.name]; + return url && url != ""; + }, + + /** + * Returns true if group titles are to be displayed. This is a small helper function for the template. + * @return {Boolean} + */ + hasGroupTitle: function() { + return this.groupBy == 'month' || this.groupBy == 'week' ? true : false; + } + +}, +function() { + this.createAlias('apply', 'applyTemplate'); +}); \ No newline at end of file diff --git a/src/calendar/view/AbstractCalendar.js b/src/calendar/view/AbstractCalendar.js index 1053729d..bc198fd0 100644 --- a/src/calendar/view/AbstractCalendar.js +++ b/src/calendar/view/AbstractCalendar.js @@ -9,7 +9,7 @@ */ Ext.define('Extensible.calendar.view.AbstractCalendar', { extend: 'Ext.Component', - + requires: [ 'Ext.CompositeElement', 'Extensible.calendar.form.EventDetails', @@ -344,208 +344,208 @@ Ext.define('Extensible.calendar.view.AbstractCalendar', { this.addCls('ext-cal-readonly'); } - this.addEvents({ - /** - * @event eventsrendered - * Fires after events are finished rendering in the view - * @param {Extensible.calendar.view.AbstractCalendar} this - */ - eventsrendered: true, - /** - * @event eventclick - * Fires after the user clicks on an event element. This is a cancelable event, so returning false from a - * handler will cancel the click without displaying the event editor view. This could be useful for - * validating the rules by which events should be editable by the user. - * @param {Extensible.calendar.view.AbstractCalendar} this - * @param {Extensible.calendar.data.EventModel} rec The {@link Extensible.calendar.data.EventModel record} for the event that was clicked on - * @param {HTMLNode} el The DOM node that was clicked on - */ - eventclick: true, - /** - * @event eventover - * Fires anytime the mouse is over an event element - * @param {Extensible.calendar.view.AbstractCalendar} this - * @param {Extensible.calendar.data.EventModel} rec The {@link Extensible.calendar.data.EventModel record} for the event that the cursor is over - * @param {HTMLNode} el The DOM node that is being moused over - */ - eventover: true, - /** - * @event eventout - * Fires anytime the mouse exits an event element - * @param {Extensible.calendar.view.AbstractCalendar} this - * @param {Extensible.calendar.data.EventModel} rec The {@link Extensible.calendar.data.EventModel record} for the event that the cursor exited - * @param {HTMLNode} el The DOM node that was exited - */ - eventout: true, - /** - * @event beforedatechange - * Fires before the start date of the view changes, giving you an opportunity to save state or anything else you may need - * to do prior to the UI view changing. This is a cancelable event, so returning false from a handler will cancel both the - * view change and the setting of the start date. - * @param {Extensible.calendar.view.AbstractCalendar} this - * @param {Date} startDate The current start date of the view (as explained in {@link #getStartDate} - * @param {Date} newStartDate The new start date that will be set when the view changes - * @param {Date} viewStart The first displayed date in the current view - * @param {Date} viewEnd The last displayed date in the current view - */ - beforedatechange: true, - /** - * @event datechange - * Fires after the start date of the view has changed. If you need to cancel the date change you should handle the - * {@link #beforedatechange} event and return false from your handler function. - * @param {Extensible.calendar.view.AbstractCalendar} this - * @param {Date} startDate The start date of the view (as explained in {@link #getStartDate} - * @param {Date} viewStart The first displayed date in the view - * @param {Date} viewEnd The last displayed date in the view - */ - datechange: true, - /** - * @event rangeselect - * Fires after the user drags on the calendar to select a range of dates/times in which to create an event. This is a - * cancelable event, so returning false from a handler will cancel the drag operation and clean up any drag shim elements - * without displaying the event editor view. This could be useful for validating that a user can only create events within - * a certain range. - * @param {Extensible.calendar.view.AbstractCalendar} this - * @param {Object} dates An object containing the start (StartDate property) and end (EndDate property) dates selected - * @param {Function} callback A callback function that MUST be called after the event handling is complete so that - * the view is properly cleaned up (shim elements are persisted in the view while the user is prompted to handle the - * range selection). The callback is already created in the proper scope, so it simply needs to be executed as a standard - * function call (e.g., callback()). - */ - rangeselect: true, - /** - * @event beforeeventcopy - * Fires before an existing event is duplicated by the user via the "copy" command. This is a - * cancelable event, so returning false from a handler will cancel the copy operation. - * @param {Extensible.calendar.view.AbstractCalendar} this - * @param {Extensible.calendar.data.EventModel} rec The {@link Extensible.calendar.data.EventModel - * record} for the event that will be copied - * @param {Date} dt The new start date to be set in the copy (the end date will be automaticaly - * adjusted to match the original event duration) - */ - beforeeventcopy: true, - /** - * @event eventcopy - * Fires after an event has been duplicated by the user via the "copy" command. If you need to - * cancel the copy operation you should handle the {@link #beforeeventcopy} event and return - * false from your handler function. - * @param {Extensible.calendar.view.AbstractCalendar} this - * @param {Extensible.calendar.data.EventModel} rec The {@link Extensible.calendar.data.EventModel - * record} for the event that was copied (with updated start and end dates) - */ - eventcopy: true, - /** - * @event beforeeventmove - * Fires after an event element has been dragged by the user and dropped in a new position, but before - * the event record is updated with the new dates, providing a hook for canceling the update. - * To cancel the move, return false from a handling function. This could be useful for validating - * that a user can only move events within a certain date range, for example. - * @param {Extensible.calendar.view.AbstractCalendar} this - * @param {Extensible.calendar.data.EventModel} rec The {@link Extensible.calendar.data.EventModel record} - * for the event that will be moved. Start and end dates will be the original values before the move started. - * @param {Date} dt The new start date to be set (the end date will be automaticaly calculated to match - * based on the event duration) - */ - beforeeventmove: true, - /** - * @event eventmove - * Fires after an event element has been moved to a new position and its data updated. If you need to - * cancel the move operation you should handle the {@link #beforeeventmove} event and return false - * from your handler function. - * @param {Extensible.calendar.view.AbstractCalendar} this - * @param {Extensible.calendar.data.EventModel} rec The {@link Extensible.calendar.data.EventModel record} - * for the event that was moved with updated start and end dates - */ - eventmove: true, - /** - * @event initdrag - * Fires when a drag operation is initiated in the view - * @param {Extensible.calendar.view.AbstractCalendar} this - */ - initdrag: true, - /** - * @event dayover - * Fires while the mouse is over a day element - * @param {Extensible.calendar.view.AbstractCalendar} this - * @param {Date} dt The date that is being moused over - * @param {Ext.Element} el The day Element that is being moused over - */ - dayover: true, - /** - * @event dayout - * Fires when the mouse exits a day element - * @param {Extensible.calendar.view.AbstractCalendar} this - * @param {Date} dt The date that is exited - * @param {Ext.Element} el The day Element that is exited - */ - dayout: true, - /** - * @event editdetails - * Fires when the user selects the option in this window to continue editing in the detailed edit form - * (by default, an instance of {@link Extensible.calendar.form.EventDetails}. Handling code should hide this window - * and transfer the current event record to the appropriate instance of the detailed form by showing it - * and calling {@link Extensible.calendar.form.EventDetails#loadRecord loadRecord}. - * @param {Extensible.calendar.view.AbstractCalendar} this - * @param {Extensible.calendar.data.EventModel} rec The {@link Extensible.calendar.data.EventModel record} that is currently being edited - * @param {Ext.Element} el The target element - */ - editdetails: true, - /** - * @event eventadd - * Fires after a new event has been added to the underlying store - * @param {Extensible.calendar.view.AbstractCalendar} this - * @param {Extensible.calendar.data.EventModel} rec The new {@link Extensible.calendar.data.EventModel record} that was added - */ - eventadd: true, - /** - * @event eventupdate - * Fires after an existing event has been updated - * @param {Extensible.calendar.view.AbstractCalendar} this - * @param {Extensible.calendar.data.EventModel} rec The new {@link Extensible.calendar.data.EventModel record} that was updated - */ - eventupdate: true, - /** - * @event eventcancel - * Fires after an event add/edit operation has been canceled by the user and no store update took place - * @param {Extensible.calendar.view.AbstractCalendar} this - * @param {Extensible.calendar.data.EventModel} rec The new {@link Extensible.calendar.data.EventModel record} that was canceled - */ - eventcancel: true, - /** - * @event beforeeventdelete - * Fires before an event is deleted by the user. This is a cancelable event, so returning false from a handler - * will cancel the delete operation. - * @param {Extensible.calendar.view.AbstractCalendar} this - * @param {Extensible.calendar.data.EventModel} rec The {@link Extensible.calendar.data.EventModel record} for the event that was deleted - * @param {Ext.Element} el The target element - */ - beforeeventdelete: true, - /** - * @event eventdelete - * Fires after an event has been deleted by the user. If you need to cancel the delete operation you should handle the - * {@link #beforeeventdelete} event and return false from your handler function. - * @param {Extensible.calendar.view.AbstractCalendar} this - * @param {Extensible.calendar.data.EventModel} rec The {@link Extensible.calendar.data.EventModel record} for the event that was deleted - * @param {Ext.Element} el The target element - */ - eventdelete: true, - /** - * @event eventexception - * Fires after an event has been processed via an Ext proxy and returned with an exception. This - * could be because of a server error, or because the data returned success: false. - * - * The view provides default handling via the overrideable {@link #notifyOnException} method. If - * any function handling this event returns false, the notifyOnException method will not be called. - * - * Note that only Server proxy and subclasses (including Ajax proxy) will raise this event. - * - * @param {Extensible.calendar.view.AbstractCalendar} this - * @param {Object} response The raw response object returned from the server - * @param {Ext.data.Operation} operation The operation that was processed - * @since 1.6.0 - */ - eventexception: true - }); + //this.addEvents({ + // /** + // * @event eventsrendered + // * Fires after events are finished rendering in the view + // * @param {Extensible.calendar.view.AbstractCalendar} this + // */ + // eventsrendered: true, + // /** + // * @event eventclick + // * Fires after the user clicks on an event element. This is a cancelable event, so returning false from a + // * handler will cancel the click without displaying the event editor view. This could be useful for + // * validating the rules by which events should be editable by the user. + // * @param {Extensible.calendar.view.AbstractCalendar} this + // * @param {Extensible.calendar.data.EventModel} rec The {@link Extensible.calendar.data.EventModel record} for the event that was clicked on + // * @param {HTMLNode} el The DOM node that was clicked on + // */ + // eventclick: true, + // /** + // * @event eventover + // * Fires anytime the mouse is over an event element + // * @param {Extensible.calendar.view.AbstractCalendar} this + // * @param {Extensible.calendar.data.EventModel} rec The {@link Extensible.calendar.data.EventModel record} for the event that the cursor is over + // * @param {HTMLNode} el The DOM node that is being moused over + // */ + // eventover: true, + // /** + // * @event eventout + // * Fires anytime the mouse exits an event element + // * @param {Extensible.calendar.view.AbstractCalendar} this + // * @param {Extensible.calendar.data.EventModel} rec The {@link Extensible.calendar.data.EventModel record} for the event that the cursor exited + // * @param {HTMLNode} el The DOM node that was exited + // */ + // eventout: true, + // /** + // * @event beforedatechange + // * Fires before the start date of the view changes, giving you an opportunity to save state or anything else you may need + // * to do prior to the UI view changing. This is a cancelable event, so returning false from a handler will cancel both the + // * view change and the setting of the start date. + // * @param {Extensible.calendar.view.AbstractCalendar} this + // * @param {Date} startDate The current start date of the view (as explained in {@link #getStartDate} + // * @param {Date} newStartDate The new start date that will be set when the view changes + // * @param {Date} viewStart The first displayed date in the current view + // * @param {Date} viewEnd The last displayed date in the current view + // */ + // beforedatechange: true, + // /** + // * @event datechange + // * Fires after the start date of the view has changed. If you need to cancel the date change you should handle the + // * {@link #beforedatechange} event and return false from your handler function. + // * @param {Extensible.calendar.view.AbstractCalendar} this + // * @param {Date} startDate The start date of the view (as explained in {@link #getStartDate} + // * @param {Date} viewStart The first displayed date in the view + // * @param {Date} viewEnd The last displayed date in the view + // */ + // datechange: true, + // /** + // * @event rangeselect + // * Fires after the user drags on the calendar to select a range of dates/times in which to create an event. This is a + // * cancelable event, so returning false from a handler will cancel the drag operation and clean up any drag shim elements + // * without displaying the event editor view. This could be useful for validating that a user can only create events within + // * a certain range. + // * @param {Extensible.calendar.view.AbstractCalendar} this + // * @param {Object} dates An object containing the start (StartDate property) and end (EndDate property) dates selected + // * @param {Function} callback A callback function that MUST be called after the event handling is complete so that + // * the view is properly cleaned up (shim elements are persisted in the view while the user is prompted to handle the + // * range selection). The callback is already created in the proper scope, so it simply needs to be executed as a standard + // * function call (e.g., callback()). + // */ + // rangeselect: true, + // /** + // * @event beforeeventcopy + // * Fires before an existing event is duplicated by the user via the "copy" command. This is a + // * cancelable event, so returning false from a handler will cancel the copy operation. + // * @param {Extensible.calendar.view.AbstractCalendar} this + // * @param {Extensible.calendar.data.EventModel} rec The {@link Extensible.calendar.data.EventModel + // * record} for the event that will be copied + // * @param {Date} dt The new start date to be set in the copy (the end date will be automaticaly + // * adjusted to match the original event duration) + // */ + // beforeeventcopy: true, + // /** + // * @event eventcopy + // * Fires after an event has been duplicated by the user via the "copy" command. If you need to + // * cancel the copy operation you should handle the {@link #beforeeventcopy} event and return + // * false from your handler function. + // * @param {Extensible.calendar.view.AbstractCalendar} this + // * @param {Extensible.calendar.data.EventModel} rec The {@link Extensible.calendar.data.EventModel + // * record} for the event that was copied (with updated start and end dates) + // */ + // eventcopy: true, + // /** + // * @event beforeeventmove + // * Fires after an event element has been dragged by the user and dropped in a new position, but before + // * the event record is updated with the new dates, providing a hook for canceling the update. + // * To cancel the move, return false from a handling function. This could be useful for validating + // * that a user can only move events within a certain date range, for example. + // * @param {Extensible.calendar.view.AbstractCalendar} this + // * @param {Extensible.calendar.data.EventModel} rec The {@link Extensible.calendar.data.EventModel record} + // * for the event that will be moved. Start and end dates will be the original values before the move started. + // * @param {Date} dt The new start date to be set (the end date will be automaticaly calculated to match + // * based on the event duration) + // */ + // beforeeventmove: true, + // /** + // * @event eventmove + // * Fires after an event element has been moved to a new position and its data updated. If you need to + // * cancel the move operation you should handle the {@link #beforeeventmove} event and return false + // * from your handler function. + // * @param {Extensible.calendar.view.AbstractCalendar} this + // * @param {Extensible.calendar.data.EventModel} rec The {@link Extensible.calendar.data.EventModel record} + // * for the event that was moved with updated start and end dates + // */ + // eventmove: true, + // /** + // * @event initdrag + // * Fires when a drag operation is initiated in the view + // * @param {Extensible.calendar.view.AbstractCalendar} this + // */ + // initdrag: true, + // /** + // * @event dayover + // * Fires while the mouse is over a day element + // * @param {Extensible.calendar.view.AbstractCalendar} this + // * @param {Date} dt The date that is being moused over + // * @param {Ext.Element} el The day Element that is being moused over + // */ + // dayover: true, + // /** + // * @event dayout + // * Fires when the mouse exits a day element + // * @param {Extensible.calendar.view.AbstractCalendar} this + // * @param {Date} dt The date that is exited + // * @param {Ext.Element} el The day Element that is exited + // */ + // dayout: true, + // /** + // * @event editdetails + // * Fires when the user selects the option in this window to continue editing in the detailed edit form + // * (by default, an instance of {@link Extensible.calendar.form.EventDetails}. Handling code should hide this window + // * and transfer the current event record to the appropriate instance of the detailed form by showing it + // * and calling {@link Extensible.calendar.form.EventDetails#loadRecord loadRecord}. + // * @param {Extensible.calendar.view.AbstractCalendar} this + // * @param {Extensible.calendar.data.EventModel} rec The {@link Extensible.calendar.data.EventModel record} that is currently being edited + // * @param {Ext.Element} el The target element + // */ + // editdetails: true, + // /** + // * @event eventadd + // * Fires after a new event has been added to the underlying store + // * @param {Extensible.calendar.view.AbstractCalendar} this + // * @param {Extensible.calendar.data.EventModel} rec The new {@link Extensible.calendar.data.EventModel record} that was added + // */ + // eventadd: true, + // /** + // * @event eventupdate + // * Fires after an existing event has been updated + // * @param {Extensible.calendar.view.AbstractCalendar} this + // * @param {Extensible.calendar.data.EventModel} rec The new {@link Extensible.calendar.data.EventModel record} that was updated + // */ + // eventupdate: true, + // /** + // * @event eventcancel + // * Fires after an event add/edit operation has been canceled by the user and no store update took place + // * @param {Extensible.calendar.view.AbstractCalendar} this + // * @param {Extensible.calendar.data.EventModel} rec The new {@link Extensible.calendar.data.EventModel record} that was canceled + // */ + // eventcancel: true, + // /** + // * @event beforeeventdelete + // * Fires before an event is deleted by the user. This is a cancelable event, so returning false from a handler + // * will cancel the delete operation. + // * @param {Extensible.calendar.view.AbstractCalendar} this + // * @param {Extensible.calendar.data.EventModel} rec The {@link Extensible.calendar.data.EventModel record} for the event that was deleted + // * @param {Ext.Element} el The target element + // */ + // beforeeventdelete: true, + // /** + // * @event eventdelete + // * Fires after an event has been deleted by the user. If you need to cancel the delete operation you should handle the + // * {@link #beforeeventdelete} event and return false from your handler function. + // * @param {Extensible.calendar.view.AbstractCalendar} this + // * @param {Extensible.calendar.data.EventModel} rec The {@link Extensible.calendar.data.EventModel record} for the event that was deleted + // * @param {Ext.Element} el The target element + // */ + // eventdelete: true, + // /** + // * @event eventexception + // * Fires after an event has been processed via an Ext proxy and returned with an exception. This + // * could be because of a server error, or because the data returned success: false. + // * + // * The view provides default handling via the overrideable {@link #notifyOnException} method. If + // * any function handling this event returns false, the notifyOnException method will not be called. + // * + // * Note that only Server proxy and subclasses (including Ajax proxy) will raise this event. + // * + // * @param {Extensible.calendar.view.AbstractCalendar} this + // * @param {Object} response The raw response object returned from the server + // * @param {Ext.data.Operation} operation The operation that was processed + // * @since 1.6.0 + // */ + // eventexception: true + //}); }, afterRender: function() { @@ -962,9 +962,13 @@ Ext.define('Extensible.calendar.view.AbstractCalendar', { // edited or was recurring before being edited AND an event store reload has not been triggered already for // this operation. If an event is not currently recurring (isRecurring = false) but still has an instance // start date set, then it must have been recurring and edited to no longer recur. - var RInstanceStartDate = Extensible.calendar.data.EventMappings.RInstanceStartDate, - isInstance = RInstanceStartDate && !!operation.records[0].get(RInstanceStartDate.name), - reload = (operation.records[0].isRecurring() || isInstance) && !operation.wasStoreReloadTriggered; + var records = 'Ext.data.operation.Destroy' == Ext.getClass(operation).getName()? operation.getResultSet().getRecords() : operation.getRecords(), + record = records[0], + RInstanceStartDate = Extensible.calendar.data.EventMappings.RInstanceStartDate, + isInstance = RInstanceStartDate && !!record.get(RInstanceStartDate.name), + reload = isInstance && !operation.wasStoreReloadTriggered; + + //reload = (record.isRecurring() || isInstance) && !operation.wasStoreReloadTriggered; if (reload) { // For calendar views with a body and a header component (e.g. weekly view, day view), this function is @@ -987,7 +991,7 @@ Ext.define('Extensible.calendar.view.AbstractCalendar', { this.refreshAfterEventChange('update', operation); - var rec = operation.records[0]; + var records = operation.getRecords(), rec = records[0]; if (this.enableFx && this.enableUpdateFx) { this.doUpdateFx(this.getEventEls(rec.data[Extensible.calendar.data.EventMappings.EventId.name]), { @@ -1012,7 +1016,7 @@ Ext.define('Extensible.calendar.view.AbstractCalendar', { }, onAdd: function(store, operation) { - var rec = operation.records[0]; + var records = operation.getRecords(), rec = records[0]; if (this.hidden === true || this.ownerCt.hidden === true || this.monitorStoreEvents === false) { // Hidden calendar view don't need to be refreshed. For views composed of header and body (for example @@ -1064,18 +1068,20 @@ Ext.define('Extensible.calendar.view.AbstractCalendar', { Extensible.log('onRemove'); this.dismissEventEditor(); - var rec = operation.records[0]; + if (operation.getResultSet()){ + var records = operation.getResultSet().getRecords(), rec = records[0]; - if (this.enableFx && this.enableRemoveFx) { - this.doRemoveFx(this.getEventEls(rec.data[Extensible.calendar.data.EventMappings.EventId.name]), { - remove: true, - scope: this, - callback: Ext.bind(this.refreshAfterEventChange, this, ['delete', operation]) - }); - } - else { - this.getEventEls(rec.data[Extensible.calendar.data.EventMappings.EventId.name]).remove(); - this.refreshAfterEventChange('delete', operation); + if (this.enableFx && this.enableRemoveFx) { + this.doRemoveFx(this.getEventEls(rec.data[Extensible.calendar.data.EventMappings.EventId.name]), { + remove: true, + scope: this, + callback: Ext.bind(this.refreshAfterEventChange, this, ['delete', operation]) + }); + } + else { + this.getEventEls(rec.data[Extensible.calendar.data.EventMappings.EventId.name]).remove(); + this.refreshAfterEventChange('delete', operation); + } } }, @@ -1441,8 +1447,27 @@ Ext.define('Extensible.calendar.view.AbstractCalendar', { // remain sorted sequentially by start time. This seems more proper // but can make for a less visually-compact layout when there are // many such events mixed together closely on the calendar. - return a[M.StartDate.name].getTime() - b[M.StartDate.name].getTime(); + + // Events are sorted by three criteria: Start time, end time and + // calendar id. The calendar id is used as the third sort criteria + // to ensure that events are always ordered the same way. Without + // that third criteria, events that start at the same time and end at + // the same time would be ordered randomly. + var sortStartDate = a[M.StartDate.name].getTime() - b[M.StartDate.name].getTime() + if (sortStartDate){ + return sortStartDate; + } + var sortEndDate = b[M.EndDate.name].getTime() - a[M.EndDate.name].getTime(); //descending + if (sortEndDate){ + return sortEndDate; + } + var sortCalendar = a[M.CalendarId.name] - b[M.CalendarId.name];//ascending + if (sortCalendar){ + return sortCalendar; + } + return 0; } + }, this)); }, @@ -1558,14 +1583,29 @@ Ext.define('Extensible.calendar.view.AbstractCalendar', { Ext.each(operation.records, function(rec) { if (rec.dirty) { if (rec.phantom) { - rec.unjoin(this.eventStore); + this.store.remove(rec); } else { rec.reject(); } } }, this); - + + // Restore deleted records back to their original positions. + // This code was copied from ExtJS V4.2.2 Ext.data.Store, function rejectChanges(). In order to maintain + // backwards compatibility with version 4.0.7, this function cannot be called directly. + var recs = this.store.removed, + len = recs.length, + i = 0, rec; + + for (i = len-1; i >= 0; i--) { + rec = recs[i]; + this.store.insert(rec.removedFrom || 0, rec); + rec.reject(); + } + // Since removals are cached in a simple array we can simply reset it here. + this.store.removed.length = 0; + if (this.fireEvent('eventexception', this, response, operation) !== false) { this.notifyOnException(response, operation); } @@ -1641,13 +1681,15 @@ Ext.define('Extensible.calendar.view.AbstractCalendar', { }, getEventRecord: function(id) { - var idx = this.store.find(Extensible.calendar.data.EventMappings.EventId.name, id, - 0, // start index - false, // match any part of string - true, // case sensitive - true // force exact match - ); - return this.store.getAt(idx); + //var idx = this.store.find(Extensible.calendar.data.EventMappings.EventId.name, id, + // 0, // start index + // false, // match any part of string + // true, // case sensitive + // true // force exact match + //); + //return this.store.getAt(idx); + + return this.store.getById(id); }, getEventRecordFromEl: function(el) { @@ -1761,8 +1803,6 @@ Ext.define('Extensible.calendar.view.AbstractCalendar', { onWrite: function(store, operation) { if (operation.wasSuccessful()) { - //var rec = operation.records[0]; - switch(operation.action) { case 'create': this.onAdd(store, operation); @@ -2025,7 +2065,7 @@ Ext.define('Extensible.calendar.view.AbstractCalendar', { if (el) { var id = me.getEventIdFromEl(el), rec = me.getEventRecord(id); - + if (rec && me.fireEvent('eventclick', me, rec, el) !== false) { if (me.readOnly !== true) { me.showEventEditor(rec, el); diff --git a/src/calendar/view/Agenda.js b/src/calendar/view/Agenda.js new file mode 100644 index 00000000..5a6b81be --- /dev/null +++ b/src/calendar/view/Agenda.js @@ -0,0 +1,331 @@ +/** + * @class Extensible.calendar.view.Agenda + * @extends Ext.Container + * + *

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)'); + // 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: + * For example:

+     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..6a2ce91b --- /dev/null +++ b/src/calendar/view/AgendaBody.js @@ -0,0 +1,667 @@ +/** + * @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 + }; + + /** + * @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. + */ + + 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; i endDtView.getTime()) { + endDtGroup = endDtView; + } + } else if (this.groupBy == 'week') { + // End date is the end of the current week or the end date of the view, whichever is earlier. + // Take into consideration that week start day is configurable. + dayOfWeek = startDtGroup.getDay(); + endDtGroup = Ext.Date.add(Ext.Date.add(startDtGroup, Ext.Date.DAY, (6 - dayOfWeek + this.startDay) % 7 + 1), Ext.Date.SECOND, -1); + if (endDtGroup.getTime() > endDtView.getTime()) { + endDtGroup = endDtView; + } + } else { + // There is only one group, therefore end date of group is end date of view + endDtGroup = endDtView; + } + + // Check if we have reached the end of the viewing period + if (startDtGroup.getTime() > endDtEvent.getTime() || startDtGroup.getTime() > endDtView.getTime()) { + break; + } + + // Create group key. The group key is used to find a group in the list of groups. The + // format of the group key depends on the configured grouping. + if (this.groupBy == 'month') { + groupKey = Ext.Date.format(startDtGroup, 'my'); + } else if (this.groupBy == 'week') { + weekInfo = this.getWeekInfo(Ext.Date.clearTime(endDtGroup)); + groupKey = weekInfo.weekNo + '_' + weekInfo.weekYear; + } else { + groupKey = 'defaultKey'; + } + + // Create a day key + dayKey = Ext.Date.format(startDtEvent, 'dmy'); + + // Check if an array representing the current group already exists + group = groups[groupKey]; + if (typeof group == 'undefined') { + group = { + startDt: startDtGroup, + endDt: endDtGroup, + days: {} + }; + if (this.groupBy == 'week') { + group.weekNo = weekInfo.weekNo; + group.weekYear = weekInfo.weekYear; + } + groups[groupKey] = group; + } + + // Add event to list of events for the day on which the event starts. + day = group.days[dayKey]; + if (typeof day == 'undefined') { + day = { + date: startDtEvent, + events: Ext.create(Ext.util.MixedCollection) // Use a MixedCollection here such that we can use the existing event sorting function that works with MixedCollections + }; + group.days[dayKey] = day; + } + day.events.add(event.data[M.EventId.name], event); + } + } + + // Sort events within days, days within groups and groups among themselves. To be able to sort, the groups and days + // objects are converted to arrays. + for (groupKey in groups) { + group = groups[groupKey]; + + daysArray = []; + for (dayKey in group.days) { + var day = group.days[dayKey]; + this.sortEventRecordsForDay(day.events); + daysArray.push(day); + } + // Sort days within group + daysArray.sort(function(a, b) { + return Dt.compare(b.date, a.date); + }); + group.days = daysArray; + groupsArray.push(group); + } + // Sort groups + groupsArray.sort(function(a, b) { + return Dt.compare(b.startDt, a.startDt); + }); + + return groupsArray; + }, + + /** + *

Returns 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; i 0 ? currDt = startDtView : currDt = startDtEvent; + + // Loop over each day the event spans and that is within the view period. + while (Dt.compare(currDt, endDtEvent) >= 0 && Dt.compare(currDt, endDtView) >= 0) { + var day; + + // Check if already a day record exists for the current day + day = days[Ext.Date.format(currDt, 'dmy')]; + if (typeof day == 'undefined') { + day = { + date: currDt, + events: Ext.create(Ext.util.MixedCollection) // Use a MixedCollection here such that we can use the existing event sorting function that works with MixedCollections + }; + days[Ext.Date.format(currDt, 'dmy')] = day; + } + day.events.add(event.data[M.EventId.name], event); + + currDt = Ext.Date.add(currDt, Ext.Date.DAY, 1); + } + } + + // Convert days from object to array and sort events within a day + for (date in days) { + this.sortEventRecordsForDay(days[date].events); + daysArray.push(days[date]); + } + // Sort days + daysArray.sort(function(a, b) { + return Dt.compare(b.date, a.date); + }); + + return daysArray; + }, + + /** + * Returns a list of events for the current view period. Attributes needed for processing the + * template are applied to the events (event colors, custom classes). + * + * @return {Ext.util.MixedCollection} Returns a collection of events objects of class + * Extensible.calendar.data.EventModel. + */ + getEvents : function(){ + var M = Extensible.calendar.data.EventMappings, + CM = Extensible.calendar.data.CalendarMappings, + events, + extraClasses, + colorCls = 'x-cal-default', + + events = this.store.queryBy(function(rec){ + return this.isEventVisible(rec.data); + }, this); + + // Loop over all events of view period and apply additional attributes to events that are needed for output. + for (var i=0; iFor a given date, returns the number of the week and the year to which the week belongs.

+ *

In 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)'); + 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..3d39f0b6 --- /dev/null +++ b/src/calendar/view/AgendaHeader.js @@ -0,0 +1,472 @@ +/** + * @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); + + /** + * @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}. + */ + + /** + * @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}. + */ + + }, + + /** + *

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: '
  • {name} 
' + }] + }; + + if (this.simpleList) { + formItems.items.push({ + xtype: 'combo', + id: this.id+'-groupby', + mode: 'local', + value: this.groupBy, + triggerAction: 'all', + forceSelection: true, + editable: false, + fieldLabel: this.groupByText, + name: 'groupby', + displayField: 'name', + valueField: 'value', + queryMode: 'local', + store: Ext.create('Ext.data.Store', { + fields : ['name', 'value'], + data : this.groupByOptions + }), + // 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: '
  • {name} 
' + }); + } + + formItems.items.push({ + xtype: 'checkboxfield', + id: this.id+'-showdetails', + value: this.showDetailsDefault, + inputvalue: '1', + fieldLabel: this.showDetailsText, + name: 'details' + }); + + return [formItems]; + }, + + /** + *

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)'); + 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/DayBody.js b/src/calendar/view/DayBody.js index c1ef35a4..246a94fe 100644 --- a/src/calendar/view/DayBody.js +++ b/src/calendar/view/DayBody.js @@ -29,42 +29,42 @@ Ext.define('Extensible.calendar.view.DayBody', { this.incrementsPerHour = this.hourIncrement / this.ddIncrement; this.minEventHeight = this.minEventDisplayMinutes / (this.hourIncrement / this.hourHeight); - this.addEvents({ - /** - * @event beforeeventresize - * Fires after the user drags the resize handle of an event to resize it, but before the resize - * operation is carried out. This is a cancelable event, so returning false from a handler will - * cancel the resize operation. - * @param {Extensible.calendar.view.DayBody} this - * @param {Extensible.calendar.data.EventModel} rec The original {@link - * Extensible.calendar.data.EventModel record} for the event that was resized - * @param {Object} data An object containing the new start and end dates that will be set into the - * event record if the event is not canceled. Format of the object is: {StartDate: [date], EndDate: [date]} - */ - beforeeventresize: true, - /** - * @event eventresize - * Fires after the user has drag-dropped the resize handle of an event and the resize operation is - * complete. If you need to cancel the resize operation you should handle the {@link #beforeeventresize} - * event and return false from your handler function. - * @param {Extensible.calendar.view.DayBody} this - * @param {Extensible.calendar.data.EventModel} rec The {@link Extensible.calendar.data.EventModel - * record} for the event that was resized containing the updated start and end dates - */ - eventresize: true, - /** - * @event dayclick - * Fires after the user clicks within the view container and not on an event element. This is a - * cancelable event, so returning false from a handler will cancel the click without displaying the event - * editor view. This could be useful for validating that a user can only create events on certain days. - * @param {Extensible.calendar.view.DayBody} this - * @param {Date} dt The date/time that was clicked on - * @param {Boolean} allday True if the day clicked on represents an all-day box, else false. Clicks - * within the DayBodyView always return false for this param. - * @param {Ext.Element} el The Element that was clicked on - */ - dayclick: true - }); + //this.addEvents({ + // /** + // * @event beforeeventresize + // * Fires after the user drags the resize handle of an event to resize it, but before the resize + // * operation is carried out. This is a cancelable event, so returning false from a handler will + // * cancel the resize operation. + // * @param {Extensible.calendar.view.DayBody} this + // * @param {Extensible.calendar.data.EventModel} rec The original {@link + // * Extensible.calendar.data.EventModel record} for the event that was resized + // * @param {Object} data An object containing the new start and end dates that will be set into the + // * event record if the event is not canceled. Format of the object is: {StartDate: [date], EndDate: [date]} + // */ + // beforeeventresize: true, + // /** + // * @event eventresize + // * Fires after the user has drag-dropped the resize handle of an event and the resize operation is + // * complete. If you need to cancel the resize operation you should handle the {@link #beforeeventresize} + // * event and return false from your handler function. + // * @param {Extensible.calendar.view.DayBody} this + // * @param {Extensible.calendar.data.EventModel} rec The {@link Extensible.calendar.data.EventModel + // * record} for the event that was resized containing the updated start and end dates + // */ + // eventresize: true, + // /** + // * @event dayclick + // * Fires after the user clicks within the view container and not on an event element. This is a + // * cancelable event, so returning false from a handler will cancel the click without displaying the event + // * editor view. This could be useful for validating that a user can only create events on certain days. + // * @param {Extensible.calendar.view.DayBody} this + // * @param {Date} dt The date/time that was clicked on + // * @param {Boolean} allday True if the day clicked on represents an all-day box, else false. Clicks + // * within the DayBodyView always return false for this param. + // * @param {Ext.Element} el The Element that was clicked on + // */ + // dayclick: true + //}); }, initDD: function() { @@ -405,11 +405,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 +429,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 +448,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= lastEventEnding) { + // This event does not overlap with the current event group. Start a new event group. + eventGroups.push(columns); + columns = []; + lastEventEnding = 0; + } + var placed = false; + + for (j = 0; j < columns.length; j++) { + var col = columns[ j ]; + if (!this.isOverlapping( col[col.length-1], evt ) ) { + col.push(evt); + placed = true; + break; } } + + if (!placed) { + columns.push([evt]); + } + + // Remember the last event time of the event group. + // Very short events have a minimum duration on screen (we can't see a one minute event). + var eventDuration = evt.data[M.EndDate.name].getTime() - evt.data[M.StartDate.name].getTime(); + var eventEnding; + if (eventDuration < minEventDuration) { + eventEnding = evt.data[M.StartDate.name].getTime() + minEventDuration; + } else { + eventEnding = evt.data[M.EndDate.name].getTime(); + } + if (eventEnding > lastEventEnding) { + lastEventEnding = eventEnding; + } } - // rendering loop + // Push the last event group, if there is one. + if(columns.length > 0){ + eventGroups.push(columns); + } + + // Rendering loop + l = eventGroups.length; + // Loop over all the event groups. for (i = 0; i < l; i++) { - evt = evts[i].data; - dt = evt[Extensible.calendar.data.EventMappings.StartDate.name].getDate(); + var evtGroup = eventGroups[i]; + var numColumns = evtGroup.length; + + // Loop over all the virtual columns of an event group + for (j = 0; j < numColumns; j++) { + col = evtGroup[j]; - if(evt._overlap !== undefined) { - var colWidth = 100 / (overlapCols[dt]+1), - evtWidth = 100 - (colWidth * evt._overlap); + // Loop over all the events of a virtual column + for (var k = 0; k < col.length; k++) { + evt = col[k]; - evt._width = colWidth; - evt._left = colWidth * evt._overcol; + // Check if event is rightmost of a group and can be expanded to the right + var colSpan = this.expandEvent(evt, j, evtGroup); + + evt.data._width = (100 * colSpan / numColumns); + evt.data._left = (j / numColumns) * 100; + var markup = this.getEventTemplate().apply(evt.data), + target = this.id + '-day-col-' + Ext.Date.format(evt.date, 'Ymd'); + Ext.DomHelper.append(target, markup); + } } - var markup = this.getEventTemplate().apply(evt), - target = this.id + '-day-col-' + Ext.Date.format(evts[i].date, 'Ymd'); - - Ext.DomHelper.append(target, markup); } this.fireEvent('eventsrendered', this); + }, + + /** + * Expand events at the far right to use up any remaining space. This implements step 5 in the layout + * algorithm described here: http://stackoverflow.com/questions/11311410/ + * @param {Object} evt Event to process. + * @param {int} iColumn Virtual column to where the event will be rendered. + * @param {Array} columns List of virtual colums for event group. Each column contains a list of events. + * @return {Number} + */ + expandEvent: function(evt, iColumn, columns) { + var colSpan = 1; + + // To see the output without event expansion, uncomment + // the line below. Watch column 3 in the output. + // return colSpan; + + for (var i = iColumn + 1; i < columns.length; i++) + { + var col = columns[i]; + for (var j = 0; j < col.length; j++) + { + var evt1 = col[j]; + if (this.isOverlapping(evt, evt1)) + { + return colSpan; + } + } + colSpan++; + } + return colSpan; }, getDayEl: function(dt) { diff --git a/src/calendar/view/Month.js b/src/calendar/view/Month.js index a6f27567..7b39ac74 100644 --- a/src/calendar/view/Month.js +++ b/src/calendar/view/Month.js @@ -88,36 +88,36 @@ Ext.define('Extensible.calendar.view.Month', { initComponent: function() { this.callParent(arguments); - - this.addEvents({ - /** - * @event dayclick - * Fires after the user clicks within the view container and not on an event element. This is a - * cancelable event, so returning false from a handler will cancel the click without displaying the event - * editor view. This could be useful for validating that a user can only create events on certain days. - * @param {Extensible.calendar.view.Month} this - * @param {Date} dt The date/time that was clicked on - * @param {Boolean} allday True if the day clicked on represents an all-day box, else false. Clicks - * within the MonthView always return true for this param. - * @param {Ext.Element} el The Element that was clicked on - */ - dayclick: true, - /** - * @event weekclick - * Fires after the user clicks within a week link (when {@link #showWeekLinks is true) - * @param {Extensible.calendar.view.Month} this - * @param {Date} dt The start date of the week that was clicked on - */ - weekclick: true, - /** - * @protected - */ - dayover: true, - /** - * @protected - */ - dayout: true - }); + + //this.addEvents({ + // /** + // * @event dayclick + // * Fires after the user clicks within the view container and not on an event element. This is a + // * cancelable event, so returning false from a handler will cancel the click without displaying the event + // * editor view. This could be useful for validating that a user can only create events on certain days. + // * @param {Extensible.calendar.view.Month} this + // * @param {Date} dt The date/time that was clicked on + // * @param {Boolean} allday True if the day clicked on represents an all-day box, else false. Clicks + // * within the MonthView always return true for this param. + // * @param {Ext.Element} el The Element that was clicked on + // */ + // dayclick: true, + // /** + // * @event weekclick + // * Fires after the user clicks within a week link (when {@link #showWeekLinks is true) + // * @param {Extensible.calendar.view.Month} this + // * @param {Date} dt The start date of the week that was clicked on + // */ + // weekclick: true, + // /** + // * @protected + // */ + // dayover: true, + // /** + // * @protected + // */ + // dayout: true + //}); }, initDD: function() { @@ -527,7 +527,7 @@ Ext.define('Extensible.calendar.view.Month', { p.setHeight(calculatedHeight); p.show(); - p.getPositionEl().alignTo(dayEl, 't-t?'); + p.alignTo(dayEl, 't-t?'); }, onHide: function() { diff --git a/src/calendar/view/MonthDayDetail.js b/src/calendar/view/MonthDayDetail.js index f3618e71..9ea755c6 100644 --- a/src/calendar/view/MonthDayDetail.js +++ b/src/calendar/view/MonthDayDetail.js @@ -15,10 +15,6 @@ Ext.define('Extensible.calendar.view.MonthDayDetail', { initComponent: function() { this.callParent(arguments); - - this.addEvents({ - eventsrendered: true - }); }, afterRender: function() { diff --git a/src/data/Model.js b/src/data/Model.js index da35ae75..d8625dd4 100644 --- a/src/data/Model.js +++ b/src/data/Model.js @@ -5,7 +5,10 @@ Ext.define('Extensible.data.Model', { extend: 'Ext.data.Model', requires: [ - 'Ext.util.MixedCollection' + 'Ext.util.MixedCollection', + 'Ext.data.field.Date', + 'Ext.data.field.Boolean', + 'Ext.data.field.Field' ], // *Must* be defined by subclasses @@ -49,11 +52,19 @@ Ext.define('Extensible.data.Model', { } } - proto.fields.clear(); + proto.fields.length = 0; len = fields.length; - + for (; i < len; i++) { - proto.fields.add(Ext.create('Ext.data.Field', fields[i])); + if ('date' == fields[i]['type']) { + proto.fields.push(Ext.create('Ext.data.field.Date', fields[i])); + } else if ('boolean' == fields[i]['type']){ + proto.fields.push(Ext.create('Ext.data.field.Boolean', fields[i])); + } else if ('int' == fields[i]['type']){ + proto.fields.push(Ext.create('Ext.data.field.Integer', fields[i])); + } else { + proto.fields.push(Ext.create('Ext.data.field.Field', fields[i])); + } } return this; } @@ -74,10 +85,10 @@ Ext.define('Extensible.data.Model', { */ clone: function(preserveId) { var copy = Ext.create(this.$className), - dataProp = this.persistenceProperty; - + dataProp = 'data'; + copy[dataProp] = Ext.Object.merge({}, this[dataProp]); - + if (preserveId !== true) { delete copy[dataProp][this.idProperty]; } diff --git a/src/form/recurrence/AbstractOption.js b/src/form/recurrence/AbstractOption.js index 7bcdaf2a..bec63743 100644 --- a/src/form/recurrence/AbstractOption.js +++ b/src/form/recurrence/AbstractOption.js @@ -56,18 +56,18 @@ Ext.define('Extensible.form.recurrence.AbstractOption', { initComponent: function() { var me = this; - - me.addEvents( - /** - * @event change - * Fires when a user-initiated change is detected in the value of the field. - * @param {Extensible.form.recurrence.AbstractOption} this - * @param {Mixed} newValue The new value - * @param {Mixed} oldValue The old value - */ - 'change' - ); - + + //me.addEvents( + // /** + // * @event change + // * Fires when a user-initiated change is detected in the value of the field. + // * @param {Extensible.form.recurrence.AbstractOption} this + // * @param {Mixed} newValue The new value + // * @param {Mixed} oldValue The old value + // */ + // 'change' + //); + me.initRRule(); me.items = me.getItemConfigs(); diff --git a/src/form/recurrence/Fieldset.js b/src/form/recurrence/Fieldset.js index 03e80c42..f427f855 100644 --- a/src/form/recurrence/Fieldset.js +++ b/src/form/recurrence/Fieldset.js @@ -73,18 +73,18 @@ Ext.define('Extensible.form.recurrence.Fieldset', { delete me.height; me.autoHeight = true; } - - this.addEvents( - /** - * @event startchange - * Fires when the start date of the recurrence series is changed - * @param {Extensible.form.recurrence.option.Interval} this - * @param {Date} newDate The new start date - * @param {Date} oldDate The previous start date - */ - 'startchange' - ); - + + //this.addEvents( + // /** + // * @event startchange + // * Fires when the start date of the recurrence series is changed + // * @param {Extensible.form.recurrence.option.Interval} this + // * @param {Date} newDate The new start date + // * @param {Date} oldDate The previous start date + // */ + // 'startchange' + //); + me.initRRule(); me.items = [{ diff --git a/src/form/recurrence/FrequencyCombo.js b/src/form/recurrence/FrequencyCombo.js index b8ee7b01..f8f13f20 100644 --- a/src/form/recurrence/FrequencyCombo.js +++ b/src/form/recurrence/FrequencyCombo.js @@ -22,7 +22,7 @@ Ext.define('Extensible.form.recurrence.FrequencyCombo', { initComponent: function() { var me = this; - + /** * @event frequencychange * Fires when a frequency list item is selected. @@ -30,10 +30,10 @@ Ext.define('Extensible.form.recurrence.FrequencyCombo', { * @param {String} value The selected frequency value (one of the names * from {@link #frequencyOptions}, e.g. 'DAILY') */ - me.addEvents('frequencychange'); - - var freq = Extensible.form.recurrence.Parser.strings.frequency; - + //me.addEvents('frequencychange'); + + var freq = Extensible.form.recurrence.Parser.config.strings.frequency; + /** * @cfg {Array} frequencyOptions * An array of arrays, each containing the name/value pair that defines a recurring @@ -71,7 +71,7 @@ Ext.define('Extensible.form.recurrence.FrequencyCombo', { me.callParent(arguments); }, - onSelect: function(combo, records) { - this.fireEvent('frequencychange', records[0].data.id); + onSelect: function(combo, record) { + this.fireEvent('frequencychange', record.data.id); } }); \ No newline at end of file diff --git a/src/form/recurrence/option/Interval.js b/src/form/recurrence/option/Interval.js index 4c364637..36788ac6 100644 --- a/src/form/recurrence/option/Interval.js +++ b/src/form/recurrence/option/Interval.js @@ -27,16 +27,16 @@ Ext.define('Extensible.form.recurrence.option.Interval', { cls: 'extensible-recur-interval', initComponent: function() { - this.addEvents( - /** - * @event startchange - * Fires when the start date of the recurrence series is changed - * @param {Extensible.form.recurrence.option.Interval} this - * @param {Date} newDate The new start date - * @param {Date} oldDate The previous start date - */ - 'startchange' - ); + //this.addEvents( + // /** + // * @event startchange + // * Fires when the start date of the recurrence series is changed + // * @param {Extensible.form.recurrence.option.Interval} this + // * @param {Date} newDate The new start date + // * @param {Date} oldDate The previous start date + // */ + // 'startchange' + //); this.callParent(arguments); }, diff --git a/src/form/recurrence/option/Weekly.js b/src/form/recurrence/option/Weekly.js index 73ab7587..8453942a 100644 --- a/src/form/recurrence/option/Weekly.js +++ b/src/form/recurrence/option/Weekly.js @@ -41,7 +41,7 @@ Ext.define('Extensible.form.recurrence.option.Weekly', { */ getCheckboxGroupItems: function() { var weekdaysId = Extensible.form.recurrence.Parser.byDayNames, - weekdaysText = Extensible.form.recurrence.Parser.strings.dayNamesShortByIndex, + weekdaysText = Extensible.form.recurrence.Parser.config.strings.dayNamesShortByIndex, checkboxArray = [], i = this.startDay; diff --git a/src/locale/extensible-lang-de.js b/src/locale/extensible-lang-de.js index fe61a2a2..8465af1e 100644 --- a/src/locale/extensible-lang-de.js +++ b/src/locale/extensible-lang-de.js @@ -40,6 +40,8 @@ Ext.onReady(function() { dayText: 'Tag', weekText: 'Woche', monthText: 'Monat', + agendaText: 'Agenda', + listText: 'Liste', jumpToText: 'Springe zu:', goText: 'Los', multiDayText: '{0} Tage', @@ -276,13 +278,13 @@ Ext.onReady(function() { }); } - if (Extensible.form.recurrence.FrequencyCombo) { + if (exists('Extensible.form.recurrence.FrequencyCombo')) { Ext.apply(Extensible.form.recurrence.FrequencyCombo.prototype, { fieldLabel: 'Wiederholen' }); } - if (Extensible.form.recurrence.RangeEditWindow) { + if (exists('Extensible.form.recurrence.RangeEditWindow')) { Ext.apply(Extensible.form.recurrence.RangeEditWindow.prototype, { title: 'Wiederkehrender Termin', saveButtonText: 'Speichern', @@ -290,7 +292,7 @@ Ext.onReady(function() { }); } - if (Extensible.form.recurrence.RangeEditPanel) { + if (exists('Extensible.form.recurrence.RangeEditPanel')) { Ext.apply(Extensible.form.recurrence.RangeEditPanel.prototype, { headerText: 'Auf welche Termine dieser Termin-Serie möchten Sie Ihre Änderungen anwenden?', optionSingleButtonText: 'Nur diesen', @@ -302,7 +304,7 @@ Ext.onReady(function() { }); } - if (Extensible.form.recurrence.option.Interval) { + if (exists('Extensible.form.recurrence.option.Interval')) { Ext.apply(Extensible.form.recurrence.option.Interval.prototype, { dateLabelFormat: 'l, j. F', strings: { @@ -320,7 +322,7 @@ Ext.onReady(function() { }); } - if (Extensible.form.recurrence.option.Duration) { + if (exists('Extensible.form.recurrence.option.Duration')) { Ext.apply(Extensible.form.recurrence.option.Duration.prototype, { strings: { andContinuing: 'und endet', @@ -332,7 +334,7 @@ Ext.onReady(function() { }); } - if (Extensible.form.recurrence.option.Weekly) { + if (exists('Extensible.form.recurrence.option.Weekly')) { Ext.apply(Extensible.form.recurrence.option.Weekly.prototype, { strings: { on: 'am' @@ -340,7 +342,7 @@ Ext.onReady(function() { }); } - if (Extensible.form.recurrence.option.Monthly) { + if (exists('Extensible.form.recurrence.option.Monthly')) { Ext.apply(Extensible.form.recurrence.option.Monthly.prototype, { strings: { // E.g. "on the 15th day of each month/year" @@ -359,6 +361,41 @@ Ext.onReady(function() { }); } + /* + * Strings for agenda view, added in x.x.x + */ + if (exists('Extensible.calendar.template.AgendaBody')) { + Ext.apply(Extensible.calendar.template.AgendaBody.prototype, { + dayDateFormat: 'D. j. M.', + hourFormat: 'G:i', + allDayText: 'Ganzer Tag', + locationText: 'Ort', + webLinkText: 'Web Link', + notesText: 'Bemerkung', + noEventsText: 'Für den gewählten Datumsbereich existieren keine Termine.', + prevLinkText: 'Zurück', + nextLinkText: 'Weiter', + reminderTooltip: 'Erinnerung ist aktiviert', + recurringTooltip: 'Wiederkehrender Termin' + }); + } + if (exists('Extensible.calendar.view.AgendaHeader')) { + Ext.apply(Extensible.calendar.view.AgendaHeader.prototype, { + dateRangeOneDay: 'Ein Tag', + dateRangeOneWeek: 'Eine Woche', + dateRangeOneMonth: 'Ein Monat', + dateRangeThreeMonths: 'Drei Monate', + dateRangeOneYear: 'Ein Jahr', + dateRangeText: 'Datumsbereich', + groupByMonths: 'Monat', + groupByWeek: 'Woche', + groupByNone: 'Nichts', + groupByText: 'Gruppieren nach', + showDetailsText: 'Details zeigen', + addBtnText: 'Neuer Termin', + resetBtnText: 'Zurücksetzen' + }); + } }); \ No newline at end of file diff --git a/src/locale/extensible-lang-en.js b/src/locale/extensible-lang-en.js index 31de4188..1cb746a2 100644 --- a/src/locale/extensible-lang-en.js +++ b/src/locale/extensible-lang-en.js @@ -46,6 +46,8 @@ Ext.onReady(function() { dayText: 'Day', weekText: 'Week', monthText: 'Month', + agendaText: 'Agenda', + listText: 'List', jumpToText: 'Jump to:', goText: 'Go', multiDayText: '{0} Days', // deprecated @@ -365,4 +367,41 @@ Ext.onReady(function() { }); } + /* + * Strings for agenda view. Added in x.x.x + */ + if (exists('Extensible.calendar.template.AgendaBody')) { + Ext.apply(Extensible.calendar.template.AgendaBody.prototype, { + dayDateFormat: 'D M j', + hourFormat: 'g:ia', + allDayText: 'All day', + locationText: 'Location', + webLinkText: 'Web Link', + notesText: 'Notes', + noEventsText: 'There are no events for the selected date range.', + prevLinkText: 'Previous', + nextLinkText: 'Next', + reminderTooltip: 'Reminder is activated', + recurringTooltip: 'Recurring event' + }); + } + + if (exists('Extensible.calendar.view.AgendaHeader')) { + Ext.apply(Extensible.calendar.view.AgendaHeader.prototype, { + dateRangeOneDay: 'One day', + dateRangeOneWeek: 'One week', + dateRangeOneMonth: 'One month', + dateRangeThreeMonths: 'Three months', + dateRangeOneYear: 'One year', + dateRangeText: 'Date range', + groupByMonths: 'Month', + groupByWeek: 'Week', + groupByNone: 'None', + groupByText: 'Group by', + showDetailsText: 'Show details', + addBtnText: 'Add event', + resetBtnText: 'Reset' + }); + } + }); \ No newline at end of file diff --git a/src/locale/extensible-lang-en_GB.js b/src/locale/extensible-lang-en_GB.js index caecb686..4d679ea7 100644 --- a/src/locale/extensible-lang-en_GB.js +++ b/src/locale/extensible-lang-en_GB.js @@ -277,13 +277,13 @@ Ext.onReady(function() { }); } - if (Extensible.form.recurrence.FrequencyCombo) { + if (exists('Extensible.form.recurrence.FrequencyCombo')) { Ext.apply(Extensible.form.recurrence.FrequencyCombo.prototype, { fieldLabel: 'Repeats' }); } - if (Extensible.form.recurrence.RangeEditWindow) { + if (exists('Extensible.form.recurrence.RangeEditWindow')) { Ext.apply(Extensible.form.recurrence.RangeEditWindow.prototype, { title: 'Recurring Event Options', saveButtonText: 'Save', @@ -291,7 +291,7 @@ Ext.onReady(function() { }); } - if (Extensible.form.recurrence.RangeEditPanel) { + if (exists('Extensible.form.recurrence.RangeEditPanel')) { Ext.apply(Extensible.form.recurrence.RangeEditPanel.prototype, { headerText: 'There are multiple events in this series. How would you like your changes applied?', optionSingleButtonText: 'Single', @@ -303,7 +303,7 @@ Ext.onReady(function() { }); } - if (Extensible.form.recurrence.option.Interval) { + if (exists('Extensible.form.recurrence.option.Interval')) { Ext.apply(Extensible.form.recurrence.option.Interval.prototype, { dateLabelFormat: 'l, F j', strings: { @@ -321,7 +321,7 @@ Ext.onReady(function() { }); } - if (Extensible.form.recurrence.option.Duration) { + if (exists('Extensible.form.recurrence.option.Duration')) { Ext.apply(Extensible.form.recurrence.option.Duration.prototype, { strings: { andContinuing: 'and continuing', @@ -333,7 +333,7 @@ Ext.onReady(function() { }); } - if (Extensible.form.recurrence.option.Weekly) { + if (exists('Extensible.form.recurrence.option.Weekly')) { Ext.apply(Extensible.form.recurrence.option.Weekly.prototype, { strings: { on: 'on' @@ -341,7 +341,7 @@ Ext.onReady(function() { }); } - if (Extensible.form.recurrence.option.Monthly) { + if (exists('Extensible.form.recurrence.option.Monthly')) { Ext.apply(Extensible.form.recurrence.option.Monthly.prototype, { strings: { // E.g. "on the 15th day of each month/year" @@ -359,4 +359,39 @@ Ext.onReady(function() { } }); } + + if (exists('Extensible.calendar.template.AgendaBody')) { + Ext.apply(Extensible.calendar.template.AgendaBody.prototype, { + dayDateFormat: 'D M j', + hourFormat: 'G:i', + allDayText: 'All day', + locationText: 'Location', + webLinkText: 'Web Link', + notesText: 'Notes', + noEventsText: 'There are no events for the selected date range.', + prevLinkText: 'Previous', + nextLinkText: 'Next', + reminderTooltip: 'Reminder is activated', + recurringTooltip: 'Recurring event' + }); + } + + if (exists('Extensible.calendar.view.AgendaHeader')) { + Ext.apply(Extensible.calendar.view.AgendaHeader.prototype, { + dateRangeOneDay: 'One day', + dateRangeOneWeek: 'One week', + dateRangeOneMonth: 'One month', + dateRangeThreeMonths: 'Three months', + dateRangeOneYear: 'One year', + dateRangeText: 'Date range', + groupByMonths: 'Month', + groupByWeek: 'Week', + groupByNone: 'None', + groupByText: 'Group by', + showDetailsText: 'Show details', + addBtnText: 'Add event', + resetBtnText: 'Reset' + }); + } + }); \ No newline at end of file