diff --git a/calendar/inc/class.calendar_timegrid_etemplate_widget.inc.php b/calendar/inc/class.calendar_timegrid_etemplate_widget.inc.php
new file mode 100644
index 0000000000..0da14ab73b
--- /dev/null
+++ b/calendar/inc/class.calendar_timegrid_etemplate_widget.inc.php
@@ -0,0 +1,31 @@
+read_holidays((int)$year);
+ egw_json_response::get()->data($holidays);
+ }
+ }
\ No newline at end of file
diff --git a/calendar/inc/class.calendar_ui.inc.php b/calendar/inc/class.calendar_ui.inc.php
index 2e261d8c7f..ae112de030 100644
--- a/calendar/inc/class.calendar_ui.inc.php
+++ b/calendar/inc/class.calendar_ui.inc.php
@@ -599,7 +599,7 @@ class calendar_ui
foreach(array(
'add' => array('icon'=>'new','text'=>'add'),
'day' => array('icon'=>'today','text'=>'Today','menuaction' => 'calendar.calendar_uiviews.day','date' => $this->bo->date2string($this->bo->now_su)),
- 'week' => array('icon'=>'week','text'=>'Weekview','menuaction' => 'calendar.calendar_uiviews.week'),
+ 'week' => array('icon'=>'week','text'=>'Weekview','menuaction' => 'calendar.calendar_uiviews.week','ajax'=>'true'),
'weekN' => array('icon'=>'multiweek','text'=>'Multiple week view','menuaction' => 'calendar.calendar_uiviews.weekN'),
'month' => array('icon'=>'month','text'=>'Monthview','menuaction' => 'calendar.calendar_uiviews.month'),
//'year' => array('icon'=>'year','text'=>'yearview','menuaction' => 'calendar.calendar_uiviews.year'),
diff --git a/calendar/inc/class.calendar_uiviews.inc.php b/calendar/inc/class.calendar_uiviews.inc.php
index 2d980ce731..3b558329e3 100644
--- a/calendar/inc/class.calendar_uiviews.inc.php
+++ b/calendar/inc/class.calendar_uiviews.inc.php
@@ -892,9 +892,9 @@ class calendar_uiviews extends calendar_ui
if (count($users) > $headerCounter)
{
$content .= $navHeader;
- }
- $content .= '';
}
+ $content .= '';
+ }
$content = '
')
+ .attr('data-date',linkData.date)
+ .attr('data-hour',linkData.hour)
+ .attr('data-minute',linkData.minute)
+ .appendTo(this.div);
+ if(this.options.owner)
+ {
+ hour.attr('data-owner', this.options.owner);
+ }
+ }
+ },
+
+ /**
+ * Set the date
+ *
+ * @param {string|Date} _date New date
+ * @param {Object[]} events=false List of events to be displayed, or false to
+ * automatically fetch data from content array
+ */
+ set_date: function(_date, events)
+ {
+ if(typeof events === 'undefined' || !events)
+ {
+ events = false;
+ }
+ if(!this._parent || !this._parent.date_helper)
+ {
+ egw.debug('warn', 'Day col widget "' + this.id + '" is missing its parent.');
+ return false;
+ }
+ if(typeof _date === "object")
+ {
+ this._parent.date_helper.set_value(_date);
+ }
+ else if(typeof _date === "string")
+ {
+ this._parent.date_helper.set_year(_date.substring(0,4));
+ this._parent.date_helper.set_month(_date.substring(4,6));
+ this._parent.date_helper.set_date(_date.substring(6,8));
+ }
+
+
+ // Add timezone offset back in, or formatDate will lose those hours
+ this.date = new Date(this._parent.date_helper.date.valueOf() + this._parent.date_helper.date.getTimezoneOffset() * 60 * 1000);
+
+ // Keep internal option in Ymd format, it gets passed around in this format
+ var new_date = date('Ymd',this.date);
+
+ // Set label
+ var date_string = this._parent._children.length === 1 ?
+ this.long_date(this.date,false, false, true) :
+ jQuery.datepicker.formatDate('DD dd',this.date);
+ this.title.text(date_string);
+
+ // Avoid redrawing if date is the same
+ if(new_date === this.options.date)
+ {
+ return;
+ }
+ else
+ {
+ this.options.date = new_date;
+ }
+
+ this.div.attr("data-date", this.options.date);
+
+ // Set holiday and today classes
+ this._day_class_holiday();
+
+ // Update all the little boxes
+ this._draw();
+
+ this._update_events(events);
+ },
+
+ /**
+ * Set the owner of this day
+ *
+ * @param {number} _owner Account ID
+ */
+ set_owner: function(_owner) {
+ this.options.owner = parseInt(_owner);
+ this.div.attr('data-sortable-id', this.options.owner);
+ },
+
+ /**
+ * Applies class for today, and any holidays for current day
+ */
+ _day_class_holiday: function() {
+ // Remove all classes
+ this.title.removeClass()
+ // Except this one...
+ .addClass("et2_clickable calendar_calDayColHeader");
+
+ // Set today class - note +1 when dealing with today, as months in JS are 0-11
+ var today = new Date();
+
+ this.title.toggleClass("calendar_calToday", this.options.date === ''+today.getUTCFullYear()+
+ sprintf("%02d",today.getUTCMonth()+1)+
+ sprintf("%02d",today.getUTCDate())
+ );
+
+ // Holidays and birthdays
+ var holidays = et2_calendar_daycol.get_holidays(this);
+ var holiday_list = [];
+ if(holidays && holidays[this.options.date])
+ {
+ holidays = holidays[this.options.date];
+ for(var i = 0; i < holidays.length; i++)
+ {
+ if (typeof holidays[i]['birthyear'] !== 'undefined')
+ {
+ this.title.addClass('calendar_calBirthday');
+
+ //If the birthdays are already displayed as event, don't
+ //show them in the caption
+ if (!this.options.display_birthday_as_event)
+ {
+ holiday_list.push(holidays[i]['name']);
+ }
+ }
+ else
+ {
+ this.title.addClass('calendar_calHoliday');
+ this.title.attr('data-holiday', holidays[i]['name']);
+
+ //If the birthdays are already displayed as event, don't
+ //show them in the caption
+ if (!this.options.display_holiday_as_event)
+ {
+ holiday_list.push(holidays[i]['name']);
+ }
+ }
+ }
+ }
+ this.title.attr('title', holiday_list.join(','));
+ },
+
+ /**
+ * Load the event data for this day and create event widgets for each.
+ *
+ * If event information is not provided, it will be pulled from the content array.
+ *
+ * @param {Object[]} [_events] Array of event information, one per event.
+ */
+ _update_events: function(_events)
+ {
+ // Remove all events
+ while(this._children.length)
+ {
+ this._children[this._children.length-1].free();
+ }
+ var events = _events || this.getArrayMgr('content').getEntry(this.options.date) || [];
+
+ // Sort events into minimally-overlapping columns
+ var columns = this._event_columns(events);
+
+ for(var c = 0; c < columns.length; c++)
+ {
+ // Calculate horizontal positioning
+ var left = Math.ceil(5 + (1.5 * 100 / (this.options.width || 100)));
+ var width = 98 - left;
+ if (columns.length !== 1)
+ {
+ width = !c ? 70 : 50;
+ left += c * (100.0-left) / columns.length;
+ }
+ if (left + width > 100.0) width = 98.0 - left;
+
+ var whole_day_counter = 0;
+
+ for(var i = 0; i < columns[c].length; i++)
+ {
+ // Calculate vertical positioning
+ var top = 0;
+ var height = 0;
+ if(columns[c][i].whole_day_on_top)
+ {
+ top = ((this.title.height()/this.div.height())*100) + this.display_settings.rowHeight*whole_day_counter++;
+ height = this.display_settings.rowHeight;
+ }
+ else
+ {
+ top = this._time_to_position(columns[c][i].start_m,whole_day_counter);
+ height = this._time_to_position(columns[c][i].end_m,whole_day_counter)-top;
+ }
+
+ // Create event
+ var event = et2_createWidget('calendar-event',{},this);
+ if(this.isInTree())
+ {
+ event.doLoadingFinished();
+ }
+ event.set_value(columns[c][i]);
+
+ // Position the event
+ event.div.css('top', top+'%');
+ event.div.css('height', height+'%');
+ event.div.css('left', left.toFixed(1)+'%');
+ event.div.css('width', width.toFixed(1)+'%');
+ }
+ }
+
+ },
+
+ /**
+ * Sort a day's events into minimally overlapping columns
+ *
+ * @param {Object[]} events
+ * @returns {Array[]} Events sorted into columns
+ */
+ _event_columns: function(events)
+ {
+ var day_start = this.date.valueOf() / 1000;
+ var dst_check = new Date(this.date);
+ dst_check.setHours(12);
+
+ // if daylight saving is switched on or off, correct $day_start
+ // gives correct times after 2am, times between 0am and 2am are wrong
+ var daylight_diff = day_start + 12*60*60 - (dst_check.valueOf()/1000);
+ if(daylight_diff)
+ {
+ day_start -= daylight_diff;
+ }
+
+ var eventCols = [], col_ends = [];
+ for(var i = 0; i < events.length; i++)
+ {
+ var event = events[i];
+ var c = 0;
+ event['multiday'] = false;
+ event['start_m'] = (event['start'] - day_start) / 60;
+ if (event['start_m'] < 0)
+ {
+ event['start_m'] = 0;
+ event['multiday'] = true;
+ }
+ event['end_m'] = (event['end'] - day_start) / 60;
+ if (event['end_m'] >= 24*60)
+ {
+ event['end_m'] = 24*60-1;
+ event['multiday'] = true;
+ }
+ if (!event['whole_day_on_top'])
+ {
+ for(c = 0; event['start_m'] < col_ends[c]; ++c);
+ col_ends[c] = event['end_m'];
+ }
+ if(typeof eventCols[c] == 'undefined')
+ {
+ eventCols[c] = [];
+ }
+ eventCols[c].push(event);
+ }
+ return eventCols;
+ },
+
+ /**
+ * Calculates the vertical position based on the time
+ *
+ * workday start- and end-time, is taken into account, as well as timeGrids px_m - minutes per pixel param
+ *
+ * @param {int} time in minutes from midnight
+ * @param {int} [row_offset=0] Add extra spacing for additional rows
+ * @return {float} position in percent
+ */
+ _time_to_position: function(time,row_offset)
+ {
+ var pos = 0.0;
+ if(typeof row_offset === 'undefined')
+ {
+ row_offset = 0;
+ }
+
+ // time before workday => condensed in the first $this->extraRows rows
+ if (this.display_settings.wd_start > 0 && time < this.display_settings.wd_start)
+ {
+ pos = ((this.title.height()/this.div.height())*100) + (row_offset + (time / this.display_settings.wd_start )) * this.display_settings.rowHeight;
+ }
+ // time after workday => condensed in the last row
+ else if (this.display_settings.wd_end < 24*60 && time > this.display_settings.wd_end+1*this.display_settings.granularity)
+ {
+ pos = 100 - (row_offset * this.display_settings.rowHeight * (1 - (time - this.display_settings.wd_end) / (24*60 - this.display_settings.wd_end)));
+ }
+ // time during the workday => 2. row on (= + granularity)
+ else
+ {
+ pos = ((this.title.height()/this.div.height())*100) + this.display_settings.rowHeight * (1+this.display_settings.extraRows+(time-this.display_settings.wd_start)/this.display_settings.granularity);
+ }
+ pos = pos.toFixed(1)
+
+ return pos;
+ },
+
+ /**
+ * Formats one or two dates (range) as long date (full monthname), optionaly with a time
+ *
+ * Take care of any timezone issues before you pass the dates in.
+ *
+ * @param {Date} first first date
+ * @param {Date} last=0 last date for range, or false for a single date
+ * @param {boolean} display_time=false should a time be displayed too
+ * @param {boolean} display_day=false should a day-name prefix the date, eg. monday June 20, 2006
+ * @return string with formatted date
+ */
+ long_date: function(first, last, display_time, display_day)
+ {
+ if(!last || typeof last !== 'object')
+ {
+ last = false;
+ }
+
+ if(!display_time) display_time = false;
+ if(!display_day) display_day = false;
+
+ var range = '';
+
+ var datefmt = egw.preference('dateformat');
+ var timefmt = egw.preference('timeformat') == 12 ? 'h:i a' : 'H:i';
+
+ var month_before_day = datefmt[0].toLowerCase() == 'm' ||
+ datefmt[2].toLowerCase() == 'm' && datefmt[4] == 'd';
+
+ if (display_day)
+ {
+ range = jQuery.datepicker.formatDate('DD',first)+(datefmt[0] != 'd' ? ' ' : ', ');
+ }
+ for (var i = 0; i < 5; i += 2)
+ {
+ switch(datefmt[i])
+ {
+ case 'd':
+ range += first.getUTCDate()+ (datefmt[1] == '.' ? '.' : '');
+ if (last && (first.getUTCMonth() != last.getUTCMonth() || first.getFullYear() != last.getFullYear()))
+ {
+ if (!month_before_day)
+ {
+ range += jQuery.datepicker.formatDate('MM',first);
+ }
+ if (first.getFullYear() != last.getFullYear() && datefmt[0] != 'Y')
+ {
+ range += (datefmt[0] != 'd' ? ', ' : ' ') . first.getFullYear();
+ }
+ if (display_time)
+ {
+ range += ' '+jQuery.datepicker.formatDate(dateTimeFormat(timefmt),first);
+ }
+ if (!last)
+ {
+ return range;
+ }
+ range += ' - ';
+
+ if (first.getFullYear() != last.getFullYear() && datefmt[0] == 'Y')
+ {
+ range += last.getFullYear() + ', ';
+ }
+
+ if (month_before_day)
+ {
+ range += jQuery.datepicker.formatDate('MM',last);
+ }
+ }
+ else
+ {
+ if (display_time)
+ {
+ range += ' '+jQuery.datepicker.formatDate(dateTimeFormat(timefmt),last);
+ }
+ if(last)
+ {
+ range += ' - ';
+ }
+ }
+ if(last)
+ {
+ range += ' ' + last.getUTCDate() + (datefmt[1] == '.' ? '.' : '');
+ }
+ break;
+ case 'm':
+ case 'M':
+ range += ' '+jQuery.datepicker.formatDate('MM',month_before_day ? first : last) + ' ';
+ break;
+ case 'Y':
+ if (datefmt[0] != 'm')
+ {
+ range += ' ' + (datefmt[0] == 'Y' ? first.getFullYear()+(datefmt[2] == 'd' ? ', ' : ' ') : last.getFullYear()+' ');
+ }
+ break;
+ }
+ }
+ if (display_time && last)
+ {
+ range += ' '+jQuery.datepicker.formatDate(dateTimeFormat(timefmt),last);
+ }
+ if (datefmt[4] == 'Y' && datefmt[0] == 'm')
+ {
+ range += ', ' + last.getFullYear();
+ }
+ return range;
+ },
+
+ attachToDOM: function()
+ {
+ this._super.apply(this, arguments);
+
+ // Remove the binding for the click handler, unless there's something
+ // custom here.
+ if (!this.onclick)
+ {
+ $j(this.node).off("click");
+ }
+ // But we do want to listen to certain clicks, and handle them internally
+ $j(this.node).on('click.et2_daycol',
+ '.calendar_calDayColHeader,.calendar_calAddEvent',
+ jQuery.proxy(this.click, this)
+ );
+ },
+
+ /**
+ * Click handler calling custom handler set via onclick attribute to this.onclick,
+ * or the default which is to open a new event at that time.
+ *
+ * Normally, you don't bind to this one, but the attribute is supported if you
+ * can get a reference to the widget.
+ *
+ * @param {Event} _ev
+ * @returns {boolean}
+ */
+ click: function(_ev) {
+
+ if($j(_ev.target).hasClass('calendar_calDayColHeader'))
+ {
+ this._parent.set_start_date(this.date);
+ this._parent.set_end_date(this.date);
+ return false;
+ }
+ else if ($j(_ev.target).hasClass('calendar_calAddEvent'))
+ {
+ // Default handler to open a new event at the selected time
+ this.egw().open(null, 'calendar', 'add', {
+ date: _ev.target.dataset.date || this.options.date,
+ hour: _ev.target.dataset.hour || this._parent.options.day_start,
+ minute: _ev.target.dataset.minute || 0
+ } , '_blank');
+ return false;
+ }
+ },
+
+ /**
+ * Code for implementing et2_IDetachedDOM
+ *
+ * @param {array} _attrs array to add further attributes to
+ */
+ getDetachedAttributes: function(_attrs) {
+
+ },
+
+ getDetachedNodes: function() {
+ return [this.getDOMNode()];
+ },
+
+ setDetachedAttributes: function(_nodes, _values) {
+
+ },
+
+});
+
+et2_register_widget(et2_calendar_daycol, ["calendar-daycol"]);
+
+// Static class stuff
+jQuery.extend(et2_calendar_daycol,
+{
+ holiday_cache: {},
+ /**
+ * Fetch and cache a list of the year's holidays
+ *
+ * @param {et2_calendar_timegrid} widget
+ * @returns {Array}
+ */
+ get_holidays: function(widget)
+ {
+ // Loaded in an iframe or something
+ if(!egw.window.et2_calendar_daycol) return {};
+
+ var cache_id = widget.options.date.substring(0,4);
+ var cache = egw.window.et2_calendar_daycol.holiday_cache[cache_id];
+ if (typeof cache == 'undefined')
+ {
+ // Fetch with json instead of jsonq because there may be more than
+ // one widget listening for the response by the time it gets back,
+ // and we can't do that when it's queued.
+ egw.window.et2_calendar_daycol.holiday_cache[cache_id] = egw.json(
+ 'calendar_timegrid_etemplate_widget::ajax_get_holidays',
+ [cache_id]
+ ).sendRequest();
+ }
+ cache = egw.window.et2_calendar_daycol.holiday_cache[cache_id];
+ if(typeof cache.done == 'function')
+ {
+ // pending, wait for it
+ cache.done(jQuery.proxy(function(response) {
+ egw.window.et2_calendar_daycol.holiday_cache[this.cache_id] = response.response[0].data||undefined;
+
+ egw.window.setTimeout(jQuery.proxy(function() {
+ this.widget._day_class_holiday();
+ },this),1);
+ },{widget:widget,cache_id:cache_id}));
+ return {};
+ }
+ else
+ {
+ return cache;
+ }
+ }
+});
\ No newline at end of file
diff --git a/calendar/js/et2_widget_event.js b/calendar/js/et2_widget_event.js
new file mode 100644
index 0000000000..67ec308ba0
--- /dev/null
+++ b/calendar/js/et2_widget_event.js
@@ -0,0 +1,284 @@
+/*
+ * Egroupware Calendar event widget
+ * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
+ * @package etemplate
+ * @subpackage api
+ * @link http://www.egroupware.org
+ * @author Nathan Gray
+ * @version $Id$
+ */
+
+
+"use strict";
+
+/*egw:uses
+ /etemplate/js/et2_core_valueWidget;
+*/
+
+/**
+ * Class for a single event, displayed in a timegrid
+ *
+ *
+ * @augments et2_valueWidget
+ */
+var et2_calendar_event = et2_valueWidget.extend([et2_IDetachedDOM],
+{
+
+ attributes: {
+ "onclick": {
+ "description": "JS code which is executed when the element is clicked. " +
+ "If no handler is provided, or the handler returns true and the event is not read-only, the " +
+ "event will be opened according to calendar settings."
+ }
+ },
+
+ /**
+ * Constructor
+ *
+ * @memberOf et2_calendar_daycol
+ */
+ init: function() {
+ this._super.apply(this, arguments);
+
+ // Main container
+ this.div = $j(document.createElement("div"))
+ .addClass("calendar_calEvent")
+ .css('width',this.options.width);
+ this.title = $j(document.createElement('div'))
+ .addClass("calendar_calEventHeader")
+ .appendTo(this.div);
+ this.body = $j(document.createElement('div'))
+ .addClass("calendar_calEventBody")
+ .appendTo(this.div);
+
+ this.setDOMNode(this.div[0]);
+ },
+
+ doLoadingFinished: function() {
+ this._super.apply(this, arguments);
+
+ // Parent will have everything we need, just load it from there
+ if(this.title.text() == '' && this.options.date &&
+ this._parent && this._parent.instanceOf(et2_calendar_timegrid))
+ {
+ // Forces an update
+ var date = this.options.date;
+ this.options.date = '';
+ this.set_date(date);
+ }
+ },
+
+ destroy: function() {
+ this._super.apply(this, arguments);
+ },
+
+ set_value: function(_value) {
+ this.options.value = _value;
+ this._update(this.options.value);
+ },
+
+ _update: function(event) {
+
+ var eventId = event.id.match(/-?\d+\.?\d*/g)[0];
+ var appName = event.id.replace(/-?\d+\.?\d*/g,'');
+ var app_id = event.app_id ? event.app_id : event.id + (event.recur_type ? ':'+event.recur_date : '')
+
+ this.div
+ .attr('data-draggable-id',event['id']+'_O'+event.owner+'_C'+(event.owner<0?'group'+Math.abs(event.owner):event.owner))
+
+ // Put everything we need for basic interaction here, so it's available immediately
+ .attr('data-id', eventId || event.id)
+ .attr('data-app', appName || 'calendar')
+ .attr('data-app_id', app_id)
+ .attr('data-start', event.start)
+ .attr('data-recur_type', event.recur_type)
+
+ .toggleClass('calendar_calEventPrivate', event.private)
+ // Remove any category classes
+ .removeClass(function(index, css) {
+ return (css.match (/(^|\s)cat_\S+/g) || []).join(' ');
+ });
+ if(event.category)
+ {
+ this.div.addClass('cat_' + event.category);
+ }
+ this.div.css('border-color', this.div.css('background-color'));
+
+ this.div.toggleClass('calendar_calEventUnknown', event.participants[egw.user('account_id')][0] == 'U');
+
+ this.title.toggle(!event.whole_day_on_top);
+ this.body.toggleClass('calendar_calEventBodySmall', event.whole_day_on_top || false);
+
+ // Header
+ var title = !event.is_private ? event['title'] : egw.lang('private');
+ var small_height = event['end_m']-event['start_m'] < 2*this._parent.display_settings.granularity ||
+ event['end_m'] <= this._parent.display_settings.wd_start || event['start_m'] >= this._parent.display_settings.wd_end;
+
+ this.title.text(small_height ? title : this._get_timespan(event))
+ // Set title color based on background brightness
+ .css('color', jQuery.Color(this.div.css('background-color')).lightness() > 0.5 ? 'black':'white');
+
+ // Body
+ if(event.whole_day_on_top)
+ {
+ this.body.html(title);
+ }
+ else
+ {
+ this.body.html('
'+title+'')
+ }
+ this.body
+ // Set background color to a lighter version of the header color
+ .css('background-color',jQuery.Color(this.div.css('background-color')).lightness("+=0.3"));
+
+ },
+
+ /**
+ * Get a text representation of the timespan of the event. Either start
+ * - end, or 'all day'
+ *
+ * @param {Object} event Event to get the timespan for
+ * @param {number} event.start_m Event start, in minutes from midnight
+ * @param {number} event.end_m Event end, in minutes from midnight
+ *
+ * @return {string} Timespan
+ */
+ _get_timespan: function(event) {
+ var timespan = '';
+ if (event['start_m'] === 0 && event['end_m'] >= 24*60-1)
+ {
+ if (event['end_m'] > 24*60)
+ {
+ timespan = jQuery.datepicker.formatTime(
+ egw.preference("timeformat") === 12 ? "h:mmtt" : "HH:mm",
+ {
+ hour: event.start_m / 60,
+ minute: event.start_m % 60,
+ seconds: 0,
+ timezone: 0
+ },
+ {"ampm": (egw.preference("timeformat") === "12")}
+ ).trim()+' - '+jQuery.datepicker.formatTime(
+ egw.preference("timeformat") === 12 ? "h:mmtt" : "HH:mm",
+ {
+ hour: event.end_m / 60,
+ minute: event.end_m % 60,
+ seconds: 0,
+ timezone: 0
+ },
+ {"ampm": (egw.preference("timeformat") === "12")}
+ ).trim();
+ }
+ else
+ {
+ timespan = egw.lang('all day');
+ }
+ }
+ else
+ {
+ var duration = event.end_m - event.start_m;
+ if (event.end_m === 24*60-1) ++duration;
+ duration = Math.floor(duration/60) + this.egw().lang('h')+(duration%60 ? duration%60 : '');
+
+ timespan = jQuery.datepicker.formatTime(
+ egw.preference("timeformat") === 12 ? "h:mmtt" : "HH:mm",
+ {
+ hour: event.start_m / 60,
+ minute: event.start_m % 60,
+ seconds: 0,
+ timezone: 0
+ },
+ {"ampm": (egw.preference("timeformat") === "12")}
+ ).trim();
+
+ timespan += ' ' + duration;
+ }
+ return timespan;
+ },
+
+ attachToDOM: function()
+ {
+ this._super.apply(this, arguments);
+
+ // Remove the binding for the click handler, unless there's something
+ // custom here.
+ if (!this.onclick)
+ {
+ $j(this.node).off("click");
+ }
+ },
+
+ /**
+ * Click handler calling custom handler set via onclick attribute to this.onclick.
+ * All other handling is done by the timegrid widget.
+ *
+ * @param {Event} _ev
+ * @returns {boolean}
+ */
+ click: function(_ev) {
+ var result = true;
+ if(typeof this.onclick == 'function')
+ {
+ // Make sure function gets a reference to the widget, splice it in as 2. argument if not
+ var args = Array.prototype.slice.call(arguments);
+ if(args.indexOf(this) == -1) args.splice(1, 0, this);
+
+ result = this.onclick.apply(this, args);
+ }
+ return result;
+ },
+
+ _edit: function()
+ {
+ if(this.options.value.recur_type)
+ {
+ var edit_id = this.options.value.id;
+ var edit_date = this.options.value.start;
+ var that = this;
+ var buttons = [
+ {text: this.egw().lang("Edit exception"), id: "exception", class: "ui-priority-primary", "default": true},
+ {text: this.egw().lang("Edit series"), id:"series"},
+ {text: this.egw().lang("Cancel"), id:"cancel"}
+ ];
+ et2_dialog.show_dialog(function(_button_id)
+ {
+ switch(_button_id)
+ {
+ case 'exception':
+ that.egw().open(edit_id, 'calendar', 'edit', {date:edit_date,exception: '1'});
+ break;
+ case 'series':
+ that.egw().open(edit_id, 'calendar', 'edit', {date:edit_date});
+ break;
+ case 'cancel':
+
+ default:
+ break;
+ }
+ },this.egw().lang("Do you want to edit this event as an exception or the whole series?"),
+ this.egw().lang("This event is part of a series"), {}, buttons, et2_dialog.WARNING_MESSAGE);
+ }
+ else
+ {
+ this.egw().open(this.options.value.id, 'calendar','edit');
+ }
+ },
+
+ /**
+ * Code for implementing et2_IDetachedDOM
+ *
+ * @param {array} _attrs array to add further attributes to
+ */
+ getDetachedAttributes: function(_attrs) {
+
+ },
+
+ getDetachedNodes: function() {
+ return [this.getDOMNode()];
+ },
+
+ setDetachedAttributes: function(_nodes, _values) {
+
+ },
+});
+et2_register_widget(et2_calendar_event, ["calendar-event"]);
\ No newline at end of file
diff --git a/calendar/js/et2_widget_timegrid.js b/calendar/js/et2_widget_timegrid.js
new file mode 100644
index 0000000000..aeec449d52
--- /dev/null
+++ b/calendar/js/et2_widget_timegrid.js
@@ -0,0 +1,683 @@
+/*
+ * Egroupware Calendar timegrid
+ * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
+ * @package etemplate
+ * @subpackage api
+ * @link http://www.egroupware.org
+ * @author Nathan Gray
+ * @version $Id$
+ */
+
+
+"use strict";
+
+/*egw:uses
+ /etemplate/js/et2_core_valueWidget;
+ /calendar/js/et2_widget_daycol.js;
+ /calendar/js/et2_widget_event.js;
+*/
+
+/**
+ * Class which implements the "calendar-timegrid" XET-Tag for displaying a span of days
+ *
+ * This widget is responsible for the times on the side
+ *
+ * @augments et2_DOMWidget
+ */
+var et2_calendar_timegrid = et2_valueWidget.extend([et2_IDetachedDOM, et2_IResizeable],
+{
+ createNamespace: true,
+
+ attributes: {
+ start_date: {
+ name: "Start date",
+ type: "any"
+ },
+ end_date: {
+ name: "End date",
+ type: "any"
+ },
+ value: {
+ type: "any",
+ description: "An array of events, indexed by date (Ymd format)."
+ },
+ day_start: {
+ name: "Day start time",
+ type: "string",
+ default: parseInt(egw.preference('workdaystarts','calendar')) || 9,
+ description: "Work day start time. If unset, this will default to the current user's preference"
+ },
+ day_end: {
+ name: "Day end time",
+ type: "string",
+ default: parseInt(egw.preference('workdayends','calendar')) || 17,
+ description: "Work day end time. If unset, this will default to the current user's preference"
+ },
+ show_weekend: {
+ name: "Weekends",
+ type: "boolean",
+ default: egw.preference('days_in_weekview','calendar') != 5,
+ description: "Display weekends. The date range should still include them for proper scrolling, but they just won't be shown."
+ },
+ granularity: {
+ name: "Granularity",
+ type: "integer",
+ default: parseInt(egw.preference('interval','calendar')) || 30,
+ description: "How many minutes per row"
+ },
+ extra_rows: {
+ name: "Extra rows",
+ type: "integer",
+ default: 2,
+ description: "Extra rows above and below the workday"
+ },
+ owner: {
+ name: "Owner",
+ type: "any", // Integer, or array of integers
+ default: 0,
+ description: "Account ID number of the calendar owner, if not the current user"
+ },
+
+ height: {
+ "default": '100%'
+ }
+ },
+ /**
+ * Constructor
+ *
+ * @memberOf et2_calendar_timegrid
+ */
+ init: function() {
+ this._super.apply(this, arguments);
+
+ // Main container
+ this.div = $j(document.createElement("div"))
+ .addClass("calendar_calTimeGrid");
+
+ // Contains times / rows
+ this.gridHeader = $j(document.createElement("div"))
+ .addClass("calendar_calGridHeader")
+ .appendTo(this.div);
+
+ // Contains days / columns
+ this.days = $j(document.createElement("div"))
+ .addClass("calendar_calDayCols")
+ .appendTo(this.div);
+
+ // Used for its date calculations
+ this.date_helper = et2_createWidget('date',{},null);
+ this.date_helper.loadingFinished();
+
+ // Used for owners
+ this.owner = et2_createWidget('select-account_ro',{},this);
+
+ // List of dates in Ymd
+ // The first one should be start_date, last should be end_date
+ this.day_list = [];
+ this.day_widgets = [];
+
+ // Update timer, to avoid redrawing twice when changing start & end date
+ this.update_timer = null;
+
+ this.setDOMNode(this.div[0]);
+ },
+ destroy: function() {
+ this._super.apply(this, arguments);
+ this.div.off();
+
+ // date_helper has no parent, so we must explicitly remove it
+ this.date_helper.destroy();
+ this.date_helper = null;
+ },
+
+ doLoadingFinished: function() {
+ this._super.apply(this, arguments);
+ this._drawGrid();
+
+ // Bind scroll event
+ // When the user scrolls, we'll move enddate - startdate days
+ this.div.on('wheel',jQuery.proxy(function(e) {
+ var direction = e.originalEvent.deltaY > 0 ? 1 : -1;
+
+ this.date_helper.set_value(this.options.end_date);
+ var end = this.date_helper.get_time();
+
+ this.date_helper.set_value(this.options.start_date);
+ var start = this.date_helper.get_time();
+
+ var delta = 1000 * 60 * 60 * 24 + (end - start);// / (1000 * 60 * 60 * 24));
+
+ // TODO - actually fetch new data
+ this.set_start_date(new Date(start + (delta * direction )));
+ this.set_end_date(new Date(end + (delta * direction)));
+
+ e.preventDefault();
+ return false;
+ },this))
+ // Bind context event to create actionobjects as needed
+ // TODO: Do it like this, or the normal way?
+ .on('contextmenu', jQuery.proxy(function(e) {
+ if(this.days.has(e.target).length)
+ {
+ var event = this._get_event_info(e.originalEvent.target);
+ this._link_event(event);
+ }
+ },this));
+
+ return true;
+ },
+
+ /**
+ * Something changed, and the days need to be re-drawn. We wait a bit to
+ * avoid re-drawing twice if start and end date both changed, then recreate
+ * the days.
+ * The whole grid is not regenerated because times aren't expected to change,
+ * just the days.
+ *
+ * @returns {undefined}
+ */
+ invalidate: function() {
+
+ // Reset the list of days
+ this.day_list = [];
+
+ // Wait a bit to see if anything else changes, then re-draw the days
+ if(this.update_timer === null)
+ {
+ this.update_timer = window.setTimeout(jQuery.proxy(function() {
+ this.update_timer = null;
+ this._drawDays();
+ },this),ET2_GRID_INVALIDATE_TIMEOUT);
+ }
+ },
+
+ getDOMNode: function(_sender) {
+ if(_sender === this || !_sender)
+ {
+ return this.div[0];
+ }
+ else if (_sender.instanceOf(et2_calendar_daycol))
+ {
+ return this.days[0];
+ }
+ else if (_sender)
+ {
+ return this.gridHeader[0];
+ }
+ },
+
+ _drawGrid: function() {
+
+ this.div.css('height', this.options.height)
+ .empty();
+
+ // Draw in the horizontal - the times
+ this._drawTimes();
+
+ // Draw in the vertical - the days
+ this.div.append(this.days);
+ this._drawDays();
+ },
+
+ /**
+ * Creates the DOM nodes for the times in the left column, and the horizontal
+ * lines (mostly via CSS) that span the whole time span.
+ */
+ _drawTimes: function() {
+ var wd_start = 60*this.options.day_start;
+ var wd_end = 60*this.options.day_end;
+ var granularity = this.options.granularity;
+ var totalDisplayMinutes = wd_end - wd_start;
+ var rowsToDisplay = (totalDisplayMinutes/granularity)+2+2*this.options.extra_rows;
+ var rowHeight = (100/rowsToDisplay).toFixed(1);
+
+ // ensure a minimum height of each row
+ if (this.options.height < (rowsToDisplay+1) * 12)
+ {
+ this.options.height = (rowsToDisplay+1) * 12;
+ }
+
+ this.gridHeader
+ .css('height', rowHeight+'%')
+ .text(this.options.label)
+ .appendTo(this.div);
+
+ // the hour rows
+ var show = {
+ 5 : [0,15,30,45],
+ 10 : [0,30],
+ 15 : [0,30],
+ 45 : [0,15,30,45]
+ };
+ var html = '';
+ for(var t = wd_start,i = 1+this.options.extra_rows; t <= wd_end; t += granularity,++i)
+ {
+ html += '
';
+ // show time for full hours, always for 45min interval and at least on every 3 row
+ var time = jQuery.datepicker.formatTime(
+ egw.preference("timeformat") == 12 ? "h:mmtt" : "HH:mm",
+ {
+ hour: t / 60,
+ minute: t % 60,
+ seconds: 0,
+ timezone: 0
+ },
+ {"ampm": (egw.preference("timeformat") == "12")}
+ );
+
+ var time_label = (typeof show[granularity] === 'undefined' ? t % 60 === 0 : show[granularity].indexOf(t % 60) !== -1) ? time : '';
+ html += '
'+time_label+"
\n";
+ }
+ this.div.append(html);
+ },
+
+ /**
+ * Set up the needed day widgets to correctly display the selected date
+ * range. First we calculate the needed dates, then we create any needed
+ * widgets. Existing widgets are recycled rather than discarded.
+ */
+ _drawDays: function() {
+ // If day list is still empty, recalculate it from start & end date
+ if(this.day_list.length === 0)
+ {
+ this.day_list = this._calculate_day_list(this.options.start_date, this.options.end_date, this.options.show_weekend);
+ }
+
+ // Create any needed widgets - otherwise, we'll just recycle
+ // Add any needed day widgets (now showing more days)
+ while(this.day_list.length > this.day_widgets.length)
+ {
+ var day = et2_createWidget('calendar-daycol',{
+ owner: this.options.owner
+ },this);
+ if(this.isInTree())
+ {
+ day.doLoadingFinished();
+ }
+ this.day_widgets.push(day);
+ }
+ // Remove any extra day widgets (now showing less)
+ var delete_index = this.day_widgets.length - 1;
+ while(this.day_widgets.length > this.day_list.length)
+ {
+ // If we're going down to an existing one, just keep it for cool CSS animation
+ while(this.day_list.indexOf(this.day_widgets[delete_index].options.date) > -1)
+ {
+ delete_index--;
+ }
+ this.day_widgets[delete_index].set_width('0px');
+ this.day_widgets[delete_index].free();
+ this.day_widgets.splice(delete_index--,1);
+ }
+
+ // Create / update day widgets with dates and data, if available
+ for(var i = 0; i < this.day_list.length; i++)
+ {
+ day = this.day_widgets[i];
+ // Set the date, and pass any data we have
+ day.set_date(this.day_list[i], this.value[this.day_list[i]] || false);
+ day.set_id(this.day_list[i]);
+ day.set_width((100/this.day_list.length).toFixed(2) + '%');
+
+ // Position
+ $j(day.getDOMNode()).css('left', ((100/this.day_list.length).toFixed(2) * i) + '%');
+ }
+
+ // Update actions
+ if(this._actionManager)
+ {
+ this._link_actions(this._actionManager.children);
+ }
+
+ // TODO: Figure out how to do this with detached nodes
+ /*
+ var nodes = this.day_col.getDetachedNodes();
+ var supportedAttrs = [];
+ this.day_col.getDetachedAttributes(supportedAttrs);
+ supportedAttrs.push("id");
+
+ for(var i = 0; i < day_count; i++)
+ {
+ this.day_col.setDetachedAttributes(nodes.clone(),)
+ }
+ */
+ },
+
+ /**
+ * Calculate a list of days between start and end date, skipping weekends if
+ * desired.
+ *
+ * @param {Date|string} start_date Date that et2_date widget can understand
+ * @param {Date|string} end_date Date that et2_date widget can understand
+ * @param {boolean} show_weekend If not showing weekend, Saturday and Sunday
+ * will not be in the returned list.
+ *
+ * @returns {string[]} List of days in Ymd format
+ */
+ _calculate_day_list: function(start_date, end_date, show_weekend) {
+
+ var day_list = [];
+
+ this.date_helper.set_value(end_date);
+ var end = this.date_helper.date.getTime();
+ var i = 1;
+ this.date_helper.set_value(start_date);
+
+ do
+ {
+ if(show_weekend || !show_weekend && [0,6].indexOf(this.date_helper.date.getUTCDay()) === -1)
+ {
+ day_list.push(''+this.date_helper.get_year() + sprintf('%02d',this.date_helper.get_month()) + sprintf('%02d',this.date_helper.get_date()));
+ }
+ this.date_helper.set_date(this.date_helper.get_date()+1);
+ }
+ // Limit it to 14 days to avoid infinite loops in case something is mis-set,
+ // though the limit is more based on how wide the screen is
+ while(end >= this.date_helper.date.getTime() && i <= 14)
+
+ return day_list;
+ },
+
+ /**
+ * Link the actions to the DOM nodes / widget bits.
+ *
+ * @param {object} actions {ID: {attributes..}+} map of egw action information
+ */
+ _link_actions: function(actions)
+ {
+ this._super.apply(this, arguments);
+
+ // Get the top level element for the tree
+ var objectManager = egw_getAppObjectManager(true);
+ var widget_object = objectManager.getObjectById(this.id);
+
+ // Time grid is just a container
+ widget_object.flags = EGW_AO_FLAG_IS_CONTAINER;
+ },
+
+ /**
+ * Bind a single event as needed to the action system.
+ *
+ * @param {Object} event
+ */
+ _link_event: function(event)
+ {
+ if(!event || !event.app_id) return;
+
+ // Go over the widget & add links - this is where we decide which actions are
+ // 'allowed' for this widget at this time
+ var objectManager = egw_getObjectManager(this.id,false);
+ if(objectManager == null)
+ {
+ // No actions set up
+ return;
+ }
+
+ var obj = null;
+ debugger;
+ if(!(obj = objectManager.getObjectById(event.app_id)))
+ {
+ obj = objectManager.addObject(event.app_id, new et2_action_object_impl(this,event.event_node));
+ obj.data = event;
+ obj.updateActionLinks(objectManager.actionLinks)
+ }
+ objectManager.setAllSelected(false);
+ obj.setSelected(true);
+ objectManager.updateSelectedChildren(obj,true)
+ },
+
+ /**
+ * Provide specific data to be displayed.
+ * This is a way to set start and end dates, owner and event data in once call.
+ *
+ * @param {Object[]} events Array of events, indexed by date in Ymd format:
+ * {
+ * 20150501: [...],
+ * 20150502: [...]
+ * }
+ * Days should be in order.
+ *
+ */
+ set_value: function(events)
+ {
+ if(typeof events !== 'object') return false;
+
+ if(events.owner)
+ {
+ this.set_owner(events.owner);
+ delete events.owner;
+ }
+ this.value = events;
+ var day_list = Object.keys(events);
+ this.set_start_date(day_list[0]);
+ this.set_end_date(day_list[day_list.length-1]);
+
+ // Reset and calculate instead of just use the keys so we can get the weekend preference
+ this.day_list = [];
+ },
+
+ /**
+ * Change the start date
+ *
+ * @param {string|number|Date} new_date New starting date
+ * @returns {undefined}
+ */
+ set_start_date: function(new_date)
+ {
+ // Use date widget's existing functions to deal
+ if(typeof new_date === "object" || typeof new_date === "string" && new_date.length > 8)
+ {
+ this.date_helper.set_value(new_date);
+ }
+ else if(typeof new_date === "string")
+ {
+ this.date_helper.set_year(new_date.substring(0,4));
+ this.date_helper.set_month(new_date.substring(4,6));
+ this.date_helper.set_date(new_date.substring(6,8));
+ }
+
+ var old_date = this.options.start_date;
+ this.options.start_date = this.date_helper.getValue();
+
+ if(old_date !== this.options.start_date && this.isAttached())
+ {
+ this.invalidate();
+ }
+ },
+
+ /**
+ * Change the end date
+ *
+ * @param {string|number|Date} new_date New end date
+ * @returns {undefined}
+ */
+ set_end_date: function(new_date)
+ {
+ // Use date widget's existing functions to deal
+ if(typeof new_date === "object" || typeof new_date === "string" && new_date.length > 8)
+ {
+ this.date_helper.set_value(new_date);
+ }
+ else if(typeof new_date === "string")
+ {
+ this.date_helper.set_year(new_date.substring(0,4));
+ this.date_helper.set_month(new_date.substring(4,6));
+ this.date_helper.set_date(new_date.substring(6,8));
+ }
+
+ var old_date = this.options.end_date;
+ this.options.end_date = this.date_helper.getValue();
+
+ if(old_date !== this.options.end_date && this.isAttached())
+ {
+ this.invalidate();
+ }
+ },
+
+ get_granularity: function()
+ {
+ // get option, or user's preference
+ if(typeof this.options.granularity === 'undefined')
+ {
+ this.options.granularity = egw.preference('interval','calendar') || 30;
+ }
+ return parseInt(this.options.granularity);
+ },
+
+ /**
+ * Click handler calling custom handler set via onclick attribute to this.onclick
+ *
+ * This also handles all its own actions, including navigation. If there is
+ * an event associated with the click, it will be found and passed to the
+ * onclick function.
+ *
+ * @param {Event} _ev
+ * @returns {boolean}
+ */
+ click: function(_ev)
+ {
+ var result = true;
+
+ // Is this click in the event stuff, or in the header?
+ if(this.days.has(_ev.target).length)
+ {
+ // Event came from inside, maybe a calendar event
+ var event = this._get_event_info(_ev.originalEvent.target);
+ if(typeof this.onclick == 'function')
+ {
+ // Make sure function gets a reference to the widget, splice it in as 2. argument if not
+ var args = Array.prototype.slice.call(arguments);
+ if(args.indexOf(this) == -1) args.splice(1, 0, this);
+
+ result = this.onclick.apply(this, args);
+ }
+
+ if(event.id && result && !this.options.disabled && !this.options.readonly)
+ {
+ this._edit_event(event);
+
+ return false;
+ }
+ return result;
+ }
+ else
+ {
+ // Default handler to open a new event at the selected time
+ this.egw().open(null, 'calendar', 'add', {
+ date: _ev.target.dataset.date || this.day_list[0],
+ hour: _ev.target.dataset.hour || this.options.day_start,
+ minute: _ev.target.dataset.minute || 0
+ } , '_blank');
+ return false;
+ }
+ },
+
+ _get_event_info: function(dom_node)
+ {
+ // Determine as much relevant info as can be found
+ var event_node = $j(dom_node).closest('[data-id]',this.div)[0];
+ var day_node = $j(event_node).closest('[data-date]',this.div)[0];
+
+ return jQuery.extend({
+ event_node: event_node,
+ day_node: day_node,
+ },
+ event_node ? event_node.dataset : {},
+ day_node ? day_node.dataset : {}
+ );
+ },
+
+ _edit_event: function(event)
+ {
+ if(event.recur_type)
+ {
+ var edit_id = event.id;
+ var edit_date = event.start;
+ var that = this;
+ var buttons = [
+ {text: this.egw().lang("Edit exception"), id: "exception", class: "ui-priority-primary", "default": true},
+ {text: this.egw().lang("Edit series"), id:"series"},
+ {text: this.egw().lang("Cancel"), id:"cancel"}
+ ];
+ et2_dialog.show_dialog(function(_button_id)
+ {
+ switch(_button_id)
+ {
+ case 'exception':
+ that.egw().open(edit_id, 'calendar', 'edit', {date:edit_date,exception: '1'});
+ break;
+ case 'series':
+ that.egw().open(edit_id, 'calendar', 'edit', {date:edit_date});
+ break;
+ case 'cancel':
+
+ default:
+ break;
+ }
+ },this.egw().lang("Do you want to edit this event as an exception or the whole series?"),
+ this.egw().lang("This event is part of a series"), {}, buttons, et2_dialog.WARNING_MESSAGE);
+ }
+ else
+ {
+ this.egw().open(event.id, event.app||'calendar','edit');
+ }
+ },
+
+
+ /**
+ * Set which user owns this. Owner is passed along to the individual
+ * days.
+ *
+ * @param {number} _owner Account ID
+ * @returns {undefined}
+ */
+ set_owner: function(_owner)
+ {
+ // Let select-account widget handle value validation
+ this.owner.set_value(_owner);
+
+ this.options.owner = _owner;//this.owner.getValue();
+
+ for (var i = this._children.length - 1; i >= 0; i--)
+ {
+ if(typeof this._children[i].set_owner === 'function')
+ {
+ this._children[i].set_owner(this.options.owner);
+ }
+ }
+ },
+
+ /**
+ * Code for implementing et2_IDetachedDOM
+ *
+ * @param {array} _attrs array to add further attributes to
+ */
+ getDetachedAttributes: function(_attrs) {
+ _attrs.push('start_date','end_date');
+ },
+
+ getDetachedNodes: function() {
+ return [this.getDOMNode()];
+ },
+
+ setDetachedAttributes: function(_nodes, _values) {
+ this.div = $j(_nodes[0]);
+
+ if(_values.start_date)
+ {
+ this.set_start_date(_values.start_date);
+ }
+ if(_values.end_date)
+ {
+ this.set_end_date(_values.end_date);
+ }
+ },
+
+ // Resizable interface
+ resize: function (_height)
+ {
+ this.options.height = _height;
+ this.div.css('height', this.options.height);
+ }
+});
+et2_register_widget(et2_calendar_timegrid, ["calendar-timegrid"]);
\ No newline at end of file
diff --git a/calendar/templates/default/app.css b/calendar/templates/default/app.css
index 487b49a6bc..d8d4a65599 100644
--- a/calendar/templates/default/app.css
+++ b/calendar/templates/default/app.css
@@ -112,7 +112,7 @@ e.g. the div with class calendar_calTimeGrid is generated by the timeGridWidget
/* single row in the time-line, always used in conjunction with row_{on|off}, you dont need to set a bgcolor, but you can
*/
-.calendar_calTimeRow,.calendar_calTimeRowOff{
+.calendar_calTimeRow {
position: absolute;
width: 100%;
/* set via inline style on runtime:
@@ -120,8 +120,8 @@ e.g. the div with class calendar_calTimeGrid is generated by the timeGridWidget
* top:
*/
}
-.calendar_calTimeRow{
-/* background-color: silver; */
+.calendar_calTimeRow:nth-child(odd) {
+ background-color: rgba(0,0,0,0.01);
}
/* time in a timeRow
@@ -166,6 +166,9 @@ e.g. the div with class calendar_calTimeGrid is generated by the timeGridWidget
* width:
*/
border-left: 1px solid silver;
+
+ /* Nice transition when changing days in a week */
+ -webkit-transition: 0.1s ease-in-out;
}
/* Calendar Id #
*/
@@ -274,6 +277,7 @@ e.g. the div with class calendar_calTimeGrid is generated by the timeGridWidget
z-index: 20;
border-width: 1px;
border-radius: 6px;
+ border-style: solid;
-moz-border-radius: 6px;
-webkit-border-radius: 6px;
/* set via inline style on runtime:
@@ -281,12 +285,22 @@ e.g. the div with class calendar_calTimeGrid is generated by the timeGridWidget
* height: depending on length
* border-color: depending on category
* background: depending on category (shade)
+ *
+ * These are defaults:
*/
+ border-color: #808080;
+ background-color: #808080;
}
.calendar_calEvent:hover{
cursor: pointer;
}
+/**
+ * User has not accepted the invitation
+ */
+.calendar_calEventUnknown {
+ background-color: white !important;
+}
/**
* All participants accepted the invitation
*/
@@ -368,6 +382,18 @@ e.g. the div with class calendar_calTimeGrid is generated by the timeGridWidget
left: 2px;
right: 2px;
height: 99%;
+
+ /* Gradient */
+ background-image: -webkit-linear-gradient(0deg, rgba(255,255,255,0) 0, rgba(255,255,255,0.85) 60%);
+ background-image: -moz-linear-gradient(90deg, rgba(255,255,255,0) 0, rgba(255,255,255,0.85) 60%);
+ background-image: linear-gradient(90deg, rgba(255,255,255,0) 0, rgba(255,255,255,0.85) 60%);
+ background-position: 50% 50%;
+ -webkit-background-origin: padding-box;
+ background-origin: padding-box;
+ -webkit-background-clip: border-box;
+ background-clip: border-box;
+ -webkit-background-size: auto auto;
+ background-size: auto auto;
}
.calendar_calEventBodySmall{
diff --git a/calendar/templates/default/view.xet b/calendar/templates/default/view.xet
new file mode 100644
index 0000000000..0e6fe26939
--- /dev/null
+++ b/calendar/templates/default/view.xet
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/calendar/templates/pixelegg/app.css b/calendar/templates/pixelegg/app.css
index 5985f4a098..8757947f50 100755
--- a/calendar/templates/pixelegg/app.css
+++ b/calendar/templates/pixelegg/app.css
@@ -11,7 +11,7 @@
* @package calendar
* @version $Id$
*/
-/* $Id: app.css 49793 2014-12-09 17:55:00Z nathangray $ */
+/* $Id: app.css 52434 2015-04-07 13:15:33Z hnategh $ */
/*Media print classes*/
@media print {
.th td,
@@ -127,8 +127,7 @@ e.g. the div with class calendar_calTimeGrid is generated by the timeGridWidget
}
/* single row in the time-line, always used in conjunction with row_{on|off}, you dont need to set a bgcolor, but you can
*/
-.calendar_calTimeRow,
-.calendar_calTimeRowOff {
+.calendar_calTimeRow {
position: absolute;
width: 100%;
/* set via inline style on runtime:
@@ -136,8 +135,8 @@ e.g. the div with class calendar_calTimeGrid is generated by the timeGridWidget
* top:
*/
}
-.calendar_calTimeRow {
- /* background-color: silver; */
+.calendar_calTimeRow:nth-child(odd) {
+ background-color: rgba(0, 0, 0, 0.01);
}
/* time in a timeRow
*/
@@ -181,6 +180,8 @@ e.g. the div with class calendar_calTimeGrid is generated by the timeGridWidget
* width:
*/
border-left: 1px solid silver;
+ /* Nice transition when changing days in a week */
+ -webkit-transition: 0.1s ease-in-out;
}
/* Calendar Id #
*/
@@ -290,6 +291,7 @@ e.g. the div with class calendar_calTimeGrid is generated by the timeGridWidget
z-index: 20;
border-width: 1px;
border-radius: 6px;
+ border-style: solid;
-moz-border-radius: 6px;
-webkit-border-radius: 6px;
/* set via inline style on runtime:
@@ -297,11 +299,21 @@ e.g. the div with class calendar_calTimeGrid is generated by the timeGridWidget
* height: depending on length
* border-color: depending on category
* background: depending on category (shade)
+ *
+ * These are defaults:
*/
+ border-color: #808080;
+ background-color: #808080;
}
.calendar_calEvent:hover {
cursor: pointer;
}
+/**
+ * User has not accepted the invitation
+ */
+.calendar_calEventUnknown {
+ background-color: white !important;
+}
/**
* All participants accepted the invitation
*/
@@ -379,6 +391,17 @@ e.g. the div with class calendar_calTimeGrid is generated by the timeGridWidget
left: 2px;
right: 2px;
height: 99%;
+ /* Gradient */
+ background-image: -webkit-linear-gradient(0deg, rgba(255, 255, 255, 0) 0, rgba(255, 255, 255, 0.85) 60%);
+ background-image: -moz-linear-gradient(90deg, rgba(255, 255, 255, 0) 0, rgba(255, 255, 255, 0.85) 60%);
+ background-image: linear-gradient(90deg, rgba(255, 255, 255, 0) 0, rgba(255, 255, 255, 0.85) 60%);
+ background-position: 50% 50%;
+ -webkit-background-origin: padding-box;
+ background-origin: padding-box;
+ -webkit-background-clip: border-box;
+ background-clip: border-box;
+ -webkit-background-size: auto auto;
+ background-size: auto auto;
}
.calendar_calEventBodySmall {
font-size: 95%;
@@ -684,6 +707,9 @@ img.calendar_print_appicon {
.calendar_favorite_portlet .calendar_plannerScale a img {
display: none;
}
+#calendar-edit select#calendar-edit_duration {
+ width: 133px;
+}
/*generell*/
.egw_fw_content_browser_iframe img[src$="svg"] {
background-color: #828282 !important;