Calendar et2 conversion work in progress.

Context menu is enabled, but not all actions are fully implemented yet.
This commit is contained in:
Nathan Gray 2015-06-10 21:51:28 +00:00
parent b4d597fbb5
commit 920616e37a
21 changed files with 2224 additions and 840 deletions

View File

@ -18,6 +18,36 @@
*/
class calendar_timegrid_etemplate_widget extends etemplate_widget
{
/**
* Set up what we know on the server side.
*
* Sending a first chunk of rows
*
* @param string $cname
* @param array $expand values for keys 'c', 'row', 'c_', 'row_', 'cont'
*/
public function beforeSendToClient($cname, array $expand=null)
{
$form_name = self::form_name($cname, $this->id, $expand);
$value =& self::get_array(self::$request->content, $form_name, true);
error_log(__METHOD__ . "($cname,".array2string($expand));
error_log(array2string($value));
foreach($value as $day => &$events)
{
foreach($events as &$event)
{
if(!is_array($event)) continue;
foreach(array('start','end') as $date)
{
$event[$date] = egw_time::to($event[$date],'Y-m-d\TH:i:s\Z');
}
}
}
}
/**
* Ajax callback to fetch the holidays for a given year.
* @param type $year

View File

@ -267,7 +267,9 @@ class calendar_ui
// retrieve saved states from prefs
if(!$states)
{
error_log('HERE');
$states = unserialize($this->bo->cal_prefs['saved_states']);
error_log(array2string($states));
}
// only look at _REQUEST, if we are in the calendar (prefs and admin show our sidebox menu too!)
if (is_null($set_states))
@ -443,7 +445,7 @@ class calendar_ui
elseif (egw_json_request::isJSONRequest())// && strpos($_GET['menuaction'], 'calendar_uiforms') === false)
{
$response = egw_json_response::get();
$response->apply('app.calendar.set_state', array($states, $_GET['menuaction']));
//$response->apply('app.calendar.set_state', array($states, $_GET['menuaction']));
}
else
{
@ -581,281 +583,24 @@ class calendar_ui
function sidebox_menu()
{
$link_vars = array();
// Magic etemplate2 favorites menu (from nextmatch widget)
// Magic etemplate2 favorites menu (from framework)
display_sidebox('calendar', lang('Favorites'), egw_framework::favorite_list('calendar'));
$file = array('menuOpened' => true); // menu open by default
$n = 0; // index for file-array
$planner_days_for_view = false;
switch($this->view)
{
case 'month': $planner_days_for_view = 0; break;
case 'week': $planner_days_for_view = $this->cal_prefs['days_in_weekview'] == 5 ? 5 : 7; break;
case 'day': $planner_days_for_view = 1; break;
}
// Toolbar with the views
$views = '<table style="width: 100%;"><tr>'."\n";
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','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'),
'planner' => array('icon'=>'planner','text'=>'Group planner','menuaction' => 'calendar.calendar_uiviews.planner','sortby' => $this->sortby),
'list' => array('icon'=>'list','text'=>'Listview','menuaction'=>'calendar.calendar_uilist.listview','ajax'=>'true'),
) as $view => $data)
{
$icon_name = array_shift($data);
$title = array_shift($data);
$vars = array_merge($link_vars,$data);
$icon = html::image('calendar',$icon_name,lang($title),"class=sideboxstar"); //to avoid jscadender from not displaying with pngfix
if ($view == 'add')
{
$link = html::a_href($icon,'javascript:'.$this->popup(egw::link('/index.php',array(
'menuaction' => 'calendar.calendar_uiforms.edit',
),false)));
}
else
{
$link = html::a_href($icon,'/index.php',$vars);
}
$views .= '<td align="center">'.$link."</td>\n";
}
$views .= "</tr></table>\n";
// hack to disable invite ACL column, if not enabled in config
if ($_GET['menuaction'] == 'preferences.uiaclprefs.index' &&
(!$this->bo->require_acl_invite || $this->bo->require_acl_invite == 'groups' && !($_REQUEST['owner'] < 0)))
{
$views .= "<style type='text/css'>\n\t.aclInviteColumn { display: none; }\n</style>\n";
}
$file[++$n] = array('text' => $views,'no_lang' => True,'link' => False,'icon' => False);
// special views and view-options menu
$options = '';
foreach(array(
array(
'text' => lang('dayview'),
'value' => 'menuaction=calendar.calendar_uiviews.day',
'selected' => $this->view == 'day',
),
array(
'text' => lang('four days view'),
'value' => 'menuaction=calendar.calendar_uiviews.day4',
'selected' => $this->view == 'day4',
),
array(
'text' => lang('weekview with weekend'),
'value' => 'menuaction=calendar.calendar_uiviews.week&days=7',
'selected' => $this->view == 'week' && $this->cal_prefs['days_in_weekview'] != 5,
),
array(
'text' => lang('weekview without weekend'),
'value' => 'menuaction=calendar.calendar_uiviews.week&days=5',
'selected' => $this->view == 'week' && $this->cal_prefs['days_in_weekview'] == 5,
),
array(
'text' => lang('Multiple week view'),
'value' => 'menuaction=calendar.calendar_uiviews.weekN',
'selected' => $this->view == 'weekN',
),
array(
'text' => lang('monthview'),
'value' => 'menuaction=calendar.calendar_uiviews.month',
'selected' => $this->view == 'month',
),
array(
'text' => lang('yearview'),
'value' => 'menuaction=calendar.calendar_uiviews.year',
'selected' => $this->view == 'year',
),
array(
'text' => lang('planner by category'),
'value' => 'menuaction=calendar.calendar_uiviews.planner&sortby=category'.
($planner_days_for_view !== false ? '&planner_days='.$planner_days_for_view : ''),
'selected' => $this->view == 'planner' && $this->sortby != 'user',
),
array(
'text' => lang('planner by user'),
'value' => 'menuaction=calendar.calendar_uiviews.planner&sortby=user'.
($planner_days_for_view !== false ? '&planner_days='.$planner_days_for_view : ''),
'selected' => $this->view == 'planner' && $this->sortby == 'user',
),
array(
'text' => lang('yearly planner'),
'value' => 'menuaction=calendar.calendar_uiviews.planner&sortby=month',
'selected' => $this->view == 'planner' && $this->sortby == 'month',
),
array(
'text' => lang('listview'),
'value' => 'menuaction=calendar.calendar_uilist.listview&ajax=true',
'selected' => $this->view == 'listview',
),
) as $data)
{
$options .= '<option value="'.$data['value'].'"'.($data['selected'] ? ' selected="1"' : '').'>'.html::htmlspecialchars($data['text'])."</option>\n";
}
$file[++$n] = $this->_select_box('displayed view','view',$options);
// Search
$file[++$n] = array(
'text' => html::input('keywords', '', 'text',
'id="calendar_keywords" style="width: 96.5%;" placeholder="'.html::htmlspecialchars(lang('Search').'...').'"'),
'no_lang' => True,
'link' => False,
'icon' => false,
);
// Minicalendar
$link = array();
foreach(array(
'day' => 'calendar.calendar_uiviews.day',
'week' => 'calendar.calendar_uiviews.week',
'month' => 'calendar.calendar_uiviews.month') as $view => $menuaction)
{
if ($this->view == 'planner' || $this->view == 'listview')
{
$link_vars['menuaction'] = $this->view_menuaction; // must be first one
switch($view)
{
case 'day': $link_vars[$this->view.'_days'] = $this->view == 'planner' ? 1 : ''; break;
case 'week': $link_vars[$this->view.'_days'] = $this->cal_prefs['days_in_weekview'] == 5 ? 5 : 7; break;
case 'month': $link_vars[$this->view.'_days'] = 0; break;
}
if ($this->view == 'listview') $link_vars['ajax'] = 'true';
}
elseif(substr($this->view,0,4) == 'week' && $view == 'week')
{
$link_vars['menuaction'] = $this->view_menuaction; // stay in the N-week-view
}
elseif ($view == 'day' && $this->view == 'day4')
{
$link_vars['menuaction'] = $this->view_menuaction; // stay in the day-view
}
else
{
$link_vars['menuaction'] = $menuaction;
}
unset($link_vars['date']); // gets set in jscal
$link[$view] = $l = egw::link('/index.php',$link_vars,false);
}
if (($flatdate = $this->date)) // string if format YYYYmmdd or timestamp
{
$flatdate = is_int($flatdate) ? adodb_date('m/d/Y',$flatdate) :
substr($flatdate,4,2).'/'.substr($flatdate,6,2).'/'.substr($flatdate,0,4);
}
$jscalendar = '<div id="calendar-container"></div>';
$file[++$n] = array('text' => $jscalendar,'no_lang' => True,'link' => False,'icon' => False);
// Category Selection
$cat_id = explode(',',$this->cat_id);
$current_view_url = egw::link('/index.php',array(
'menuaction' => $this->view_menuaction,
'date' => $this->date,
)+($this->view == 'listview' ? array('ajax' => 'true') : array()),false);
$select = ' <select style="width: 86%;" id="calendar_cat_id" name="cat_id" title="'.
lang('Select a %1',lang('Category')). '"'.($cat_id && count($cat_id) > 1 ? ' multiple=true size=4':''). '>'.
'<option value="0">'.lang('All categories').'</option>'.
$this->categories->formatted_list('select','all',$cat_id,'True').
"</select>\n" . html::image('phpgwapi','category','','id="calendar_cat_id_multiple"')."
<script type=\"text/javascript\" src=\"{$GLOBALS['egw_info']['server']['webserver_url']}/calendar/js/navigation.js\" id=\"calendar-navigation-script\"".
" data-link-day-url =\"".htmlspecialchars($link['day']).
"\" data-link-week-url=\"".htmlspecialchars($link['week']).
"\" data-link-month-url=\"".htmlspecialchars($link['month']).
"\" data-date=\"".htmlspecialchars($flatdate).
"\" data-current-date=\"".htmlspecialchars(egw_time::to('now', 'Ymd')).
"\" data-current-view-url=\"".htmlspecialchars($current_view_url)."\"/></script>\n";
$file[++$n] = array(
'text' => $select,
'no_lang' => True,
'link' => False,
'icon' => false,
// Target for etemplate
$file[] = array(
'no_lang' => true,
'text'=>'<span id="calendar-et2_target" />',
'link'=>false,
'icon' => false
);
// Calendarselection: User or Group
if(count($this->bo->grants) > 0 && $this->accountsel->account_selection != 'none')
{
$grants = array();
foreach($this->bo->list_cals() as $grant)
{
$grants[] = $grant['grantor'];
}
// we no longer exclude non-accounts from the account-selection: it shows all types of participants
$accounts = explode(',',$this->owner);
$file[] = array(
'text' =>
$this->accountsel->selection('owner','uical_select_owner',$accounts,'calendar+',count($accounts) > 1 ? 4 : 1,False,
' style="width: '.(count($accounts) > 1 && in_array($this->common_prefs['account_selection'],array('selectbox','groupmembers')) ? '86%' : '86%').';"'.
' title="'.lang('select a %1',lang('user')).'"','',$grants,false,array($this->bo,'participant_name')),
'no_lang' => True,
'link' => False,
'icon' => false,
);
}
// Filter all or hideprivate
$filter_options = '';
foreach(array(
'default' => array(lang('Not rejected'), lang('Show all status, but rejected')),
'accepted' => array(lang('Accepted'), lang('Show only accepted events')),
'unknown' => array(lang('Invitations'), lang('Show only invitations, not yet accepted or rejected')),
'tentative' => array(lang('Tentative'), lang('Show only tentative accepted events')),
'delegated' => array(lang('Delegated'), lang('Show only delegated events')),
'rejected' => array(lang('Rejected'),lang('Show only rejected events')),
'owner' => array(lang('Owner too'),lang('Show also events just owned by selected user')),
'all' => array(lang('All incl. rejected'),lang('Show all status incl. rejected events')),
'hideprivate' => array(lang('Hide private infos'),lang('Show all events, as if they were private')),
'showonlypublic' => array(lang('Hide private events'),lang('Show only events flagged as public, (not checked as private)')),
'no-enum-groups' => array(lang('only group-events'),lang('Do not include events of group members')),
'not-unknown' => array(lang('No meeting requests'),lang('Show all status, but unknown')),
) as $value => $label)
{
list($label,$title) = $label;
$filter_options .= '<option value="'.$value.'"'.($this->filter == $value ? ' selected="selected"' : '').' title="'.$title.'">'.$label.'</options>'."\n";
}
// add deleted filter, if history logging is activated
if($GLOBALS['egw_info']['server']['calendar_delete_history'])
{
$filter_options .= '<option value="deleted"'.($this->filter == 'deleted' ? ' selected="selected"' : '').' title="'.lang('Show events that have been deleted').'">'.lang('Deleted').'</options>'."\n";
}
$file[] = $this->_select_box('Filter','filter',$filter_options,'86%');
// enable this to get checkbox setting $this->test eg. usable to trigger different code in calendar_so
//$file[count($file)-1]['text'] .= html::checkbox('test', $this->test==='true', 'true', 'id="calendar_test"');
// Merge print
// Merge print placeholders (selectbox in etemplate)
if ($GLOBALS['egw_info']['user']['preferences']['calendar']['document_dir'])
{
$options = '';
$documents = calendar_merge::get_documents($GLOBALS['egw_info']['user']['preferences']['calendar']['document_dir'], '', null,'calendar');
foreach($documents as $key => $value)
{
$options .= '<option value="'.html::htmlspecialchars($key).'">'.html::htmlspecialchars($value)."</option>\n";
}
if($options != '') {
$options = '<option value="">'.lang('Insert in document')."</option>\n" . $options;
$select = ' <select style="width: 86%;" name="merge" id="calendar_merge" title="'.
html::htmlspecialchars(lang('Select a %1',lang('merge document...'))).'">'.
$options."</select>\n";
$file[] = array(
'text' => $select,
'no_lang' => True,
'link' => False,
'icon' => false,
);
}
{
$file['Placeholders'] = egw::link('/index.php','menuaction=calendar.calendar_merge.show_replacements');
}
$appname = 'calendar';
$menu_title = lang('Calendar Menu');
display_sidebox($appname,$menu_title,$file);
@ -884,6 +629,213 @@ class calendar_ui
}
}
/**
* Makes the sidebox content with etemplate, after hook is processed
*/
function sidebox_etemplate($content = array())
{
if($content['merge'])
{
$_GET['merge'] = $content['merge'];
$this->merge();
return;
}
$sidebox = new etemplate_new('calendar.sidebox');
$content['view'] = $this->view ? $this->view : 'week';
$content['date'] = $this->date ? $this->date : egw_time();
$owners = $this->owner ? is_array($this->owner) ? array($this->owner) : explode(',',$this->owner) : array($GLOBALS['egw_info']['user']['account_id']);
/*
foreach($owners as $owner)
{
$app = 'home-accounts';
switch(substr($owner, 0,1))
{
case 'r':
$app = 'resources';
break;
}
$content['owner'][] = array('app' => $app, 'id' => (int)$owner ? $owner : substr($owner,1));
}
*/
$sel_options = array();
$readonlys = array();
foreach(array(
array(
'text' => lang('dayview'),
'value' => '{"view":"day"}',
'selected' => $this->view == 'day',
),
array(
'text' => lang('four days view'),
'value' => '{"view":"day4","days":4}',
'selected' => $this->view == 'day4',
),
array(
'text' => lang('weekview with weekend'),
'value' => '{"view":"week","days":7}',
'selected' => $this->view == 'week' && $this->cal_prefs['days_in_weekview'] != 5,
),
array(
'text' => lang('weekview without weekend'),
'value' => '{"view":"week","days":5}',
'selected' => $this->view == 'week' && $this->cal_prefs['days_in_weekview'] == 5,
),
array(
'text' => lang('Multiple week view'),
'value' => '{"view":"weekN"}',
'selected' => $this->view == 'weekN',
),
array(
'text' => lang('monthview'),
'value' => '{"view":"month"}',
'selected' => $this->view == 'month',
),
array(
'text' => lang('yearview'),
'value' => '{"view":"year", "menuaction":"calendar.calendar_uiviews.year"}',
'selected' => $this->view == 'year',
),
array(
'text' => lang('planner by category'),
'value' => '{"view":"planner", "menuaction":"calendar.calendar_uiviews.planner","sortby":"category"}',
'selected' => $this->view == 'planner' && $this->sortby != 'user',
),
array(
'text' => lang('planner by user'),
'value' => '{"view":"planner", "menuaction":"calendar.calendar_uiviews.planner","sortby":"user"}',
'selected' => $this->view == 'planner' && $this->sortby == 'user',
),
array(
'text' => lang('yearly planner'),
'value' => '{"view":"planner", "menuaction":"calendar.calendar_uiviews.planner","sortby":"month"}',
'selected' => $this->view == 'planner' && $this->sortby == 'month',
),
array(
'text' => lang('listview'),
'value' => '{"view":"listview"}',
'selected' => $this->view == 'listview',
),
)as $data)
{
if($data['selected'])
{
$content['view'] = $data['value'];
}
$sel_options['view'][] = array(
'label' => $data['text'],
'value' => $data['value']
);
}
$sel_options['filter'] = array(
array('value' => 'default', 'label' => lang('Not rejected'), 'title' => lang('Show all status, but rejected')),
array('value' => 'accepted', 'label' => lang('Accepted'), 'title' => lang('Show only accepted events')),
array('value' => 'unknown', 'label' => lang('Invitations'), 'title' => lang('Show only invitations, not yet accepted or rejected')),
array('value' => 'tentative', 'label' => lang('Tentative'), 'title' => lang('Show only tentative accepted events')),
array('value' => 'delegated', 'label' => lang('Delegated'), 'title' => lang('Show only delegated events')),
array('value' => 'rejected', 'label' => lang('Rejected'),'title' => lang('Show only rejected events')),
array('value' => 'owner', 'label' => lang('Owner too'),'title' => lang('Show also events just owned by selected user')),
array('value' => 'all', 'label' => lang('All incl. rejected'),'title' => lang('Show all status incl. rejected events')),
array('value' => 'hideprivate', 'label' => lang('Hide private infos'),'title' => lang('Show all events, as if they were private')),
array('value' => 'showonlypublic', 'label' => lang('Hide private events'),'title' => lang('Show only events flagged as public, (not checked as private)')),
array('value' => 'no-enum-groups', 'label' => lang('only group-events'),'title' => lang('Do not include events of group members')),
array('value' => 'not-unknown', 'label' => lang('No meeting requests'),'title' => lang('Show all status, but unknown')),
);
// Merge print
if ($GLOBALS['egw_info']['user']['preferences']['calendar']['document_dir'])
{
$sel_options['merge'] = calendar_merge::get_documents($GLOBALS['egw_info']['user']['preferences']['calendar']['document_dir'], '', null,'calendar');
}
else
{
$readonlys['merge'] = true;
}
// Sidebox?
$sidebox->exec('calendar.calendar_ui.sidebox_etemplate', $content, $sel_options, $readonlys);
}
/**
* Prepare an array of event information for sending to the client
*
* This involves changing timestamps into strings with timezone
*
* @param type $event
*/
protected function to_client(&$event)
{
if (!$this->bo->check_perms(EGW_ACL_EDIT,$event))
{
$event['class'] .= 'rowNoEdit ';
}
// Delete disabled for other applications
if (!$this->bo->check_perms(EGW_ACL_DELETE,$event) || !is_numeric($event['id']))
{
$event['class'] .= 'rowNoDelete ';
}
// mark deleted events
if ($event['deleted'])
{
$event['class'] .= 'rowDeleted ';
}
$event['recure'] = $this->bo->recure2string($event);
if (empty($event['description'])) $event['description'] = ' '; // no description screws the titles horz. alignment
if (empty($event['location'])) $event['location'] = ' '; // no location screws the owner horz. alignment
// respect category permissions
if(!empty($event['category']))
{
$event['category'] = $this->categories->check_list(EGW_ACL_READ, $event['category']);
}
if(!(int)$event['id'] && preg_match('/^([a-z_-]+)([0-9]+)$/i',$event['id'],$matches))
{
$app = $matches[1];
$app_id = $matches[2];
$icons = array();
if (($is_private = calendar_bo::integration_get_private($app,$app_id,$event)))
{
$icons[] = html::image('calendar','private');
}
else
{
$icons = calendar_uiviews::integration_get_icons($app,$app_id,$event);
}
}
else
{
$is_private = !$this->bo->check_perms(EGW_ACL_READ,$event);
}
if ($is_private)
{
$event['is_private'] = true;
$event['class'] .= 'rowNoView ';
}
$event['app'] = 'calendar';
$event['app_id'] = $event['id'];
if ($event['recur_type'] != MCAL_RECUR_NONE)
{
$event['app_id'] .= ':'.$event['recur_date'];
}
// Change dates
foreach(calendar_egw_record::$types['date-time'] as $field)
{
if(is_int($event[$field]))
{
$event[$field] = egw_time::to($event[$field],'Y-m-d\TH:i:s').'Z';
}
}
}
public function merge($timespan = array())
{
// Merge print

View File

@ -1033,7 +1033,7 @@ class calendar_uiforms extends calendar_ui
function _create_exception(&$event,&$preserv)
{
// In some cases where the user makes the first day an exception, actual_date may be missing
$preserv['actual_date'] = $preserv['acutal_date'] ? $preserv['actual_date'] : $event['start'];
$preserv['actual_date'] = $preserv['actual_date'] ? $preserv['actual_date'] : $event['start'];
$event['end'] += $preserv['actual_date'] - $event['start'];
$event['reference'] = $preserv['reference'] = $event['id'];
@ -2492,7 +2492,7 @@ class calendar_uiforms extends calendar_ui
{
return false;
}
list($eventId, $date) = explode(':',$eventId);
$old_event=$event=$this->bo->read($eventId);
if (!$durationT)
{
@ -2503,6 +2503,25 @@ class calendar_uiforms extends calendar_ui
$duration = $durationT;
}
// If we have a recuring event for a particular day, make an exception
if ($event['recur_type'] != MCAL_RECUR_NONE && $date)
{
$date = new egw_time($date, egw_time::$user_timezone);
if (!empty($event['whole_day']))
{
$date =& $this->bo->so->startOfDay($date);
$date->setUser();
}
error_log("Loading event for " . $date);
$event = $this->bo->read($eventId, $date, true);
$preserv['actual_date'] = $date; // remember the date clicked
// For DnD, always create an exception
$this->_create_exception($event,$preserv);
unset($event['id']);
$date = $date->format('ts');
}
$event['start'] = $this->bo->date2ts($targetDateTime);
$event['end'] = $event['start']+$duration;
$status_reset_to_unknown = false;
@ -2533,9 +2552,10 @@ class calendar_uiforms extends calendar_ui
$response = egw_json_response::get();
if(!is_array($conflicts))
{
$response->redirect(egw::link('/index.php',array(
'menuaction' => $this->view_menuaction,
)));
// Directly update stored data. If event is still visible, it will
// be notified & update itself.
$this->to_client($event);
$response->call('egw.dataStoreUID','calendar::'.$event['id'].($date?':'.$date:''),$event);
}
else
{

View File

@ -308,8 +308,8 @@ class calendar_uilist extends calendar_ui
}
}
$search_params = array(
'cat_id' => $this->cat_id ? explode(',',$this->cat_id) : 0,
'filter' => $this->filter,
'cat_id' => $params['cat_id'] ? explode(',',$params['cat_id']) : 0,
'filter' => isset($params['filter']) ? $params['filter'] : $this->filter,
'query' => $params['search'],
'offset' => (int) $params['start'],
'num_rows'=> $params['num_rows'],
@ -325,7 +325,7 @@ class calendar_uilist extends calendar_ui
$label = lang('Before %1',$this->bo->long_date($this->date));
break;
case 'custom':
$this->first = $search_params['start'] = $params['startdate'];
$this->first = $search_params['start'] = egw_time::to($params['startdate'],'ts');
$this->last = $search_params['end'] = strtotime('+1 day', $this->bo->date2ts($params['enddate']))-1;
$label = $this->bo->long_date($this->first,$this->last);
break;
@ -361,46 +361,25 @@ class calendar_uilist extends calendar_ui
// fall through to after given date
case 'after':
default:
$this->date = $params['startdate'] ? egw_time::to($params['startdate'],'ts') : $this->date;
$label = lang('After %1',$this->bo->long_date($this->date));
$search_params['start'] = $this->date;
break;
}
if ((int) $params['col_filter']['participant'])
if ($params['col_filter']['participant'])
{
$search_params['users'] = (int) $params['col_filter']['participant'];
$search_params['users'] = is_array($params['col_filter']['participant']) ? $params['col_filter']['participant'] : (int) $params['col_filter']['participant'];
}
elseif(empty($params['search'])) // active search displays entries from all users
{
$search_params['users'] = explode(',',$this->owner);
}
$rows = $js_integration_data = array();
if ($label)
{
$GLOBALS['egw_info']['flags']['app_header'] .= ': '.$label;
// Add it in as specific option, or it will be cleared
$rows['sel_options']['filter'] = $this->date_filters;
$rows['sel_options']['filter'][$params['filter']] = $label;
}
foreach((array) $this->bo->search($search_params) as $event)
{
if (!$this->bo->check_perms(EGW_ACL_EDIT,$event))
{
$event['class'] .= 'rowNoEdit ';
}
// Delete disabled for other applications
if (!$this->bo->check_perms(EGW_ACL_DELETE,$event) || !is_numeric($event['id']))
{
$event['class'] .= 'rowNoDelete ';
}
// mark deleted events
if ($event['deleted'])
{
$event['class'] .= 'rowDeleted ';
}
$this->to_client($event);
$event['recure'] = $this->bo->recure2string($event);
if ($params['csv_export'])
{
$event['participants'] = implode(",\n",$this->bo->participants($event,true));
@ -410,14 +389,7 @@ class calendar_uilist extends calendar_ui
$event['parts'] = implode(",\n",$this->bo->participants($event,true));
$event['date'] = $this->bo->date2string($event['start']);
}
if (empty($event['description'])) $event['description'] = ' '; // no description screws the titles horz. alignment
if (empty($event['location'])) $event['location'] = ' '; // no location screws the owner horz. alignment
// respect category permissions
if(!empty($event['category']))
{
$event['category'] = $this->categories->check_list(EGW_ACL_READ, $event['category']);
}
if(!(int)$event['id'] && preg_match('/^([a-z_-]+)([0-9]+)$/i',$event['id'],$matches))
{

View File

@ -216,14 +216,44 @@ class calendar_uiviews extends calendar_ui
if (!$this->view) $this->view = 'week';
// handle views in other files
if (!isset($this->public_functions[$this->view]))
if (!isset($this->public_functions[$this->view]) && $this->view !== 'listview')
{
$GLOBALS['egw']->redirect_link('/index.php',array('menuaction'=>$this->view_menuaction,'ajax'=>'true'),'calendar');
}
// get manual to load the right page
$GLOBALS['egw_info']['flags']['params']['manual'] = array('page' => 'ManualCalendar'.ucfirst($this->view));
$this->{$this->view}();
// Sidebox & iframe for old views
if(in_array($this->view,array('year','planner')))
{
$old_calendar = $this->{$this->view}();
}
$this->sidebox_etemplate(array('old_calendar' => $old_calendar));
// Load the different views once, we'll switch between them on the client side
$tmpl = new etemplate_new('calendar.todo');
$label = '';
$tmpl->exec('calendar_uiviews::index',array('todos'=>$this->get_todos($label), 'label' => $label));
// Actually, this takes care of most of it...
$this->week();
// List view in a separate file
$list_ui = new calendar_uilist();
$list_ui->listview();
// Set the current state
if (egw_json_request::isJSONRequest())// && strpos($_GET['menuaction'], 'calendar_uiforms') === false)
{
$states = array();
foreach(array('date','cat_id','filter','owner','view') as $state)
{
if($this->$state) $states[$state] = $this->$state;
}
$states['date'] = egw_time::to($states['date'],'Y-m-d\TH:i:s').'Z';
$response = egw_json_response::get();
$response->apply('app.calendar.set_state', array($states));
}
}
/**
@ -801,9 +831,6 @@ class calendar_uiviews extends calendar_ui
*/
function week($days=0,$home=false)
{
$this->use_time_grid = $days != 4 && !in_array($this->use_time_grid_pref,array('day','day4')) ||
$days == 4 && $this->use_time_grid_pref != 'day';
if (!$days)
{
$days = isset($_GET['days']) ? $_GET['days'] : $this->cal_prefs['days_in_weekview'];
@ -822,7 +849,6 @@ class calendar_uiviews extends calendar_ui
$wd_start = $this->first = $this->bo->date2ts($this->date);
$this->last = strtotime("+$days days",$this->first) - 1;
$GLOBALS['egw_info']['flags']['app_header'] .= ': '.lang('Four days view').' '.$this->bo->long_date($this->first,$this->last);
$navHeader =lang('Four days view').' '.$this->bo->long_date($this->first,$this->last);
}
else
{
@ -841,27 +867,6 @@ class calendar_uiviews extends calendar_ui
}
$this->last = strtotime("+$days days",$this->first) - 1;
$GLOBALS['egw_info']['flags']['app_header'] .=': ' .lang('Week').' '.$this->week_number($this->first).': '.$this->bo->long_date($this->first,$this->last);
$navHeader = lang('Week').' '.$this->week_number($this->first).': '.$this->bo->long_date($this->first,$this->last);
}
$navHeader = '<div class="calendar_calWeek calendar_calWeekNavHeader">'
.html::a_href(html::image('phpgwapi','left',lang('previous'),$options=' alt="<<"'),array(
'menuaction' => $this->view_menuaction,
'date' => date('Ymd', strtotime("-$days days",$this->first)),
)). '<span>'.$navHeader;
$navHeader = $navHeader.'</span>'.html::a_href(html::image('phpgwapi','right',lang('next'),$options=' alt=">>"'),array(
'menuaction' => $this->view_menuaction,
'date' => date('Ymd', strtotime("+$days days",$this->last)),
)).'</div>';
$merge = $this->merge();
if($merge)
{
egw::redirect_link('/index.php',array(
'menuaction' => 'calendar.calendar_uiviews.index',
'msg' => $merge,
));
}
$search_params = array(
@ -872,45 +877,10 @@ class calendar_uiviews extends calendar_ui
$users = $this->search_params['users'];
if (!is_array($users)) $users = array($users);
if (count($users) == 1 || count($users) > $this->bo->calview_no_consolidate) // for more then X users, show all in one row
{
$content = $this->timeGridWidget($this->tagWholeDayOnTop($this->bo->search($search_params)),$this->cal_prefs['interval']);
}
else
{
$content = '';
$headerCounter = 0;
foreach($this->_get_planner_users(false) as $uid => $label)
{
$content .= '<div data-sortable-id="'.$uid.'">';
$search_params['users'] = $uid;
$content .= '<b>'.$label."</b>\n";
$content .= $this->close_button($uid);
$content .= $this->timeGridWidget($this->tagWholeDayOnTop($this->bo->search($search_params)),
count($users) * $this->cal_prefs['interval'],400 / count($users),'','',$uid);
++$headerCounter;
if (count($users) > $headerCounter)
{
$content .= $navHeader;
}
$content .= '</div>';
}
$content = '<div class="cal_is_sortable">'.$content.'</div>';
}
$content = $navHeader.$content;
if (!$home)
{
$this->do_header();
echo $content;
}
$content = array('view' => array());
// Always do 7 days for a week so scrolling works properly
$this->last = $search_params['end'] = strtotime("+7 days",$this->first) - 1;
$this->last = ($days == 4 ? $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)) +
@ -925,10 +895,12 @@ class calendar_uiviews extends calendar_ui
+ 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 = $home ? $home :new etemplate_new('calendar.view');
$tmpl->setElementAttribute('view','show_weekend', $days == 7);
// Get the actions
$tmpl->setElementAttribute('view','actions',$this->get_actions());
$tmpl->exec(__METHOD__, $content);
}
@ -1054,6 +1026,26 @@ $tmpl->setElementAttribute('view','actions',$ui->get_actions());
}
}
/**
* Get todos via AJAX
*/
public static function ajax_get_todos($date, $owner)
{
$date = egw_time::to($date, 'array');
$ui = new calendar_uiviews();
$ui->year = $date['year'];
$ui->month = $date['month'];
$ui->day = $date['day'];
$ui->owner = (int)$owner;
$label = '';
$todos = $ui->get_todos($label);
egw_json_response::get()->data(array(
'label' => $label,
'todos' => $todos
));
}
/**
* Query the open ToDo's via a hook from InfoLog or any other 'calendar_include_todos' provider
*
@ -2856,6 +2848,54 @@ $tmpl->setElementAttribute('view','actions',$ui->get_actions());
'" data-date ="'.$this->bo->date2string($event['start']).'|'.$data['popup'].'">'."\n".$data['html'].$indent."</div>\n";
}
protected static function get_actions()
{
// Just copy from the list, but change to match our needs
$ui = new calendar_uilist();
$actions = $ui->get_actions();
unset($actions['no_notifications']);
unset($actions['select_all']);
// This disables the event actions for the grid rows (calendar weeks/owners)
foreach($actions as $id => &$action)
{
if($id=='add') continue;
if(!$action['enabled'])
{
$action['enabled'] = 'javaScript:app.calendar.is_event';
}
//$action['disableClass'] = 'view_row';
//$action['hideOnDisabled'] = true;
}
foreach($actions['status']['children'] as $id => &$status)
{
$status = array(
'id' => $id,
'caption' => $status,
'onExecute' => 'javaScript:app.calendar.status'
);
}
/*
$actions['drag_calendar'] = array(
'dragType' => array('calendar'),
'type' => 'drag',
'enabled' => 'javaScript:app.calendar.is_event'
);
/*
Calendar DnD is handled internally
$actions['drop_calendar'] = array(
'acceptedTypes' => array('calendar'),
'type' => 'drop',
'onExecute' => 'javaScript:app.calendar.move'
);
*
*/
return $actions;
}
/**
* Marks whole day events for later usage and increments extraRows
*

File diff suppressed because it is too large Load Diff

View File

@ -69,6 +69,10 @@ var et2_calendar_daycol = et2_valueWidget.extend([et2_IDetachedDOM],
this.setDOMNode(this.div[0]);
// Used for its date calculations - note this is a datetime, parent
// uses just a date
this.date_helper = et2_createWidget('date-time',{},null);
this.date_helper.loadingFinished();
// Init to defaults, just in case
this.display_settings = {
@ -137,10 +141,6 @@ var et2_calendar_daycol = et2_valueWidget.extend([et2_IDetachedDOM],
.attr('data-hour',linkData.hour)
.attr('data-minute',linkData.minute)
.appendTo(this.div);
if(this.options.owner)
{
hour.attr('data-owner', this.options.owner);
}
}
},
@ -150,13 +150,19 @@ var et2_calendar_daycol = et2_valueWidget.extend([et2_IDetachedDOM],
* @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
* @param {boolean} force_redraw=false Redraw even if the date is the same.
* Used for when new data is available.
*/
set_date: function(_date, events)
set_date: function(_date, events, force_redraw)
{
if(typeof events === 'undefined' || !events)
{
events = false;
}
if(typeof force_redraw === 'undefined' || !force_redraw)
{
force_redraw = false;
}
if(!this._parent || !this._parent.date_helper)
{
egw.debug('warn', 'Day col widget "' + this.id + '" is missing its parent.');
@ -173,21 +179,23 @@ var et2_calendar_daycol = et2_valueWidget.extend([et2_IDetachedDOM],
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);
this.date = new Date(this._parent.date_helper.getValue());
// Keep internal option in Ymd format, it gets passed around in this format
var new_date = date('Ymd',this.date);
var new_date = ""+this._parent.date_helper.get_year()+
sprintf("%02d",this._parent.date_helper.get_month())+
sprintf("%02d",this._parent.date_helper.get_date());
// Set label
// Add timezone offset back in, or formatDate will lose those hours
var formatDate = new Date(this.date.valueOf() + this.date.getTimezoneOffset() * 60 * 1000);
var date_string = this._parent._children.length === 1 ?
this.long_date(this.date,false, false, true) :
jQuery.datepicker.formatDate('DD dd',this.date);
this.long_date(formatDate,false, false, true) :
jQuery.datepicker.formatDate('DD dd',formatDate);
this.title.text(date_string);
// Avoid redrawing if date is the same
if(new_date === this.options.date)
if(new_date === this.options.date && !force_redraw)
{
return;
}
@ -320,12 +328,13 @@ var et2_calendar_daycol = et2_valueWidget.extend([et2_IDetachedDOM],
}
// Create event
var event = et2_createWidget('calendar-event',{},this);
var event = et2_createWidget('calendar-event',{id:columns[c][i].app_id||columns[c][i].id},this);
if(this.isInTree())
{
event.doLoadingFinished();
}
event.set_value(columns[c][i]);
event._link_actions(this._parent._parent.options.actions||{});
// Position the event
event.div.css('top', top+'%');
@ -347,7 +356,7 @@ var et2_calendar_daycol = et2_valueWidget.extend([et2_IDetachedDOM],
{
var day_start = this.date.valueOf() / 1000;
var dst_check = new Date(this.date);
dst_check.setHours(12);
dst_check.setUTCHours(12);
// if daylight saving is switched on or off, correct $day_start
// gives correct times after 2am, times between 0am and 2am are wrong
@ -363,18 +372,32 @@ var et2_calendar_daycol = et2_valueWidget.extend([et2_IDetachedDOM],
var event = events[i];
var c = 0;
event['multiday'] = false;
event['start_m'] = (event['start'] - day_start) / 60;
if(typeof event.start !== 'object')
{
this.date_helper.set_value(event.start);
event.start = new Date(this.date_helper.getValue());
}
if(typeof event.end !== 'object')
{
this.date_helper.set_value(event.end);
event.end = new Date(this.date_helper.getValue());
}
event['start_m'] = parseInt((event.start.valueOf()/1000 - day_start) / 60);
if (event['start_m'] < 0)
{
event['start_m'] = 0;
event['multiday'] = true;
}
event['end_m'] = (event['end'] - day_start) / 60;
event['end_m'] = parseInt((event.end.valueOf()/1000 - day_start) / 60);
if (event['end_m'] >= 24*60)
{
event['end_m'] = 24*60-1;
event['multiday'] = true;
}
if(!event.start.getUTCHours() && !event.start.getUTCMinutes() && event.end.getUTCHours() == 23 && event.end.getUTCMinutes() == 59)
{
event.whole_day_on_top = (event.non_blocking && event.non_blocking != '0');
}
if (!event['whole_day_on_top'])
{
for(c = 0; event['start_m'] < col_ends[c]; ++c);
@ -419,7 +442,7 @@ var et2_calendar_daycol = et2_valueWidget.extend([et2_IDetachedDOM],
// 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 = this.display_settings.rowHeight * (1+this.display_settings.extraRows+(time-this.display_settings.wd_start)/this.display_settings.granularity);
}
pos = pos.toFixed(1)

View File

@ -50,6 +50,9 @@ var et2_calendar_event = et2_valueWidget.extend([et2_IDetachedDOM],
this.body = $j(document.createElement('div'))
.addClass("calendar_calEventBody")
.appendTo(this.div);
this.icons = $j(document.createElement('div'))
.addClass("calendar_calEventIcons")
.appendTo(this.title);
this.setDOMNode(this.div[0]);
},
@ -70,41 +73,103 @@ var et2_calendar_event = et2_valueWidget.extend([et2_IDetachedDOM],
destroy: function() {
this._super.apply(this, arguments);
// Unregister, or we'll continue to be notified...
var old_app_id = this.options.value.app_id ? this.options.value.app_id : this.options.value.id + (this.options.value.recur_type ? ':'+this.options.value.recur_date : '');
egw.dataUnregisterUID('calendar::'+old_app_id,false,this);
},
set_value: function(_value) {
// Un-register for updates
if(this.options.value)
{
var old_app_id = this.options.value.app_id ? this.options.value.app_id : this.options.value.id + (this.options.value.recur_type ? ':'+this.options.value.recur_date : '');
egw.dataUnregisterUID('calendar::'+old_app_id,false,this);
}
this.options.value = _value;
this._update(this.options.value);
// Register for updates
var app_id = this.options.value.app_id ? this.options.value.app_id : this.options.value.id + (this.options.value.recur_type ? ':'+this.options.value.recur_date : '');
egw.dataRegisterUID('calendar::'+app_id, function(event) {
// Copy to avoid changes, which may cause nm problems
event = jQuery.extend({},event);
var list = [event];
// Let parent format any missing data
this._parent._event_columns(list);
// Calculate vertical positioning
// TODO: Maybe move this somewhere common between here & parent?
var top = 0;
var height = 0;
if(event.whole_day_on_top)
{
top = ((this._parent.title.height()/this._parent.div.height())*100) + this._parent.display_settings.rowHeight;
height = this._parent.display_settings.rowHeight;
}
else
{
top = this._parent._time_to_position(event.start_m,0);
height = this._parent._time_to_position(event.end_m,0)-top;
}
// Position the event - horizontal is controlled by parent
this.div.css('top', top+'%');
this.div.css('height', height+'%');
this._update(event);
},this,this.getInstanceManager().execId,this.id);
if(!egw.dataHasUID('calendar::'+app_id))
{
this._update(this.options.value);
}
},
_update: function(event) {
// Copy new information
this.options.value = 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 : '')
var app_id = event.app_id ? event.app_id : event.id + (event.recur_type ? ':'+event.recur_date : '');
this._parent.date_helper.set_value(event.start);
var formatted_start = this._parent.date_helper.getValue();
this.set_id(eventId || event.id);
this.div
// Empty & re-append to make sure dnd helpers are gone
.empty()
.append(this.title)
.append(this.body)
// ?
.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-start', formatted_start)
.attr('data-owner', event.owner)
.attr('data-recur_type', event.recur_type)
.attr('data-resize', event.whole_day ? 'WD' : '' + (event.recur_type ? 'S':''))
.addClass(event.class)
.toggleClass('calendar_calEventPrivate', event.private)
// Remove any category classes
.removeClass(function(index, css) {
return (css.match (/(^|\s)cat_\S+/g) || []).join(' ');
});
})
// Remove any resize classes, the handles are gone due to empty()
.removeClass('ui-resizable');
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.div.toggleClass('calendar_calEventUnknown', event.participants[egw.user('account_id')] ? event.participants[egw.user('account_id')][0] == 'U' : false);
this.title.toggle(!event.whole_day_on_top);
this.body.toggleClass('calendar_calEventBodySmall', event.whole_day_on_top || false);
@ -114,10 +179,14 @@ var et2_calendar_event = et2_valueWidget.extend([et2_IDetachedDOM],
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.div.attr('data-title', title);
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');
this.icons.appendTo(this.title)
.html(this._icons());
// Body
if(event.whole_day_on_top)
{
@ -131,6 +200,89 @@ var et2_calendar_event = et2_valueWidget.extend([et2_IDetachedDOM],
// Set background color to a lighter version of the header color
.css('background-color',jQuery.Color(this.div.css('background-color')).lightness("+=0.3"));
this.set_statustext(this._tooltip());
},
_tooltip: function() {
var status_class = 'calendar_calEventAllAccepted';
status:
for(var id in this.options.value.participants)
{
var status = this.options.value.participants[id];
if (parseInt(id) < 0) continue; // as we cant accept/reject groups, we dont care about them here
status = et2_calendar_event.split_status(status);
switch (status)
{
case 'A':
case '': // app without status
break;
case 'U':
status_class = 'calendar_calEventSomeUnknown';
break status; // break for
default:
status_class = 'calendar_calEventAllAnswered';
break;
}
}
var border = this.div.css('border-color');
var bg_color = this.div.css('background-color');
var header_color = this.title.css('color');
this._parent.date_helper.set_value(this.options.value.start);
var start = this._parent.date_helper.input_date.val();
this._parent.date_helper.set_value(this.options.value.end);
var end = this._parent.date_helper.input_date.val();
var times = !this.options.value.multiday ?
'<span class="calendar_calEventLabel">'+this.egw().lang('Time')+'</span>:' + this._get_timespan(this.options.value) :
'<span class="calendar_calEventLabel">'+this.egw().lang('Start') + '</span>:' +start+
'<span class="calendar_calEventLabel">'+this.egw().lang('End') + '</span>:' + end
var cat = et2_createWidget('select-cat',{'readonly':true},this);
cat.set_value(this.options.value.category);
var cat_label = cat.node.innerText;
cat.destroy();
return '<div class="calendar_calEventTooltip ' + status_class+ '" style="border-color: '+border+'; background: '+bg_color+';">'+
'<div class="calendar_calEventHeaderSmall" style="background-color: {bordercolor};">'+
'<font style="color:'+header_color+'">'+this._get_timespan(this.options.value)+'</font>'+
this.icons[0].outerHTML+
'</div>'+
'<div class="calendar_calEventBodySmall">'+
'<p style="margin: 0px;">'+
'<span class="calendar_calEventTitle">'+this.div.attr('data-title')+'</span><br>'+
this.options.value.description+'</p>'+
'<p style="margin: 2px 0px;">'+times+'</p>'+
(this.options.value.location ? '<p><span class="calendar_calEventLabel">'+this.egw().lang('Location') + '</span>:' + this.options.value.location+'</p>' : '')+
(cat_label ? '<p><span class="calendar_calEventLabel">'+this.egw().lang('Category') + '</span>:' + cat_label+'</p>' : '')+
'<p><span class="calendar_calEventLabel">'+this.egw().lang('Participants')+'</span>:<br />'+
(this.options.value.parts ? this.options.value.parts.replace("\n","<br />"):'')+'</p>'+
'</div>'+
'</div>';
},
/**
* Get actual icons from list
* @returns {undefined}
*/
_icons: function() {
var icons = [];
if(this.options.value.is_private)
{
icons.push('<img src="'+this.egw().image('private','calendar')+'"/>');
}
if(this.options.value.alarm && !jQuery.isEmptyObject(this.options.value.alarm) && !this.options.value.is_private)
{
icons.push('<img src="'+this.egw().image('alarm','calendar')+'" title="'+this.egw().lang('alarm')+'"/>');
}
if(this.options.value.participants[egw.user('account_id')] && this.options.value.participants[egw.user('account_id')][0] == 'U')
{
icons.push('<img src="'+this.egw().image('cnr-pending','calendar')+'" title="'+lang('Needs action')+'"/>');
}
return icons;
},
/**
@ -227,41 +379,49 @@ var et2_calendar_event = et2_valueWidget.extend([et2_IDetachedDOM],
}
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);
/**
* Show the recur prompt for this event
*
* @param {function} callback
*/
recur_prompt: function(callback)
{
et2_calendar_event.recur_prompt(this.options.value,callback);
},
/**
* Link the actions to the DOM nodes / widget bits.
*
* @param {object} actions {ID: {attributes..}+} map of egw action information
*/
_link_actions: function(actions)
{
// Get the top level element - timegrid or so
var objectManager = egw_getAppObjectManager(true).getObjectById(this._parent._parent._parent.id) || egw_getAppObjectManager(true);
var widget_object = objectManager.getObjectById('calendar::'+this.id);
if (widget_object == null) {
// Add a new container to the object manager which will hold the widget
// objects
widget_object = objectManager.insertObject(false, new egwActionObject(
'calendar::'+this.id, objectManager, new et2_event_action_object_impl(this,this.getDOMNode()),
objectManager.manager.getActionById(this.id) || objectManager.manager
));
}
else
{
this.egw().open(this.options.value.id, 'calendar','edit');
widget_object.setAOI(new et2_event_action_object_impl(this, this.getDOMNode()));
}
// Delete all old objects
widget_object.clear();
widget_object.unregisterActions();
// Go over the widget & add links - this is where we decide which actions are
// 'allowed' for this widget at this time
var action_links = this._get_action_links(actions);
this._parent._parent._init_links_dnd(widget_object.manager,action_links);
widget_object.updateActionLinks(action_links);
},
/**
@ -281,4 +441,122 @@ var et2_calendar_event = et2_valueWidget.extend([et2_IDetachedDOM],
},
});
et2_register_widget(et2_calendar_event, ["calendar-event"]);
et2_register_widget(et2_calendar_event, ["calendar-event"]);
// Static class stuff
/**
* Recur prompt
* If the event is recurring, asks the user if they want to edit the event as
* an exception, or change the whole series. Then the callback is called.
*
* @param {Object} event_data - Event information
* @param {string} event_data.id - Unique ID for the event, possibly with a timestamp
* @param {string|Date} event_data.start - Start date/time for the event
* @param {number} event_data.recur_type - Recur type, or 0 for a non-recurring event
* @param {Function} [callback] - Callback is called with the button (exception, series, single or cancel) and the event data.
*
* @augments {et2_calendar_event}
*/
et2_calendar_event.recur_prompt = function(event_data, callback)
{
var edit_id = event_data.id;
var edit_date = event_data.start;
var egw = this.egw ? (typeof this.egw == 'function' ? this.egw() : this.egw) : (window.opener || window).egw;
var that = this;
if(typeof callback != 'function')
{
callback = function(_button_id)
{
switch(_button_id)
{
case 'exception':
egw.open(edit_id, event_data.app||'calendar', 'edit', {date:edit_date,exception: '1'});
break;
case 'series':
case 'single':
egw.open(edit_id, event_data.app||'calendar', 'edit', {date:edit_date});
break;
case 'cancel':
default:
break;
}
};
}
if(event_data.recur_type)
{
var buttons = [
{text: egw.lang("Edit exception"), id: "exception", class: "ui-priority-primary", "default": true},
{text: egw.lang("Edit series"), id:"series"},
{text: egw.lang("Cancel"), id:"cancel"}
];
et2_dialog.show_dialog(
function(button_id) {callback.call(that, button_id, event_data);},
(!event_data.is_private ? event_data['title'] : egw.lang('private')) + "\n" +
egw.lang("Do you want to edit this event as an exception or the whole series?"),
egw.lang("This event is part of a series"), {}, buttons, et2_dialog.QUESTION_MESSAGE
);
}
else
{
callback.call(this,'single',event_data);
}
};
et2_calendar_event.drag_helper = function(event,ui) {
debugger;
ui.helper.width(ui.width());
};
/**
* splits the combined status, quantity and role
*
* @param {string} status - combined value, O: status letter: U, T, A, R
* @param {int} [quantity] - quantity
* @param {string} [role]
* @return string status U, T, A or R, same as $status parameter on return
*/
et2_calendar_event.split_status = function(status,quantity,role)
{
quantity = 1;
role = 'REQ-PARTICIPANT';
//error_log(__METHOD__.__LINE__.array2string($status));
var matches = null;
if (typeof status === 'string' && status.length > 1)
{
matches = status.match(/^.([0-9]*)(.*)$/gi);
}
if(matches)
{
if (parseInt(matches[1]) > 0) quantity = parseInt(matches[1]);
if (matches[2]) role = matches[2];
status = status[0];
}
else if (status === true)
{
status = 'U';
}
return status;
}
/**
* The egw_action system requires an egwActionObjectInterface Interface implementation
* to tie actions to DOM nodes. This one can be used by any widget.
*
* The class extension is different than the widgets
*
* @param {et2_DOMWidget} widget
* @param {Object} node
*
*/
function et2_event_action_object_impl(widget, node)
{
var aoi = new et2_action_object_impl(widget, node);
// _outerCall may be used to determine, whether the state change has been
// evoked from the outside and the stateChangeCallback has to be called
// or not.
aoi.doSetState = function(_state, _outerCall) {
};
return aoi;
};

View File

@ -78,6 +78,18 @@ var et2_calendar_timegrid = et2_valueWidget.extend([et2_IDetachedDOM, et2_IResiz
description: "Account ID number of the calendar owner, if not the current user"
},
"onchange": {
"name": "onchange",
"type": "js",
"default": et2_no_init,
"description": "JS code which is executed when the date range changes."
},
"onevent_change": {
"name": "onevent_change",
"type": "js",
"default": et2_no_init,
"description": "JS code which is executed when an event changes."
},
height: {
"default": '100%'
}
@ -128,24 +140,210 @@ var et2_calendar_timegrid = et2_valueWidget.extend([et2_IDetachedDOM, et2_IResiz
// date_helper has no parent, so we must explicitly remove it
this.date_helper.destroy();
this.date_helper = null;
// Stop the invalidate timer
if(this.update_timer)
{
window.clearTimeout(this.update_timer);
}
},
doLoadingFinished: function() {
this._super.apply(this, arguments);
this._drawGrid();
// Actions may be set on a parent, so we need to explicitly get in here
// and get ours
this._link_actions(this.options.actions || this._parent.options.actions || []);
// Automatically bind drag and resize for every event using jQuery directly
// - no action system -
var timegrid = this;
// Show the current time while dragging
var drag_helper = function(event, element,height)
{
this.dropEnd = timegrid._get_time_from_position(element.getBoundingClientRect().left,
element.getBoundingClientRect().top+parseInt(height));
if (typeof this.dropEnd != 'undefined' && this.dropEnd.length)
{
this.dropEnd.addClass("drop-hover");
var time = jQuery.datepicker.formatTime(
egw.preference("timeformat") == 12 ? "h:mmtt" : "HH:mm",
{
hour: this.dropEnd.attr('data-hour'),
minute: this.dropEnd.attr('data-minute'),
seconds: 0,
timezone: 0
},
{"ampm": (egw.preference("timeformat") == "12")}
);
this.innerHTML = '<div style="font-size: 1.1em; text-align:center; font-weight: bold; height:100%;"><span class="calendar_timeDemo" >'+time+'</span></div>';
}
else
{
this.innerHTML = '<div class="calendar_d-n-d_forbiden"></div>';
}
return this.dropEnd;
};
this.div.on('mouseover', '.calendar_calEvent:not(.ui-resizable):not(.rowNoEdit)', function() {
// Load the event
timegrid._get_event_info(this);
var that = this;
//Resizable event handler
$j(this).resizable
({
distance: 10,
grid: [10000,timegrid.rowHeight],
autoHide: true,
handles: 's,se',
containment:'parent',
/**
* Triggered when the resizable is created.
*
* @param {event} event
* @param {Object} ui
*/
create:function(event, ui)
{
var resizeHelper = event.target.getAttribute('data-resize');
if (resizeHelper == 'WD' || resizeHelper == 'WDS')
{
jQuery(this).resizable('destroy');
}
},
/**
* Triggered at start of resizing a calEvent
*
* @param {event} event
* @param {Object} ui
*/
start:function(event, ui)
{
this.dropStart = timegrid._get_time_from_position(ui.element[0].getBoundingClientRect().left,ui.element[0].getBoundingClientRect().top).last();
this.dropDate = timegrid._get_event_info(this).start;
},
/**
* Triggered at the end of resizing the calEvent.
*
* @param {event} event
* @param {Object} ui
*/
stop:function(event, ui)
{
var e = new jQuery.Event('change');
e.originalEvent = event;
e.data = {duration: 0};
var event_data = timegrid._get_event_info(this);
var event_widget = timegrid.getWidgetById(event_data.id);
var sT = parseInt(this.dropStart.attr('data-hour'))* 60 + parseInt(this.dropStart.attr('data-minute'));
if (typeof this.dropEnd != 'undefined' && this.dropEnd.length == 1)
{
var eT = parseInt(this.dropEnd.attr('data-hour') * 60) + parseInt(this.dropEnd.attr('data-minute'));
e.data.duration = ((eT - sT)/60) * 3600;
if(event_widget)
{
event_widget.options.value.duration = e.data.duration;
}
$j(this).trigger(e);
// That cleared the resize handles, so remove for re-creation...
$j(this).resizable('destroy');
}
// Clear the helper, re-draw
event_widget.set_value(event_widget.options.value);
},
/**
* Triggered during the resize, on the drag of the resize handler
*
* @param {event} event
* @param {Object} ui
*/
resize:function(event, ui)
{
// Add 5px to make sure it doesn't land right on the edge of a div
drag_helper.call(this,event,ui.element[0],ui.helper.outerHeight()+5);
}
});
});
// Customize and override some draggable settings
this.div.on('dragcreate','.calendar_calEvent:not(.rowNoEdit)', function(event,ui) {
$j(this).draggable('option','cursorAt',false);
})
.on('dragstart', '.calendar_calEvent:not(.rowNoEdit)', function(event,ui) {
$j('.calendar_calEvent',ui.helper).width($j(this).width())
.height($j(this).outerHeight())
.appendTo(ui.helper);
})
.on('dragstop','.calendar_calEvent:not(.rowNoEdit)', function(event,ui) {
var e = new jQuery.Event('change');
e.originalEvent = event;
e.data = {start: 0};
if (typeof this.dropEnd != 'undefined' && this.dropEnd.length >= 1)
{
var drop_date = this.dropEnd.attr('data-date')||false;
var eT = parseInt(this.dropEnd.attr('data-hour') * 60) + parseInt(this.dropEnd.attr('data-minute'));
var event_data = timegrid._get_event_info(this);
var event_widget = timegrid.getWidgetById(event_data.id);
if(event_widget)
{
event_widget._parent.date_helper.set_year(drop_date.substring(0,4));
event_widget._parent.date_helper.set_month(drop_date.substring(4,6));
event_widget._parent.date_helper.set_date(drop_date.substring(6,8));
event_widget._parent.date_helper.set_hours(this.dropEnd.attr('data-hour'));
event_widget._parent.date_helper.set_minutes(this.dropEnd.attr('data-minute'));
event_widget.options.value.start = event_widget._parent.date_helper.getValue();
event_widget.recur_prompt(function(button_id) {
//Get infologID if in case if it's an integrated infolog event
if (event_data.app === 'infolog')
{
// If it is an integrated infolog event we need to edit infolog entry
egw().json('stylite_infolog_calendar_integration::ajax_moveInfologEvent', [event_data.id, event_widget.options.value.start||false]).sendRequest();
}
else
{
//Edit calendar event
egw().json('calendar.calendar_uiforms.ajax_moveEvent',[button_id=='series' ? event_data.id : event_data.app_id,event_data.owner, event_widget.options.value.start, timegrid.options.owner||egw.user('account_id')]).sendRequest();
}
});
}
}
})
// As event is dragged, update the time
.on('drag', '.calendar_calEvent:not(.rowNoEdit)', function(event,ui) {
this.dropEnd = drag_helper.call($j('.calendar_calEventHeader',ui.helper)[0],event,ui.helper[0],0);
$j('.calendar_timeDemo',ui.helper).css('bottom','auto');
});
// 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);
this.date_helper.set_value(this.options.end_date || this.options.start_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));
var delta = 1000 * 60 * 60 * 24 + Math.max(0,end - start);
// TODO - actually fetch new data
this.set_start_date(new Date(start + (delta * direction )));
@ -153,15 +351,6 @@ var et2_calendar_timegrid = et2_valueWidget.extend([et2_IDetachedDOM, et2_IResiz
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;
@ -173,10 +362,12 @@ var et2_calendar_timegrid = et2_valueWidget.extend([et2_IDetachedDOM, et2_IResiz
* the days.
* The whole grid is not regenerated because times aren't expected to change,
* just the days.
*
*
* @param {boolean} trigger=false Trigger an event once things are done.
* Waiting until invalidate completes prevents 2 updates when changing the date range.
* @returns {undefined}
*/
invalidate: function() {
invalidate: function(trigger) {
// Reset the list of days
this.day_list = [];
@ -185,12 +376,49 @@ var et2_calendar_timegrid = et2_valueWidget.extend([et2_IDetachedDOM, et2_IResiz
if(this.update_timer === null)
{
this.update_timer = window.setTimeout(jQuery.proxy(function() {
this.update_timer = null;
this._drawDays();
},this),ET2_GRID_INVALIDATE_TIMEOUT);
this.widget.update_timer = null;
// Update actions
if(this._actionManager)
{
this._link_actions(this._actionManager.children);
}
this.widget._drawDays();
if(this.trigger)
{
this.widget.change();
}
},{widget:this,"trigger":trigger}),ET2_GRID_INVALIDATE_TIMEOUT);
}
},
detachFromDOM: function() {
// Remove the binding to the change handler
$j(this.div).off("change.et2_calendar_timegrid");
this._super.apply(this, arguments);
},
attachToDOM: function() {
this._super.apply(this, arguments);
// Add the binding for the event change handler
$j(this.div).on("change.et2_calendar_timegrid", '.calendar_calEvent', this, function(e) {
// Make sure function gets a reference to the widget
var args = Array.prototype.slice.call(arguments);
if(args.indexOf(this) == -1) args.push(this);
return e.data.event_change.apply(e.data, args);
});
// Add the binding for the change handler
$j(this.div).on("change.et2_calendar_timegrid", '*:not(.calendar_calEvent)', this, function(e) {
return e.data.change.call(e.data, e, this);
});
},
getDOMNode: function(_sender) {
if(_sender === this || !_sender)
{
@ -216,7 +444,7 @@ var et2_calendar_timegrid = et2_valueWidget.extend([et2_IDetachedDOM, et2_IResiz
// Draw in the vertical - the days
this.div.append(this.days);
this._drawDays();
this.invalidate();
},
/**
@ -229,7 +457,8 @@ var et2_calendar_timegrid = et2_valueWidget.extend([et2_IDetachedDOM, et2_IResiz
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);
var rowHeight = (100/rowsToDisplay).toFixed(1);
this.rowHeight = this.div.height() / rowsToDisplay;
// ensure a minimum height of each row
if (this.options.height < (rowsToDisplay+1) * 12)
@ -266,7 +495,7 @@ var et2_calendar_timegrid = et2_valueWidget.extend([et2_IDetachedDOM, et2_IResiz
);
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";
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);
},
@ -311,22 +540,28 @@ var et2_calendar_timegrid = et2_valueWidget.extend([et2_IDetachedDOM, et2_IResiz
}
// Create / update day widgets with dates and data, if available
// TODO: need data doesn't take category & other filters into account
var need_data = true;
for(var i = 0; i < this.day_list.length; i++)
{
day = this.day_widgets[i];
// Set the date, and pass any data we have
if(typeof this.value[this.day_list[i]] === 'undefined') need_data = true;
if(day.options.owner != this.options.owner) need_data = true;
day.set_date(this.day_list[i], this.value[this.day_list[i]] || false);
day.set_owner(this.options.owner);
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)
// Fetch any needed data
if(need_data)
{
this._link_actions(this._actionManager.children);
this._fetch_data();
}
// TODO: Figure out how to do this with detached nodes
@ -365,7 +600,7 @@ var et2_calendar_timegrid = et2_valueWidget.extend([et2_IDetachedDOM, et2_IResiz
do
{
if(show_weekend || !show_weekend && [0,6].indexOf(this.date_helper.date.getUTCDay()) === -1)
if(show_weekend || !show_weekend && [0,6].indexOf(this.date_helper.date.getUTCDay()) === -1 || end_date == start_date)
{
day_list.push(''+this.date_helper.get_year() + sprintf('%02d',this.date_helper.get_month()) + sprintf('%02d',this.date_helper.get_date()));
}
@ -385,45 +620,313 @@ var et2_calendar_timegrid = et2_valueWidget.extend([et2_IDetachedDOM, et2_IResiz
*/
_link_actions: function(actions)
{
this._super.apply(this, arguments);
// Get the top level element for the tree
// Get the parent? Might be a grid row, might not. Either way, it is
// just a container with no valid actions
var objectManager = egw_getAppObjectManager(true);
var widget_object = objectManager.getObjectById(this.id);
var parent = objectManager.getObjectById(this._parent.id);
if(!parent) return;
for(var i = 0; i < parent.children.length; i++)
{
var parent_finder = jQuery(this.div, parent.children[i].iface.doGetDOMNode());
if(parent_finder.length > 0)
{
parent = parent.children[i];
break;
}
}
return;
// Ug.
// This binds into the egw action system. Most user interactions (drag to move, resize)
// are handled internally using jQuery directly.
var widget_object = parent.getObjectById(this.id);
var aoi = new et2_action_object_impl(this,this.getDOMNode());
aoi.doTriggerEvent = function(_event, _data) {
// Time grid is just a container
widget_object.flags = EGW_AO_FLAG_IS_CONTAINER;
},
// Determine target node
var event = _data.event || false;
if(!event) return;
var nodes = $j('.calendar_calAddEvent[data-hour]',this.doGetDOMNode()).filter(function() {
var offset = $j(this).offset();
var range={x:[offset.left,offset.left+$j(this).outerWidth()],y:[offset.top,offset.top+$j(this).outerHeight()]};
return(event.pageX >=range.x[0] && event.pageX <= range.x[1]) && (event.pageY >= range.y[0] && event.pageY <= range.y[1]);
});
/**
* Bind a single event as needed to the action system.
*
* @param {Object} event
*/
_link_event: function(event)
{
if(!event || !event.app_id) return;
switch(_event)
{
case EGW_AI_DRAG_OVER:
// Highlight target time, and display time in helper
if(nodes.length)
{
// Highlight the destination time
$j('[data-date]',this.doGetDOMNode()).removeClass("ui-state-active");
nodes.addClass('ui-state-active');
// Update the helper with the actual time
var time = jQuery.datepicker.formatTime(
egw.preference("timeformat") == 12 ? "h:mmtt" : "HH:mm",
{
hour: nodes.attr('data-hour'),
minute: nodes.attr('data-minute'),
seconds: 0,
timezone: 0
},
{"ampm": (egw.preference("timeformat") == "12")}
);
_data.ui.helper[0].innerHTML = '<div class="calendar_d-n-d_timeCounter"><span>'+time+'</span></div>';
if(_data.ui.draggable)
{
_data.ui.draggable
.off('.et2_timegrid')
.on('drag.et2_timegrid',jQuery.proxy(function(event, ui) {this.doTriggerEvent(EGW_AI_DRAG_OVER,{event:event,ui:ui});},this))
_data.ui.helper.css('width', _data.ui.draggable.width()+'px')
.css('height', _data.ui.draggable.height()+'px');
}
}
break;
case EGW_AI_DRAG_OUT:
// Reset
$j('[data-date]',this.doGetDOMNode()).removeClass("ui-state-active");
_data.ui.draggable.off('.et2_timegrid');
$j('.calendar_d-n-d_timeCounter',_data.ui.helper[0]).remove();
break;
}
};
if (widget_object == null) {
// Add a new container to the object manager which will hold the widget
// objects
widget_object = parent.insertObject(false, new egwActionObject(
this.id, parent, aoi,
parent.manager.getActionById(this.id) || parent.manager
));
}
else
{
widget_object.setAOI(aoi);
}
// Delete all old objects
widget_object.clear();
widget_object.unregisterActions();
// 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)
var action_links = this._get_action_links(actions);
this._init_links_dnd(widget_object.manager, action_links);
widget_object.updateActionLinks(action_links);
},
/**
* Automatically add dnd support for linking
*/
_init_links_dnd: function(mgr,actionLinks) {
var self = this;
var drop_action = mgr.getActionById('egw_link_drop');
var drag_action = mgr.getActionById('egw_link_drag');
// Check if this app supports linking
if(!egw.link_get_registry(this.dataStorePrefix || this.egw().appName, 'query') ||
egw.link_get_registry(this.dataStorePrefix || this.egw().appName, 'title'))
{
// No actions set up
if(drop_action)
{
drop_action.remove();
if(actionLinks.indexOf(drop_action.id) >= 0)
{
actionLinks.splice(actionLinks.indexOf(drop_action.id),1);
}
}
if(drag_action)
{
drag_action.remove();
if(actionLinks.indexOf(drag_action.id) >= 0)
{
actionLinks.splice(actionLinks.indexOf(drag_action.id),1);
}
}
return;
}
var obj = null;
debugger;
if(!(obj = objectManager.getObjectById(event.app_id)))
// Don't re-add
if(drop_action == null)
{
obj = objectManager.addObject(event.app_id, new et2_action_object_impl(this,event.event_node));
obj.data = event;
obj.updateActionLinks(objectManager.actionLinks)
// Create the drop action that links entries
drop_action = mgr.addAction('drop', 'egw_link_drop', egw.lang('Create link'), egw.image('link'), function(action, source, dropped) {
// Extract link IDs
var links = [];
var id = '';
for(var i = 0; i < source.length; i++)
{
if(!source[i].id) continue;
id = source[i].id.split('::');
links.push({app: id[0] == 'filemanager' ? 'link' : id[0], id: id[1]});
}
if(!links.length)
{
return;
}
// Link the entries
egw.json(self.egw().getAppName()+".etemplate_widget_link.ajax_link.etemplate",
dropped.id.split('::').concat([links]),
function(result) {
if(result)
{
this.egw().message('Linked');
}
},
self,
true,
self
).sendRequest();
},true);
}
objectManager.setAllSelected(false);
obj.setSelected(true);
objectManager.updateSelectedChildren(obj,true)
if(actionLinks.indexOf(drop_action.id) < 0)
{
actionLinks.push(drop_action.id);
}
// Accept other links, and files dragged from the filemanager
// This does not handle files dragged from the desktop. They are
// handled by et2_nextmatch, since it needs DOM stuff
if(drop_action.acceptedTypes.indexOf('link') == -1)
{
drop_action.acceptedTypes.push('link');
}
// Don't re-add
if(drag_action == null)
{
// Create drag action that allows linking
drag_action = mgr.addAction('drag', 'egw_link_drag', egw.lang('link'), 'link', function(action, selected) {
// Drag helper - list titles. Arbitrarily limited to 10.
var helper = $j(document.createElement("div"));
for(var i = 0; i < selected.length && i < 10; i++)
{
var id = selected[i].id.split('::');
var span = $j(document.createElement('span')).appendTo(helper);
egw.link_title(id[0],id[1], function(title) {
this.append(title);
this.append('<br />');
}, span);
}
// As we wanted to have a general defaul helper interface, we return null here and not using customize helper for links
// TODO: Need to decide if we need to create a customized helper interface for links anyway
//return helper;
return null;
},true);
}
if(actionLinks.indexOf(drag_action.id) < 0)
{
actionLinks.push(drag_action.id);
}
drag_action.set_dragType('link');
},
/**
* Get all action-links / id's of 1.-level actions from a given action object
*
* Here we are only interested in drop events.
*
* @param actions
* @returns {Array}
*/
_get_action_links: function(actions)
{
var action_links = [];
// TODO: determine which actions are allowed without an action (empty actions)
for(var i in actions)
{
var action = actions[i];
if(action.type == 'drop')
{
action_links.push(typeof action.id != 'undefined' ? action.id : i);
}
}
return action_links;
},
/**
* Use the egw.data system to get data from the calendar list for the
* selected time span.
*
*/
_fetch_data: function()
{
this.egw().dataFetch(
this.getInstanceManager().etemplate_exec_id,
{start: 0, num_rows:0},
jQuery.extend({}, app.calendar.state,
{
get_rows: 'calendar.calendar_uilist.get_rows',
row_id:'row_id',
startdate:this.options.start_date,
enddate:this.options.end_date,
col_filter: {participant: this.options.owner},
filter:'custom'
}),
this.id,
function(data) {
console.log(data);
var updated_days = {};
for(var i = 0; i < data.order.length && data.total; i++)
{
var record = this.egw().dataGetUIDdata(data.order[i]);
if(record && record.data)
{
if(typeof updated_days[record.data.date] === 'undefined')
{
updated_days[record.data.date] = [];
}
// Copy, to avoid unwanted changes by reference
updated_days[record.data.date].push(jQuery.extend({},record.data));
// Check for multi-day events listed once
// Date must stay a string or we might cause problems with nextmatch
var dates = {
start: typeof record.data.start === 'string' ? record.data.start : record.data.start.toJSON(),
end: typeof record.data.end === 'string' ? record.data.end : record.data.end.toJSON(),
};
if(dates.start.substr(0,10) != dates.end.substr(0,10))
{
this.date_helper.set_value(record.data.end);
var end = this.date_helper.date.getTime();
this.date_helper.set_value(record.data.start);
do
{
var expanded_date = ''+this.date_helper.get_year() + sprintf('%02d',this.date_helper.get_month()) + sprintf('%02d',this.date_helper.get_date());
if(typeof(updated_days[expanded_date]) == 'undefined')
{
updated_days[expanded_date] = [];
}
if(record.data.date !== expanded_date)
{
// Copy, to avoid unwanted changes by reference
updated_days[expanded_date].push(jQuery.extend({},record.data));
}
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)
}
}
}
for(var i = 0; i < this.day_list.length; i++)
{
var day = this.day_widgets[i];
day.set_date(this.day_list[i], updated_days[this.day_list[i]]||[], true);
this.value[this.day_list[i]] = updated_days[this.day_list[i]];
}
}, this,null
);
},
/**
@ -442,15 +945,37 @@ var et2_calendar_timegrid = et2_valueWidget.extend([et2_IDetachedDOM, et2_IResiz
{
if(typeof events !== 'object') return false;
var use_days_sent = true;
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]);
if(events.start_date)
{
this.set_start_date(events.start_date);
delete events.start_date;
use_days_sent = false;
}
if(events.end_date)
{
this.set_end_date(events.end_date);
delete events.end_date;
use_days_sent = false;
}
this.value = events || {};
if(use_days_sent)
{
var day_list = Object.keys(events);
if(day_list.length)
{
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 = [];
@ -464,6 +989,11 @@ var et2_calendar_timegrid = et2_valueWidget.extend([et2_IDetachedDOM, et2_IResiz
*/
set_start_date: function(new_date)
{
if(!new_date || new_date === null)
{
throw exception('Invalid start date. ' + new_date.toString());
}
// Use date widget's existing functions to deal
if(typeof new_date === "object" || typeof new_date === "string" && new_date.length > 8)
{
@ -481,7 +1011,7 @@ var et2_calendar_timegrid = et2_valueWidget.extend([et2_IDetachedDOM, et2_IResiz
if(old_date !== this.options.start_date && this.isAttached())
{
this.invalidate();
this.invalidate(true);
}
},
@ -493,6 +1023,10 @@ var et2_calendar_timegrid = et2_valueWidget.extend([et2_IDetachedDOM, et2_IResiz
*/
set_end_date: function(new_date)
{
if(!new_date || new_date === null)
{
throw exception('Invalid end date. ' + new_date.toString());
}
// Use date widget's existing functions to deal
if(typeof new_date === "object" || typeof new_date === "string" && new_date.length > 8)
{
@ -510,7 +1044,74 @@ var et2_calendar_timegrid = et2_valueWidget.extend([et2_IDetachedDOM, et2_IResiz
if(old_date !== this.options.end_date && this.isAttached())
{
this.invalidate();
this.invalidate(true);
}
},
/**
* Call change handler, if set
*/
change: function() {
if (this.onchange)
{
if(typeof this.onchange == 'function')
{
// Make sure function gets a reference to the widget
var args = Array.prototype.slice.call(arguments);
if(args.indexOf(this) == -1) args.push(this);
return this.onchange.apply(this, args);
} else {
return (et2_compileLegacyJS(this.options.onchange, this, _node))();
}
}
},
/**
* Call event change handler, if set
*/
event_change: function(event, dom_node) {
if (this.onevent_change)
{
var event_data = this._get_event_info(dom_node);
var event_widget = this.getWidgetById(event_data.id);
et2_calendar_event.recur_prompt(event_data, jQuery.proxy(function(button_id, event_data) {
// No need to continue
if(button_id === 'cancel') return false;
if(typeof this.onevent_change == 'function')
{
// Make sure function gets a reference to the widget
var args = Array.prototype.slice.call(arguments);
if(args.indexOf(event_widget) == -1) args.push(event_widget);
// Put button ID in event
event.button_id = button_id;
return this.onevent_change.apply(this, [event, event_widget, button_id]);
} else {
return (et2_compileLegacyJS(this.options.onevent_change, event_widget, dom_node))();
}
},this));
}
return false;
},
/**
* Turn on or off the visibility of weekends
*
* @param {boolean} weekends
*/
set_show_weekend: function(weekends)
{
if(this.options.show_weekend !== weekends)
{
this.options.show_weekend = weekends ? true : false;
if(this.isAttached())
{
this.invalidate();
}
}
},
@ -554,13 +1155,13 @@ var et2_calendar_timegrid = et2_valueWidget.extend([et2_IDetachedDOM, et2_IResiz
if(event.id && result && !this.options.disabled && !this.options.readonly)
{
this._edit_event(event);
et2_calendar_event.recur_prompt(event);
return false;
}
return result;
}
else
else if (_ev.target.dataset.date)
{
// Default handler to open a new event at the selected time
this.egw().open(null, 'calendar', 'add', {
@ -587,43 +1188,28 @@ var et2_calendar_timegrid = et2_valueWidget.extend([et2_IDetachedDOM, et2_IResiz
);
},
_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');
}
/**
* Get time from position
*
* @param {number} x
* @param {number} y
* @returns {DOMNode[]} time node(s) for the given position
*/
_get_time_from_position: function(x,y) {
x = Math.round(x);
y = Math.round(y);
var nodes = $j('.calendar_calAddEvent[data-hour]',this.div).removeClass('drop-hover').filter(function() {
var offset = $j(this).offset();
var range={x:[offset.left,offset.left+$j(this).outerWidth()],y:[offset.top,offset.top+$j(this).outerHeight()]};
var i = (x >=range.x[0] && x <= range.x[1]) && (y >= range.y[0] && y <= range.y[1]);
return i;
}).addClass("drop-hover");
return nodes;
},
/**
* Set which user owns this. Owner is passed along to the individual
* days.
@ -633,17 +1219,15 @@ var et2_calendar_timegrid = et2_valueWidget.extend([et2_IDetachedDOM, et2_IResiz
*/
set_owner: function(_owner)
{
var old = this.options.owner || 0;
// Let select-account widget handle value validation
this.owner.set_value(_owner);
this.owner.set_value(typeof _owner == "string" || typeof _owner == "number" ? _owner : jQuery.extend([],_owner));
this.options.owner = _owner;//this.owner.getValue();
for (var i = this._children.length - 1; i >= 0; i--)
if(old !== this.options.owner && this.isAttached())
{
if(typeof this._children[i].set_owner === 'function')
{
this._children[i].set_owner(this.options.owner);
}
this.invalidate(true);
}
},

View File

@ -13,7 +13,7 @@ $setup_info['calendar']['name'] = 'calendar';
$setup_info['calendar']['version'] = '14.2.002';
$setup_info['calendar']['app_order'] = 3;
$setup_info['calendar']['enable'] = 1;
$setup_info['calendar']['index'] = 'calendar.calendar_uiviews.index';
$setup_info['calendar']['index'] = 'calendar.calendar_uiviews.index&ajax=true';
$setup_info['calendar']['license'] = 'GPL';
$setup_info['calendar']['description'] =

View File

@ -13,6 +13,23 @@
}
}
/**
* Sidebox
*/
#calendar-sidebox_owner {
width: 82%;
}
#calendar-sidebox_cat_id {
width: 86%;
}
#calendar-sidebox_buttons tbody {
width: 100%;
}
#calendar-todo {
float: right;
width: 30%;
}
/* Header classes */
tr.dialogHeader td, tr.dialogHeader2 td, tr.dialogHeader3 td, tr.dialogHeader4 td,
tr.dialogOperators td,.dialogFooterToolbar {
@ -280,6 +297,13 @@ e.g. the div with class calendar_calTimeGrid is generated by the timeGridWidget
border-style: solid;
-moz-border-radius: 6px;
-webkit-border-radius: 6px;
/* It is important there are no CSS transitions, it breaks resize */
-webkit-transition:none;
-moz-transition: none !important;
-o-transition: none !important;
-ms-transition: none !important;
transition: none !important;
/* set via inline style on runtime:
* top: depending on startime
* height: depending on length

View File

@ -0,0 +1,57 @@
<?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.sidebox">
<vbox parent_node="calendar-et2_target">
<description label="eTemplate"/>
<grid id="buttons" width="100%">
<columns>
<column/>
<column/>
<column/>
<column/>
<column/>
<column/>
<column/>
</columns>
<rows><row width="100%">
<buttononly align="center" class="sideboxstar" id="add" onclick="egw.open(null,'calendar','add');" image="new" label="Add"/>
<buttononly align="center" class="sideboxstar" id="day" image="today" label="Today" onclick="app.calendar.update_state({view:'day',date:new Date()});"/>
<buttononly align="center" class="sideboxstar" id="week" image="week" label="Weekview" onclick="app.calendar.update_state({view:'week'});"/>
<buttononly align="center" class="sideboxstar" id="weekN" image="multiweek" label="Multiple week view" onclick="app.calendar.update_state({view:'weekN'});"/>
<buttononly align="center" class="sideboxstar" id="month" image="month" label="Multiple week view" onclick="app.calendar.update_state({view:'month'});"/>
<buttononly align="center" class="sideboxstar" id="planner" image="planner" label="Group planner"/>
<buttononly align="center" class="sideboxstar" id="listview" image="list" label="Listview" onclick="app.calendar.update_state({view:'listview'});"/>
</row>
</rows>
</grid>
<select id="view" class="et2_fullWidth" onchange="app.calendar.update_state(JSON.parse(widget.getValue()));"/>
<textbox id="keywords" class="et2_fullWidth" onchange="app.calendar.update_state({keywords: widget.getValue()});"/>
<date id="date" class="et2_fullWidth" inline="true" onchange="app.calendar.update_state({date:widget.getValue(),view:'day'});"/>
<hbox width="100%">
<select-cat id="cat_id" empty_label="All categories" width="86%" onchange="app.calendar.update_state({cat_id: widget.getValue()});"/>
<button align="right" id="cat_id_multiple" image="category"/>
</hbox>
<select-account id="owner" class="et2_fullWidth" onchange="app.calendar.update_state({owner: widget.getValue()});" expand_multiple_rows="4"/>
<!--
<taglist id="owner" class="et2_fullWidth" onchange="app.calendar.update_state({owner: widget.getValue()});" autocomplete_params=''/>
-->
<select id="filter" no_lang="true" class="et2_fullWidth" onchange="app.calendar.update_state({filter: widget.getValue()});"/>
<select id="merge" empty_label="Insert in document" onchange="widget.getInstanceManager().postSubmit();widget.set_value('');" class="et2_fullWidth"/>/>
</vbox>
<html id="old_calendar"/>
</template>
</overlay>

View File

@ -0,0 +1,32 @@
<?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.todo" width="30%">
<box class="calendar_calDayTodos">
<label id="label" class="calendar_calDayTodosHeader" width="100%" />
<html id="todos"/>
</box>
<!--
<grid id="todos">
<columns>
<column/>
</columns>
<rows>
<row class="todo_row">
</row>
</rows>
</grid>
-->
</template>
</overlay>

View File

@ -18,8 +18,12 @@ Egroupware
<column/>
</columns>
<rows>
<row>
<calendar-timegrid id="${row}"></calendar-timegrid>
<row class="view_row">
<calendar-timegrid id="${row}"
onchange="var state = {date:widget.options.start_date}; if(widget.options.start_date == widget.options.end_date) state.view = 'day'; app.calendar.update_state(state);"
onevent_change="app.calendar.event_change"
>
</calendar-timegrid>
</row>
</rows>
</grid>

View File

@ -11,7 +11,7 @@
* @package calendar
* @version $Id$
*/
/* $Id: app.css 52434 2015-04-07 13:15:33Z hnategh $ */
/* $Id: app.css 52715 2015-05-06 19:03:45Z nathangray $ */
/*Media print classes*/
@media print {
.th td,
@ -26,6 +26,22 @@
border-bottom: 1px solid gray;
}
}
/**
* Sidebox
*/
#calendar-sidebox_owner {
width: 82%;
}
#calendar-sidebox_cat_id {
width: 86%;
}
#calendar-sidebox_buttons tbody {
width: 100%;
}
#calendar-todo {
float: right;
width: 30%;
}
/* Header classes */
tr.dialogHeader td,
tr.dialogHeader2 td,
@ -294,6 +310,12 @@ e.g. the div with class calendar_calTimeGrid is generated by the timeGridWidget
border-style: solid;
-moz-border-radius: 6px;
-webkit-border-radius: 6px;
/* It is important there are no CSS transitions, it breaks resize */
-webkit-transition: none;
-moz-transition: none !important;
-o-transition: none !important;
-ms-transition: none !important;
transition: none !important;
/* set via inline style on runtime:
* top: depending on startime
* height: depending on length

View File

@ -72,6 +72,12 @@ Date: A date object containing the maximum date.\
Number: A number of days from today. For example 2 represents two days from today and -1 represents yesterday.\
String: A string in the user\'s date format, or a relative date. Relative dates must contain value and period pairs; valid periods are "y" for years, "m" for months, "w" for weeks, and "d" for days. For example, "+1m +7d" represents one month and seven days from today.'
},
inline: {
"name": "Inline",
"type": "boolean",
"default": false,
"description": "Instead of an input field with a popup calendar, the calendar is displayed inline, with no input field"
}
},
legacyOptions: ["data_format"],
@ -95,9 +101,9 @@ String: A string in the user\'s date format, or a relative date. Relative dates
createInputWidget: function() {
this.span = $j(document.createElement("span")).addClass("et2_date");
this.span = $j(document.createElement(this.options.inline ? 'div' : "span")).addClass("et2_date");
this.input_date = $j(document.createElement("input"));
this.input_date = $j(document.createElement(this.options.inline ? "div" : "input"));
if (this.options.blur) this.input_date.attr('placeholder', this.egw().lang(this.options.blur));
this.input_date.addClass("et2_date").attr("type", "text")
.attr("size", 7) // strlen("10:00pm")=7
@ -495,7 +501,14 @@ String: A string in the user\'s date format, or a relative date. Relative dates
timezone: 0
});
}
this.input_date.val(_value);
if(this.options.inline )
{
this.input_date.datepicker("setDate",formatDate);
}
else
{
this.input_date.val(_value);
}
if(this._oldValue !== et2_no_init && old_value != this.getValue())
{
this.change(this.input_date);

View File

@ -326,7 +326,7 @@ var et2_selectAccount = et2_selectbox.extend(
var found = false;
// Not having a value to look up causes an infinite loop
if(!search[j]) continue;
if(!search[j] || search[j] === "0") continue;
// Options are not indexed, so we must look
for(var i = 0; !found && i < this.options.select_options.length; i++)

View File

@ -52,14 +52,14 @@ var et2_taglist = et2_selectbox.extend(
"autocomplete_url": {
"name": "Autocomplete source",
"type": "string",
"default": "etemplate_widget_taglist.ajax_search.etemplate",
"default": "home.etemplate_widget_taglist.ajax_search.etemplate",
"description": "Menuaction (app.class.function) for autocomplete data source. Must return actual JSON, and nothing more."
},
"autocomplete_params": {
"name": "Autocomplete parameters",
"type": "any",
"default": {app:"addressbook"},
"description": "Extra parameters passed to autocomplete URL"
"description": "Extra parameters passed to autocomplete URL. It should be a stringified JSON object."
},
allowFreeEntries: {
@ -136,6 +136,23 @@ var et2_taglist = et2_selectbox.extend(
},
transformAttributes: function(_attrs) {
this._super.apply(this, arguments);
// Handle url parameters - they should be an object
if(typeof _attrs.autocomplete_params == 'string')
{
try
{
_attrs.autocomplete_params = JSON.parse(_attrs.autocomplete_params)
}
catch (e)
{
this.egw().debug('warn', 'Invalid autocomplete_params: '+_attrs.autocomplete_params );
}
}
},
doLoadingFinished: function() {
this._super.apply(this, arguments);

View File

@ -928,7 +928,7 @@ table.et2_grid {
/**
* Sortable grid
*/
table.et2_grid tbody.ui-sortable > tr:not(.th) {
table.et2_grid tbody.ui-sortable:not(.ui-sortable-disabled) > tr:not(.th) {
cursor: ns-resize;
}

View File

@ -264,7 +264,7 @@ class config
'site_title','login_logo_file','login_logo_url','login_logo_title','favicon_file',
'markuntranslated','link_list_thumbnail','enabled_spellcheck','debug_minify',
'call_link','call_popup', // addressbook
'hide_birthdays'), // calendar
'hide_birthdays','calview_no_consolidate'), // calendar
'projectmanager' => array('hours_per_workday', 'duration_units'),
'manual' => array('manual_remote_egw_url'),
'infolog' => array('status'),

View File

@ -651,13 +651,13 @@ function egwDropActionImplementation()
// Set cursor back to auto. Seems FF can't handle cursor reversion
$j('body').css({cursor:'auto'});
_aoi.triggerEvent(EGW_AI_DRAG_OUT);
_aoi.triggerEvent(EGW_AI_DRAG_OUT,{event: event,ui:ui});
},
"over": function() {
_aoi.triggerEvent(EGW_AI_DRAG_OVER);
"over": function(event, ui) {
_aoi.triggerEvent(EGW_AI_DRAG_OVER,{event: event,ui:ui});
},
"out": function() {
_aoi.triggerEvent(EGW_AI_DRAG_OUT);
"out": function(event,ui) {
_aoi.triggerEvent(EGW_AI_DRAG_OUT,{event: event,ui:ui});
},
"tolerance": "pointer",
hoverClass: "drop-hover",