From eb999f98a60221d4227c1b7dfbd8fed700714c60 Mon Sep 17 00:00:00 2001 From: Nathan Gray Date: Fri, 6 Nov 2015 22:57:27 +0000 Subject: [PATCH] Calendar planner: - Hover shows current time - Clicking a row adds event at that time for that user / category --- calendar/js/et2_widget_planner.js | 271 +++++++++++++++++--------- calendar/js/et2_widget_planner_row.js | 47 ++++- calendar/templates/default/app.css | 12 +- 3 files changed, 239 insertions(+), 91 deletions(-) diff --git a/calendar/js/et2_widget_planner.js b/calendar/js/et2_widget_planner.js index cdd1e490a1..5fa9071b79 100644 --- a/calendar/js/et2_widget_planner.js +++ b/calendar/js/et2_widget_planner.js @@ -99,6 +99,10 @@ var et2_calendar_planner = et2_valueWidget.extend([et2_IDetachedDOM, et2_IResize this.rows = $j(document.createElement("div")) .appendTo(this.div); + + this.vertical_bar = $j(document.createElement("div")) + .addClass('verticalBar') + .appendTo(this.div); // Used for its date calculations this.date_helper = et2_createWidget('date-time',{},null); @@ -158,96 +162,124 @@ var et2_calendar_planner = et2_valueWidget.extend([et2_IDetachedDOM, et2_IResize * than binding it for every calendar event. */ this.div.on('mouseover', '.calendar_calEvent:not(.ui-resizable):not(.rowNoEdit)', function() { + // Load the event + planner._get_event_info(this); + var that = this; - // Load the event - planner._get_event_info(this); - var that = this; + //Resizable event handler + $j(this).resizable + ({ + distance: 10, + grid: [5, 10000], + autoHide: false, + handles: 'e', + containment:'parent', - //Resizable event handler - $j(this).resizable - ({ - distance: 10, - grid: [5, 10000], - autoHide: false, - handles: 'e', - containment:'parent', - - /** - * Triggered when the resizable is created. - * - * @param {event} event - * @param {Object} ui - */ - create:function(event, ui) - { - var resizeHelper = event.target.getAttribute('data-resize'); - if (resizeHelper == 'WD' || resizeHelper == 'WDS') + /** + * Triggered when the resizable is created. + * + * @param {event} event + * @param {Object} ui + */ + create:function(event, ui) { - jQuery(this).resizable('destroy'); - } - }, + var resizeHelper = event.target.getAttribute('data-resize'); + if (resizeHelper == 'WD' || resizeHelper == 'WDS') + { + jQuery(this).resizable('destroy'); + } + }, - /** - * Triggered at the end of resizing the calEvent. - * - * @param {event} event - * @param {Object} ui - */ - stop:function(event, ui) - { - var e = new jQuery.Event('change'); - e.originalEvent = event; - e.data = {duration: 0}; - var event_data = planner._get_event_info(this); - var event_widget = planner.getWidgetById('event_'+event_data.id); - var sT = event_widget.options.value.start_m; - if (typeof this.dropEnd != 'undefined') + /** + * Triggered at the end of resizing the calEvent. + * + * @param {event} event + * @param {Object} ui + */ + stop:function(event, ui) { - var eT = parseInt(this.dropEnd.getUTCHours() * 60) + parseInt(this.dropEnd.getUTCMinutes()); - e.data.duration = ((eT - sT)/60) * 3600; + var e = new jQuery.Event('change'); + e.originalEvent = event; + e.data = {duration: 0}; + var event_data = planner._get_event_info(this); + var event_widget = planner.getWidgetById('event_'+event_data.id); + var sT = event_widget.options.value.start_m; + if (typeof this.dropEnd != 'undefined') + { + var eT = parseInt(this.dropEnd.getUTCHours() * 60) + parseInt(this.dropEnd.getUTCMinutes()); + e.data.duration = ((eT - sT)/60) * 3600; + if(event_widget) + { + event_widget.options.value.end_m = eT; + event_widget.options.value.duration = e.data.duration; + } + + // Leave the helper there until the update is done + var loading = ui.helper.clone().appendTo(ui.helper.parent()); + + // and add a loading icon so user knows something is happening + $j('.calendar_timeDemo',loading).after('
'); + + $j(this).trigger(e); + + // That cleared the resize handles, so remove for re-creation... + $j(this).resizable('destroy'); + + // Remove loading, done or not + loading.remove(); + } + // Clear the helper, re-draw if(event_widget) { - event_widget.options.value.end_m = eT; - event_widget.options.value.duration = e.data.duration; + event_widget._parent.position_event(event_widget); } + }, - // Leave the helper there until the update is done - var loading = ui.helper.clone().appendTo(ui.helper.parent()); - - // and add a loading icon so user knows something is happening - $j('.calendar_timeDemo',loading).after('
'); - - $j(this).trigger(e); - - // That cleared the resize handles, so remove for re-creation... - $j(this).resizable('destroy'); - - // Remove loading, done or not - loading.remove(); - } - // Clear the helper, re-draw - if(event_widget) + /** + * Triggered during the resize, on the drag of the resize handler + * + * @param {event} event + * @param {Object} ui + */ + resize:function(event, ui) { - event_widget._parent.position_event(event_widget); + planner._drag_helper(this,{ + top:ui.position.top, + left: ui.position.left + ui.helper.width() + },ui.helper.outerHeight()); } - }, + }); + }) + .on('mousemove', function(event) { + // Position bar by mouse + planner.vertical_bar.position({ + my: 'right-1', + of: event, + collision: 'fit' + }); + planner.vertical_bar.css('top','0px'); - /** - * Triggered during the resize, on the drag of the resize handler - * - * @param {event} event - * @param {Object} ui - */ - resize:function(event, ui) + // Get time at mouse + if(planner.options.group_by == 'month') { - planner._drag_helper(this,{ - top:ui.position.top, - left: ui.position.left + ui.helper.width() - },ui.helper.outerHeight()); + var time = planner._get_time_from_position(event.clientX, event.clientY); + } + else + { + var time = planner._get_time_from_position(event.offsetX, event.offsetY); + } + // Passing to formatter, cancel out timezone + if(time) + { + var formatDate = new Date(time.valueOf() + time.getTimezoneOffset() * 60 * 1000); + planner.vertical_bar.html(''+date(egw.preference('timeformat','calendar') == 12 ? 'h:ia' : 'H:i',formatDate)+''); + } + else + { + planner.vertical_bar.text(''); } }); - }); // Customize and override some draggable settings this.div.on('dragcreate','.calendar_calEvent:not(.rowNoEdit)', function(event,ui) { @@ -327,14 +359,14 @@ var et2_calendar_planner = et2_valueWidget.extend([et2_IDetachedDOM, et2_IResize row.set_label(name); } },user); - labels.push({id: user, label: label}); + labels.push({id: user, label: label, data: {participants:user,owner:''}}); } else if (user < 0) // groups { egw.accountData(user,'account_fullname',true,function(result) { for(var id in result) { - this.push({id: id, label: result[id]}); + this.push({id: id, label: result[id], data: {participants:id,owner:''}}); } },labels); } @@ -345,7 +377,7 @@ var et2_calendar_planner = et2_valueWidget.extend([et2_IDetachedDOM, et2_IResize { if(accounts[j].value === user) { - labels.push({id: user, label: accounts[j].label}); + labels.push({id: user, label: accounts[j].label, data: {participants:user,owner:''}}); break; } } @@ -475,7 +507,11 @@ var et2_calendar_planner = et2_valueWidget.extend([et2_IDetachedDOM, et2_IResize draw_row: function(sort_key, label, events) { var key = sort_key.split('-'); - this._drawRow(sort_key, label, events, new Date(key[0],key[1],1),new Date(key[0],parseInt(key[1])+1,0)); + this._drawRow( + sort_key, label, events, + new Date(key[0]+"-"+sprintf("%02d",parseInt(key[1])+1)+"-01T00:00:00Z"), + new Date(key[0],parseInt(key[1])+1,0) + ); } }, // Group by category has one row for each [sub]category @@ -508,7 +544,7 @@ var et2_calendar_planner = et2_valueWidget.extend([et2_IDetachedDOM, et2_IResize } }, row_labels: function() { - return [{id:'',label: egw.lang('none')}]; + return [{id:'',label: egw.lang('none'), data: {}}]; }, group: function(labels, rows, event) { var label_index = false; @@ -523,7 +559,7 @@ var et2_calendar_planner = et2_valueWidget.extend([et2_IDetachedDOM, et2_IResize if(label_index === false) { label_index = labels.length; - labels.push({id: event.category, label: ''}); + labels.push({id: event.category, label: '', data: {cat_id:event.category}}); var im = this.getInstanceManager(); // Fake it to use the cache / call var categories = et2_selectbox.cat_options({ @@ -685,7 +721,13 @@ var et2_calendar_planner = et2_valueWidget.extend([et2_IDetachedDOM, et2_IResize // Draw the rows for(var key in labels) { - grouper.draw_row.call(this,labels[key].id, labels[key].label, events[key] || []); + var row = grouper.draw_row.call(this,labels[key].id, labels[key].label, events[key] || []); + + // Add extra data for clicking on row + for(var extra in labels[key].data) + { + row.getDOMNode().dataset[extra] = labels[key].data[extra]; + } } // Adjust header if there's a scrollbar @@ -720,6 +762,8 @@ var et2_calendar_planner = et2_valueWidget.extend([et2_IDetachedDOM, et2_IResize // Add actual events row._update_events(events); + + return row; }, @@ -1035,13 +1079,13 @@ var et2_calendar_planner = et2_valueWidget.extend([et2_IDetachedDOM, et2_IResize { if (typeof holidays[i]['birthyear'] !== 'undefined') { - day_class += ' calendar_calBirthday'; + day_class += ' calendar_calBirthday '; holiday_list.push(holidays[i]['name']); } else { - day_class += 'calendar_calHoliday'; + day_class += 'calendar_calHoliday '; holiday_list.push(holidays[i]['name']); } @@ -1054,11 +1098,11 @@ var et2_calendar_planner = et2_valueWidget.extend([et2_IDetachedDOM, et2_IResize sprintf("%02d",today.getUTCDate()) ) { - day_class += "calendar_calToday"; + day_class += "calendar_calToday "; } if(date.getUTCDay() == 0 || date.getUTCDay() == 6) { - day_class += "calendar_weekend"; + day_class += "calendar_weekend "; } return day_class; }, @@ -1531,7 +1575,7 @@ var et2_calendar_planner = et2_valueWidget.extend([et2_IDetachedDOM, et2_IResize } var old_date = this.options.start_date; - this.options.start_date = this.date_helper.getValue(); + this.options.start_date = new Date(this.date_helper.getValue()); if(old_date !== this.options.start_date && this.isAttached()) { @@ -1567,7 +1611,7 @@ var et2_calendar_planner = et2_valueWidget.extend([et2_IDetachedDOM, et2_IResize this.date_helper.set_minutes(59); this.date_helper.date.setSeconds(59); var old_date = this.options.end_date; - this.options.end_date = this.date_helper.getValue(); + this.options.end_date = new Date(this.date_helper.getValue()); if(old_date !== this.options.end_date && this.isAttached()) { @@ -1711,6 +1755,26 @@ var et2_calendar_planner = et2_valueWidget.extend([et2_IDetachedDOM, et2_IResize return false; } + else if (!event.id) + { + // Clicked in row, but not on an event + // Default handler to open a new event at the selected time + if(this.options.group_by == 'month') + { + var date = this._get_time_from_position(_ev.clientX, _ev.clientY); + } + else + { + var date = this._get_time_from_position(_ev.offsetX, _ev.offsetY); + } + var data = $j(_ev.target).closest('.calendar_plannerRowWidget')[0].dataset || {}; + this.egw().open(null, 'calendar', 'add', jQuery.extend({ + start: date.toJSON(), + hour: date.getUTCHours(), + minute: date.getUTCMinutes() + },data) , '_blank'); + return false; + } return result; } else if (!jQuery.isEmptyObject(_ev.target.dataset)) @@ -1787,9 +1851,38 @@ var et2_calendar_planner = et2_valueWidget.extend([et2_IDetachedDOM, et2_IResize y = Math.round(y); var rel_x = Math.min(x / $j('.calendar_eventRows',this.div).width(),1); + var rel_time = 0; - var rel_time = (new Date(this.options.end_date) - new Date(this.options.start_date))*rel_x/1000; - this.date_helper.set_value(this.options.start_date); + // Simple math, the x is offset from start date + if(this.options.group_by !== 'month') + { + rel_time = (new Date(this.options.end_date) - new Date(this.options.start_date))*rel_x/1000; + this.date_helper.set_value(this.options.start_date.toJSON()); + } + else + { + // Find the correct row so we know which month, then get the offset + var row = $j(document.elementFromPoint(x, y)).closest('.calendar_plannerRowWidget'); + var row_widget = null; + for(var i = 0; i < this._children.length && row.length > 0; i++) + { + if(this._children[i].div[0] == row[0]) + { + row_widget = this._children[i]; + break; + } + } + if(row_widget) + { + rel_x = Math.min((x-row_widget.rows.offset().left)/row_widget.rows.width(),1); + rel_time = (new Date(row_widget.options.end_date) - new Date(row_widget.options.start_date))*rel_x/1000; + this.date_helper.set_value(row_widget.options.start_date.toJSON()); + } + else + { + return false; + } + } var interval = egw.preference('interval','calendar') || 30; this.date_helper.set_minutes(Math.round(rel_time / (60 * interval))*interval); diff --git a/calendar/js/et2_widget_planner_row.js b/calendar/js/et2_widget_planner_row.js index bba1318f80..e73841f5b2 100644 --- a/calendar/js/et2_widget_planner_row.js +++ b/calendar/js/et2_widget_planner_row.js @@ -58,6 +58,10 @@ var et2_calendar_planner_row = et2_valueWidget.extend([et2_IDetachedDOM], // Used for its date calculations this.date_helper = et2_createWidget('date-time',{},null); this.date_helper.loadingFinished(); + + this.set_start_date(this.options.start_date); + this.set_end_date(this.options.end_date); + }, doLoadingFinished: function() { @@ -137,6 +141,43 @@ var et2_calendar_planner_row = et2_valueWidget.extend([et2_IDetachedDOM], } }, + /** + * Change the start date + * + * @param {Date} new_date New end date + * @returns {undefined} + */ + set_start_date: function(new_date) + { + if(!new_date || new_date === null) + { + throw exception('Invalid end date. ' + new_date.toString()); + } + + this.options.start_date = new Date(typeof new_date == 'string' ? new_date : new_date.toJSON()); + this.options.start_date.setUTCHours(0); + this.options.start_date.setUTCMinutes(0); + this.options.start_date.setUTCSeconds(0); + }, + /** + * Change the end date + * + * @param {string|number|Date} new_date New end date + * @returns {undefined} + */ + set_end_date: function(new_date) + { + if(!new_date || new_date === null) + { + throw exception('Invalid end date. ' + new_date.toString()); + } + + this.options.end_date = new Date(typeof new_date == 'string' ? new_date : new_date.toJSON()); + this.options.end_date.setUTCHours(23); + this.options.end_date.setUTCMinutes(59); + this.options.end_date.setUTCSeconds(59); + }, + /** * Mark special days (birthdays, holidays) on the planner * @@ -357,7 +398,6 @@ var et2_calendar_planner_row = et2_valueWidget.extend([et2_IDetachedDOM], // Basic scaling, doesn't consider working times pos = (t - start) / (end - start); - // Month view if(this._parent.options.group_by !== 'month') { @@ -389,6 +429,11 @@ var et2_calendar_planner_row = et2_valueWidget.extend([et2_IDetachedDOM], } */ } + else + { + // 2678400 is the number of seconds in 31 days + //pos = (t - start) / 2678400000; + } pos = 100 * pos; return pos; diff --git a/calendar/templates/default/app.css b/calendar/templates/default/app.css index 7953a8210d..5b5bd804a2 100644 --- a/calendar/templates/default/app.css +++ b/calendar/templates/default/app.css @@ -687,8 +687,11 @@ e.g. the div with class calendar_calTimeGrid is generated by the timeGridWidget min-height: 20px; height: 100%; } +.calendar_eventRows { + margin-left: 2px; +} .calendar_plannerWidget .verticalBar { - width: 1px; + width: 8ex; height: 100%; position: absolute; top: 0px; @@ -696,6 +699,13 @@ e.g. the div with class calendar_calTimeGrid is generated by the timeGridWidget pointer-events: none; z-index: 40; } +.calendar_plannerWidget .verticalBar > span { + position: absolute; + bottom: 0px; + right: 0px; + background-color: rgba(255,255,255,.8); +} + /** * Filler for month with less then 31 days in yearly planner