WIP on converting Calendar to etemplate2

- Weekview shows widgets now
This commit is contained in:
Nathan Gray 2015-05-06 19:03:45 +00:00
parent b0c7eb2650
commit 2f6c66c600
10 changed files with 1763 additions and 14 deletions

View File

@ -0,0 +1,31 @@
<?php
/*
* Egroupware
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @link http://www.egroupware.org
* @author Nathan Gray
* @version $Id$
*/
/**
* Creates a grid with rows for the time, columns for (multiple) days containing events
*
* The associated javascript files are loaded by calendar/js/app.js using the
* server-side include manager to get all the dependancies
*
* @author Nathan Gray
*/
class calendar_timegrid_etemplate_widget extends etemplate_widget
{
/**
* Ajax callback to fetch the holidays for a given year.
* @param type $year
*/
public static function ajax_get_holidays($year)
{
$cal_bo = new calendar_bo();
$holidays = $cal_bo->read_holidays((int)$year);
egw_json_response::get()->data($holidays);
}
}

View File

@ -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'),

View File

@ -907,8 +907,29 @@ class calendar_uiviews extends calendar_ui
echo $content;
}
$content = array('view' => array());
return $content;
// Always do 7 days for a week so scrolling works properly
$this->last = $search_params['end'] = strtotime("+7 days",$this->first) - 1;
if (count($users) == 1 || count($users) > $this->bo->calview_no_consolidate) // for more then X users, show all in one row
{
$content['view'][] = $this->tagWholeDayOnTop($this->bo->search($search_params)) +
array('owner' => $users);
}
else
{
foreach($this->_get_planner_users(false) as $uid => $label)
{
$search_params['users'] = $uid;
$content['view'][] = $this->tagWholeDayOnTop($this->bo->search($search_params))
+ array('owner' => $uid);
}
}
$tmpl = new etemplate_new('calendar.view');
$tmpl->setElementAttribute('view','show_weekend', $days == 7);
$ui = new calendar_uilist();
$tmpl->setElementAttribute('view','actions',$ui->get_actions());
$tmpl->exec(__METHOD__, $content);
}
/**

View File

@ -10,7 +10,8 @@
*/
/*egw:uses
/etemplate/js/etemplate2.js
/etemplate/js/etemplate2.js;
/calendar/js/et2_widget_timegrid.js;
*/
/**

View File

@ -0,0 +1,650 @@
/*
* Egroupware
* @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
et2_core_valueWidget;
/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_daycol = et2_valueWidget.extend([et2_IDetachedDOM],
{
attributes: {
date: {
name: "Date",
type: "any",
description: "What date is this daycol for. YYYYMMDD or Date"
},
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"
},
display_birthday_as_event: {
name: "Birthdays",
type: "boolean",
default: false,
description: "Display birthdays as events"
},
display_holiday_as_event: {
name: "Holidays",
type: "boolean",
default: false,
description: "Display holidays as events"
}
},
/**
* Constructor
*
* @memberOf et2_calendar_daycol
*/
init: function() {
this._super.apply(this, arguments);
// Main container
this.div = $j(document.createElement("div"))
.addClass("calendar_calDayCol")
.css('width',this.options.width);
this.title = $j(document.createElement('div'))
.appendTo(this.div);
this.setDOMNode(this.div[0]);
// Init to defaults, just in case
this.display_settings = {
wd_start: 60*9,
wd_end: 60*17,
granularity: 30,
extraRows: 2,
rowsToDisplay: 10,
rowHeight: 20
}
},
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);
},
/**
* Draw the individual divs for clicking
*/
_draw: function() {
// Remove any existing
$j('.calendar_calAddEvent',this.div).remove();
// Grab real values from parent
if(this._parent && this._parent.instanceOf(et2_calendar_timegrid))
{
this.display_settings.wd_start = 60*this._parent.options.day_start;
this.display_settings.wd_end = 60*this._parent.options.day_end;
this.display_settings.granularity = this._parent.options.granularity;
this.display_settings.extraRows = this._parent.options.extra_rows;
}
this.display_settings.rowsToDisplay = ((this.display_settings.wd_end - this.display_settings.wd_start)/this.display_settings.granularity)+2+2*this.display_settings.extraRows;
this.display_settings.rowHeight= (100/this.display_settings.rowsToDisplay).toFixed(1);
// adding divs to click on for each row / time-span
for(var t =this.display_settings.wd_start,i = 1+this.display_settings.extraRows; t <= this.display_settings.wd_end; t += this.display_settings.granularity,++i)
{
var linkData = {
'menuaction':'calendar.calendar_uiforms.edit',
'date' : this.options.date,
'hour' : sprintf("%02d",Math.floor(t / 60)),
'minute' : sprintf("%02d",Math.floor(t % 60))
};
if (this.options.owner) linkData['owner'] = this.options.owner;
var droppableDateTime = linkData['date'] + "T" + linkData['hour'] + linkData['minute'];
var droppableID='drop_'+droppableDateTime+'_O'+(this.options.owner<0?'group'+Math.abs(this.options.owner):this.options.owner);
var hour = jQuery('<div id="' + droppableID + '" style="height:'+ this.display_settings.rowHeight +'%; top: '+ (i*this.display_settings.rowHeight).toFixed(1) +'%;" class="calendar_calAddEvent">')
.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;
}
}
});

View File

@ -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('<span class="calendar_calEventTitle">'+title+'</span>')
}
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"]);

View File

@ -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 += '<div class="calendar_calTimeRow" style="height: '+rowHeight+'%; top:'+ (i*rowHeight).toFixed(1) +'%;">';
// 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 += '<div class="calendar_calTimeRowTime et2_clickable data-time="'+time.trim()+' data-hour="'+Math.floor(t/60)+'" data-minute="'+(t%60)+'">'+time_label+"</div></div>\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"]);

View File

@ -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{

View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Egroupware
@license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
@package
@subpackage
@link http://www.egroupware.org
@author Nathan Gray
@version $Id$
-->
<!DOCTYPE overlay PUBLIC '-//Stylite AG//eTemplate 2//EN' 'http://www.egroupware.org/etemplate2.dtd'>
<overlay>
<template id="calendar.view">
<grid id="view" width="100%" height="100%">
<columns>
<column/>
</columns>
<rows>
<row>
<calendar-timegrid id="${row}"></calendar-timegrid>
</row>
</rows>
</grid>
</template>
</overlay>

View File

@ -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;