forked from extern/egroupware
WIP on converting Calendar to etemplate2
- Weekview shows widgets now
This commit is contained in:
parent
b0c7eb2650
commit
2f6c66c600
@ -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);
|
||||
}
|
||||
}
|
@ -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'),
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -10,7 +10,8 @@
|
||||
*/
|
||||
|
||||
/*egw:uses
|
||||
/etemplate/js/etemplate2.js
|
||||
/etemplate/js/etemplate2.js;
|
||||
/calendar/js/et2_widget_timegrid.js;
|
||||
*/
|
||||
|
||||
/**
|
||||
|
650
calendar/js/et2_widget_daycol.js
Normal file
650
calendar/js/et2_widget_daycol.js
Normal 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;
|
||||
}
|
||||
}
|
||||
});
|
284
calendar/js/et2_widget_event.js
Normal file
284
calendar/js/et2_widget_event.js
Normal 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"]);
|
683
calendar/js/et2_widget_timegrid.js
Normal file
683
calendar/js/et2_widget_timegrid.js
Normal 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"]);
|
@ -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{
|
||||
|
27
calendar/templates/default/view.xet
Normal file
27
calendar/templates/default/view.xet
Normal 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>
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user