forked from extern/egroupware
Calendar et2 conversion work in progress.
Context menu is enabled, but not all actions are fully implemented yet.
This commit is contained in:
parent
b4d597fbb5
commit
920616e37a
@ -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
|
||||
|
@ -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,
|
||||
);
|
||||
|
||||
// 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);
|
||||
// Target for etemplate
|
||||
$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,
|
||||
'no_lang' => true,
|
||||
'text'=>'<span id="calendar-et2_target" />',
|
||||
'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
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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 ';
|
||||
}
|
||||
$this->to_client($event);
|
||||
|
||||
// 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 ($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))
|
||||
{
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -26,6 +26,123 @@ app.classes.calendar = AppJS.extend(
|
||||
*/
|
||||
appname: 'calendar',
|
||||
|
||||
/**
|
||||
* etemplate for the sidebox filters
|
||||
*/
|
||||
sidebox_et2: null,
|
||||
|
||||
/**
|
||||
* etemplates and settings for the different views some (day view)
|
||||
* use more than one template, some use the same template as others,
|
||||
* most need different handling for their various attributes.
|
||||
*
|
||||
* Attributes are setter: function to calculate value
|
||||
*/
|
||||
views: {
|
||||
day: {
|
||||
etemplates: ['calendar.view','calendar.todo'],
|
||||
set_start_date: function(state) {
|
||||
return state.date ? new Date(state.date) : new Date();
|
||||
},
|
||||
set_end_date: function(state) {
|
||||
var d = state.date ? new Date(state.date) : new Date();
|
||||
d.setUTCHours(23);
|
||||
return d;
|
||||
},
|
||||
set_owner: function(state) {
|
||||
return state.owner || 0;
|
||||
},
|
||||
set_show_weekend: function(state)
|
||||
{
|
||||
state.days = '1';
|
||||
return parseInt(egw.preference('days_in_weekview','calendar')) == 7;
|
||||
}
|
||||
},
|
||||
day4: {
|
||||
etemplates: ['calendar.view'],
|
||||
set_start_date: function(state) {
|
||||
return state.date ? new Date(state.date) : new Date();
|
||||
},
|
||||
set_end_date: function(state) {
|
||||
var d = state.date ? new Date(state.date) : new Date();
|
||||
d.setUTCHours(24*4-1);
|
||||
return d;
|
||||
},
|
||||
set_owner: function(state) {
|
||||
return state.owner || 0;
|
||||
},
|
||||
set_show_weekend: function(state)
|
||||
{
|
||||
state.days = '4';
|
||||
return parseInt(egw.preference('days_in_weekview','calendar')) == 7;
|
||||
}
|
||||
},
|
||||
week: {
|
||||
etemplates: ['calendar.view'],
|
||||
set_start_date: function(state) {
|
||||
return app.calendar.date.start_of_week(state.date || new Date());
|
||||
},
|
||||
set_end_date: function(state) {
|
||||
var d = app.calendar.date.start_of_week(state.date || new Date());
|
||||
// Always 7 days, we just turn weekends on or off
|
||||
d.setUTCHours(24*7-1);
|
||||
return d;
|
||||
},
|
||||
set_owner: function(state) {
|
||||
return state.owner || 0;
|
||||
},
|
||||
set_show_weekend: function(state)
|
||||
{
|
||||
state.days = '' + (state.days >= 5 ? state.days : egw.preference('days_in_weekview','calendar') || 7);
|
||||
return parseInt(state.days) == 7;
|
||||
}
|
||||
},
|
||||
weekN: {
|
||||
etemplates: ['calendar.view'],
|
||||
set_start_date: function(state) {
|
||||
return app.calendar.date.start_of_week(state.date || new Date());
|
||||
},
|
||||
set_end_date: function(state) {
|
||||
var d = app.calendar.date.start_of_week(state.date || new Date());
|
||||
// Always 7 days, we just turn weekends on or off
|
||||
d.setUTCHours(24*7-1);
|
||||
return d;
|
||||
},
|
||||
set_show_weekend: function(state)
|
||||
{
|
||||
state.days = '' + (state.days >= 5 ? state.days : egw.preference('days_in_weekview','calendar') || 7);
|
||||
return parseInt(state.days) == 7;
|
||||
}
|
||||
},
|
||||
month: {
|
||||
etemplates: ['calendar.view'],
|
||||
set_start_date: function(state) {
|
||||
var d = state.date ? new Date(state.date) : new Date();
|
||||
d.setUTCDate(1);
|
||||
return app.calendar.date.start_of_week(d);
|
||||
},
|
||||
set_end_date: function(state) {
|
||||
var d = state.date ? new Date(state.date) : new Date();
|
||||
d = new Date(d.getFullYear(),d.getUTCMonth() + 1, 0);
|
||||
var week_start = app.calendar.date.start_of_week(d);
|
||||
if(week_start < d) week_start.setUTCHours(24*7);
|
||||
week_start.setUTCHours(week_start.getUTCHours()-1);
|
||||
return week_start;
|
||||
},
|
||||
},
|
||||
listview: {etemplates: ['calendar.list']}
|
||||
},
|
||||
|
||||
/**
|
||||
* Current internal state
|
||||
*/
|
||||
state: {
|
||||
date: new Date(),
|
||||
view: egw.preference('defaultcalendar','calendar') || 'day',
|
||||
owner: egw.user('account_id'),
|
||||
days: egw.preference('days_in_weekview','calendar')
|
||||
},
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
@ -89,8 +206,10 @@ app.classes.calendar = AppJS.extend(
|
||||
|
||||
switch (_name)
|
||||
{
|
||||
case 'calendar.list':
|
||||
this.filter_change();
|
||||
case 'calendar.sidebox':
|
||||
this.sidebox_et2 = _et2.widgetContainer;
|
||||
$j(_et2.DOMContainer).hide();
|
||||
this._setup_sidebox_filters();
|
||||
break;
|
||||
|
||||
case 'calendar.edit':
|
||||
@ -122,6 +241,44 @@ app.classes.calendar = AppJS.extend(
|
||||
case 'calendar.freetimesearch':
|
||||
this.set_enddate_visibility();
|
||||
break;
|
||||
case 'home.legacy':
|
||||
break;
|
||||
case 'calendar.list':
|
||||
this.filter_change();
|
||||
// Fall through
|
||||
default:
|
||||
var hidden = typeof this.state.view !== 'undefined';
|
||||
var all_loaded = true;
|
||||
// Record the templates for the views so we can switch between them
|
||||
for(var view in this.views)
|
||||
{
|
||||
var index = this.views[view].etemplates.indexOf(_name)
|
||||
if(index > -1)
|
||||
{
|
||||
this.views[view].etemplates[index] = _et2;
|
||||
// If a template disappears, we want to release it
|
||||
$j(_et2.DOMContainer).one('clear',jQuery.proxy(function() {
|
||||
this.view[index] = _name;
|
||||
},{view: this.views[view], index: index, name: _name}));
|
||||
|
||||
if(this.state.view === view)
|
||||
{
|
||||
hidden = false;
|
||||
}
|
||||
}
|
||||
this.views[view].etemplates.forEach(function(et) {all_loaded = all_loaded && typeof et !== 'string';});
|
||||
}
|
||||
|
||||
// Start hidden, except for current view
|
||||
if(hidden)
|
||||
{
|
||||
$j(_et2.DOMContainer).hide();
|
||||
}
|
||||
if(all_loaded)
|
||||
{
|
||||
this.setState({state:this.state});
|
||||
}
|
||||
|
||||
}
|
||||
},
|
||||
|
||||
@ -225,208 +382,6 @@ app.classes.calendar = AppJS.extend(
|
||||
{
|
||||
var that = this;
|
||||
|
||||
//Draggable & Resizable selector
|
||||
var $drag = jQuery("div[id^='drag_']")
|
||||
//draggable event handler
|
||||
.draggable
|
||||
({
|
||||
stack: jQuery("div[id^='drag_']"),
|
||||
revert: "invalid",
|
||||
delay: 50,
|
||||
|
||||
cursorAt:{top:0,left:0},
|
||||
containment: ".egw_fw_content_browser_iframe",
|
||||
scroll: true,
|
||||
opacity: .6,
|
||||
cursor: "move",
|
||||
|
||||
/**
|
||||
* Triggered when the dragging of calEvent stoped.
|
||||
*
|
||||
* @param {event} event
|
||||
* @param {Object} ui
|
||||
*/
|
||||
stop: function(event, ui)
|
||||
{
|
||||
ui.helper.width(oldWidth);
|
||||
ui.helper[0].innerHTML = oldInnerHTML;
|
||||
},
|
||||
|
||||
/**
|
||||
* Triggered while dragging a calEvent.
|
||||
*
|
||||
* @param {event} event
|
||||
* @param {Object} ui
|
||||
*
|
||||
*/
|
||||
drag:function(event, ui)
|
||||
{
|
||||
//that.dragEvent();
|
||||
},
|
||||
|
||||
/**
|
||||
* Triggered when the dragging of calEvent started.
|
||||
*
|
||||
* @param {event} event
|
||||
* @param {Object} ui
|
||||
*
|
||||
*/
|
||||
start: function(event, ui)
|
||||
{
|
||||
oldInnerHTML = ui.helper[0].innerHTML;
|
||||
oldWidth = ui.helper.width();
|
||||
ui.helper.width(jQuery("#calColumn").width());
|
||||
}
|
||||
})
|
||||
|
||||
//Resizable event handler
|
||||
.resizable
|
||||
({
|
||||
distance: 10,
|
||||
|
||||
|
||||
/**
|
||||
* Triggered when the resizable is created.
|
||||
*
|
||||
* @param {event} event
|
||||
* @param {Object} ui
|
||||
*/
|
||||
create:function(event, ui)
|
||||
{
|
||||
var resizeHelper = event.target.getAttribute('data-resize').split("|")[3];
|
||||
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)
|
||||
{
|
||||
var resizeHelper = event.target.getAttribute('data-resize');
|
||||
var dataResize = resizeHelper.split("|");
|
||||
var time = dataResize[1].split(":");
|
||||
|
||||
this.dropStart = that.resizeHelper(ui.element[0].getBoundingClientRect().left,ui.element[0].getBoundingClientRect().top);
|
||||
this.dropDate = dataResize[0]+"T"+time[0]+time[1];
|
||||
//$j(this).resizable("option","containment",".calendar_calDayCol");
|
||||
},
|
||||
|
||||
/**
|
||||
* Triggered at the end of resizing the calEvent.
|
||||
*
|
||||
* @param {event} event
|
||||
* @param {Object} ui
|
||||
*/
|
||||
stop:function(event, ui)
|
||||
{
|
||||
var eventFlag = event.target.getAttribute('data-resize').split("|")[3];
|
||||
var dropdate = that.cal_dnd_tZone_converter(this.dropDate);
|
||||
var sT = parseInt((dropdate.split("T")[1].substr(0,2)* 60)) + parseInt(dropdate.split("T")[1].substr(2,2));
|
||||
if (this.dropEnd != 'undefined' && this.dropEnd)
|
||||
{
|
||||
var eT = parseInt(this.dropEnd.getAttribute('data-date').split("|")[1] * 60) + parseInt(this.dropEnd.getAttribute('data-date').split("|")[2]);
|
||||
var newDuration = ((eT - sT)/60) * 3600;
|
||||
that.dropEvent(this.getAttribute('id'),dropdate,newDuration,eventFlag);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Triggered during the resize, on the drag of the resize handler
|
||||
*
|
||||
* @param {event} event
|
||||
* @param {Object} ui
|
||||
*/
|
||||
resize:function(event, ui)
|
||||
{
|
||||
this.dropEnd = that.resizeHelper(ui.element[0].getBoundingClientRect().left,
|
||||
ui.element[0].getBoundingClientRect().top+ui.size.height);
|
||||
|
||||
if (typeof this.dropEnd != 'undefined' && this.dropEnd)
|
||||
{
|
||||
if (this.dropEnd.getAttribute('id').match(/drop_/g))
|
||||
{
|
||||
var dH = this.dropEnd.getAttribute('data-date').split("|")[1];
|
||||
var dM = this.dropEnd.getAttribute('data-date').split("|")[2];
|
||||
}
|
||||
var dataResize = event.target.getAttribute('data-resize').split("|");
|
||||
this.innerHTML = '<div style="font-size: 1.1em; text-align:center; font-weight: bold; height:100%;"><span class="calendar_timeDemo" >'+dH+':'+dM+'</span></div>';
|
||||
}
|
||||
else
|
||||
{
|
||||
this.innerHTML = '<div class="calendar_d-n-d_forbiden"></div>';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
//Droppable selector
|
||||
var $drop = jQuery("div[id^='drop_']")
|
||||
//Droppable event handler
|
||||
.droppable
|
||||
({
|
||||
/**
|
||||
* Make all draggable calEvents acceptable
|
||||
*
|
||||
*/
|
||||
accept:function()
|
||||
{
|
||||
return true;
|
||||
},
|
||||
tolerance:'pointer',
|
||||
|
||||
/**
|
||||
* Triggered when the calEvent dropped.
|
||||
*
|
||||
* @param {event} event
|
||||
* @param {Object} ui
|
||||
*/
|
||||
drop:function(event, ui)
|
||||
{
|
||||
var dgId = ui.draggable[0].getAttribute('id');
|
||||
var dgOwner = dgId.substring(dgId.lastIndexOf("_C")+2,dgId.lastIndexOf(""));
|
||||
var dpOwner = event.target.getAttribute('data-owner');
|
||||
var eventFlag = ui.draggable[0].getAttribute('data-resize').split("|")[3];
|
||||
if (dpOwner == null) dpOwner = dgOwner;
|
||||
if (dpOwner == dgOwner )
|
||||
{
|
||||
that.dropEvent(ui.draggable[0].id, event.target.getAttribute('id').substring(event.target.getAttribute('id').lastIndexOf("drop_")+5, event.target.getAttribute('id').lastIndexOf("_O")),null,eventFlag);
|
||||
}
|
||||
else
|
||||
{
|
||||
jQuery(ui.draggable).draggable("option","revert",true);
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* Triggered when draggable calEvent is over a droppable calCell.
|
||||
*
|
||||
* @param {event} event
|
||||
* @param {Object} ui
|
||||
*/
|
||||
over:function(event, ui)
|
||||
{
|
||||
var timeDemo = event.target.id.substring(event.target.id.lastIndexOf("T")+1,event.target.id.lastIndexOf("_O"));
|
||||
var dgId = ui.draggable[0].getAttribute('id');
|
||||
var dgOwner = dgId.substring(dgId.lastIndexOf("_C")+2,dgId.lastIndexOf(""));
|
||||
var dpOwner = event.target.getAttribute('data-owner');
|
||||
if (dpOwner == null) dpOwner = dgOwner;
|
||||
if (dpOwner === dgOwner )
|
||||
{
|
||||
ui.helper[0].innerHTML = '<div class="calendar_d-n-d_timeCounter"><span>'+timeDemo+'</span></div>';
|
||||
}
|
||||
else
|
||||
{
|
||||
ui.helper[0].innerHTML = '<div class="calendar_d-n-d_forbiden"></div>';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
//jQuery Calendar Event selector
|
||||
var $iframeBody = jQuery("body")
|
||||
//mouseover event handler for calendar tooltip
|
||||
@ -485,23 +440,6 @@ app.classes.calendar = AppJS.extend(
|
||||
}
|
||||
})
|
||||
|
||||
//onClick event handler for calendar Events
|
||||
.on("click", "div.calendar_calEvent", function(ev){
|
||||
var Id = ev.currentTarget.id.replace(/drag_/g,'').split("_")[0];
|
||||
var eventId = Id.match(/-?\d+\.?\d*/g)[0];
|
||||
var appName = Id.replace(/-?\d+\.?\d*/g,'');
|
||||
var startDate = ev.currentTarget.getAttribute('data-resize').split("|")[0];
|
||||
var eventFlag = ev.currentTarget.getAttribute('data-resize').split("|")[3];
|
||||
if (eventFlag != 'S' && eventFlag != 'WDS')
|
||||
{
|
||||
that.egw.open(eventId,appName !=""?appName:'calendar','edit');
|
||||
}
|
||||
else
|
||||
{
|
||||
that.edit_series(eventId,startDate);
|
||||
}
|
||||
})
|
||||
|
||||
//Click event handler for integrated apps
|
||||
.on("click","div.calendar_plannerEvent",function(ev){
|
||||
var eventId = ev.currentTarget.getAttribute('data-date').split("|")[1];
|
||||
@ -516,63 +454,26 @@ app.classes.calendar = AppJS.extend(
|
||||
that.edit_series(eventId,startDate);
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
//Click event handler for calendar cells
|
||||
.on("click","div.calendar_calAddEvent, div.calendar_calTimeRowTime",function(ev){
|
||||
var timestamp = ev.target.getAttribute('data-date').split("|");
|
||||
if (typeof ev.target.getAttribute('id') != 'undefined' && ev.target.getAttribute('id'))
|
||||
{
|
||||
var owner = ev.target.getAttribute('id').split("_");
|
||||
|
||||
var ownerId = owner[2].match( /Ogroup/g)?owner[2].replace( /Ogroup/g, '-'):owner[2].replace( /^\D+/g, '');
|
||||
if (owner[2].match( /Or/g))
|
||||
{
|
||||
ownerId = 'r' + ownerId;
|
||||
}
|
||||
}
|
||||
|
||||
var eventInfo = {
|
||||
date: timestamp[0],
|
||||
hour: timestamp[1],
|
||||
minute: timestamp[2]
|
||||
};
|
||||
|
||||
if (typeof ownerId !='undefined' && ownerId != 0)
|
||||
{
|
||||
jQuery(eventInfo).extend(eventInfo,{owner: ownerId});
|
||||
}
|
||||
|
||||
that.egw.open(null, 'calendar', 'add', eventInfo , '_blank');
|
||||
})
|
||||
|
||||
//Click event handler for calendar todos
|
||||
.on("click", "a[data-todo]",function(ev){
|
||||
var windowSize = ev.currentTarget.getAttribute('data-todo').split("|")[1];
|
||||
var link = ev.currentTarget.getAttribute('href');
|
||||
that.egw.open_link(link,'_blank',windowSize);
|
||||
return false;
|
||||
});
|
||||
|
||||
//******************************** Calendar Sortable ************************
|
||||
/**
|
||||
* Setup and handle sortable calendars.
|
||||
*
|
||||
* You can only sort calendars if there is more than one owner, and the calendars
|
||||
* are not combined (many owners, multi-week or month views)
|
||||
* @returns {undefined}
|
||||
*/
|
||||
_sortable: function() {
|
||||
// Calender current state
|
||||
var state = this.getState();
|
||||
|
||||
if (state && state.view === "day"
|
||||
&& typeof state.owner != 'undefined'
|
||||
&& typeof state.owner == 'string' && state.owner.split(',').length > 1)
|
||||
var sortable = jQuery('#calendar-view_view tbody');
|
||||
if(!sortable.sortable('instance'));
|
||||
{
|
||||
$iframeBody.find('#calendar_calDayCols')
|
||||
.addClass('cal_is_sortable')
|
||||
.css({"white-space":"nowrap"})
|
||||
.children().each(function(){
|
||||
// Change day view columns position in order to get sortable placeholder working
|
||||
jQuery(this).css({position:"relative",display:"inline-block", left:"none"});
|
||||
});
|
||||
}
|
||||
|
||||
$iframeBody.find('.cal_is_sortable').sortable ({
|
||||
jQuery('#calendar-view_view tbody').sortable({
|
||||
cancel: "#divAppboxHeader, .calendar_calWeekNavHeader, .calendar_plannerHeader",
|
||||
placeholder: "srotable_cal_wk_ph",
|
||||
handle: '.calendar_calGridHeader',
|
||||
//placeholder: "srotable_cal_wk_ph",
|
||||
axis:"y",
|
||||
revert: true,
|
||||
helper:"clone",
|
||||
@ -596,30 +497,40 @@ app.classes.calendar = AppJS.extend(
|
||||
};
|
||||
$sortItem.sortable('option', options);
|
||||
break;
|
||||
default:
|
||||
$sortItem.sortable('destroy');
|
||||
}
|
||||
},
|
||||
start: function ()
|
||||
{
|
||||
$drag.draggable('disable');
|
||||
$drop.droppable('disable');
|
||||
// Put owners into row IDs
|
||||
app.calendar.views[state.view].etemplates[0].widgetContainer.iterateOver(function(widget) {
|
||||
widget.div.parents('tr').attr('data-owner',widget.options.owner);
|
||||
},this,et2_calendar_timegrid)
|
||||
},
|
||||
stop: function ()
|
||||
{
|
||||
$drag.draggable('enable');
|
||||
$drop.droppable('enable');
|
||||
},
|
||||
update: function ()
|
||||
{
|
||||
if (state && typeof state.owner !== 'undefined')
|
||||
{
|
||||
var sortedArr = jQuery(this).sortable('toArray', {attribute:"data-sortable-id"});
|
||||
state.owner = sortedArr.join(',');
|
||||
that.setState({state:state});
|
||||
var sortedArr = sortable.sortable('toArray', {attribute:"data-owner"});
|
||||
// Directly update, since there is no other changes needed,
|
||||
// and we don't want the current sort order applied
|
||||
app.calendar.state.owner = sortedArr;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Enable or disable
|
||||
if(state.view == 'weekN' || state.view === 'month' || state.owner.length == 1 || state.owner.length > egw.config('calview_no_consolidate','phpgwapi'))
|
||||
{
|
||||
sortable.sortable('disable');
|
||||
}
|
||||
else
|
||||
{
|
||||
sortable.sortable('enable');
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@ -681,6 +592,24 @@ app.classes.calendar = AppJS.extend(
|
||||
return date;
|
||||
},
|
||||
|
||||
/**
|
||||
* Handler for changes generated by internal user interactions, like
|
||||
* drag & drop inside calendar and resize.
|
||||
*
|
||||
* @param {Event} event
|
||||
* @param {et2_calendar_event} widget Widget for the event
|
||||
* @param {string} dialog_button - 'single', 'series', or 'exception', based on the user's answer
|
||||
* in the popup
|
||||
* @returns {undefined}
|
||||
*/
|
||||
event_change: function(event, widget, dialog_button)
|
||||
{
|
||||
egw().json(
|
||||
'calendar.calendar_uiforms.ajax_moveEvent',
|
||||
[widget.id, widget.options.value.owner, widget.options.value.start, widget.options.value.owner, widget.options.value.duration]
|
||||
).sendRequest();
|
||||
},
|
||||
|
||||
/**
|
||||
* This function tries to recognise the type of dropped event, and sends relative request to server accordingly
|
||||
* -ATM we have three different requests:
|
||||
@ -1034,6 +963,40 @@ app.classes.calendar = AppJS.extend(
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Change status (via AJAX)
|
||||
*
|
||||
* @param {egwAction} _action
|
||||
* @param {egwActionObject} _events
|
||||
*/
|
||||
status: function(_action, _events)
|
||||
{
|
||||
// Should be a single event, but we'll do it for all
|
||||
for(var i = 0; i < _events.length; i++)
|
||||
{
|
||||
var event_widget = _events[i].iface.getWidget() || false;
|
||||
if(!event_widget) continue;
|
||||
|
||||
event_widget.recur_prompt(jQuery.proxy(function(button_id,event_data) {
|
||||
console.log(event_data.title, ' ', event_data.start, ' Status change ', _action.data.id, ' Button: ', button_id );
|
||||
switch(button_id)
|
||||
{
|
||||
case 'exception':
|
||||
|
||||
break;
|
||||
case 'series':
|
||||
case 'single':
|
||||
this.egw.open(event_data.id, event_data.app||'calendar', 'edit', {date:event_data.start});
|
||||
break;
|
||||
case 'cancel':
|
||||
default:
|
||||
break;
|
||||
}
|
||||
},this));
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* this function try to fix ids which are from integrated apps
|
||||
*
|
||||
@ -1241,13 +1204,6 @@ app.classes.calendar = AppJS.extend(
|
||||
this.egw.lang("This event is part of a series"), {}, buttons, et2_dialog.WARNING_MESSAGE);
|
||||
},
|
||||
|
||||
/**
|
||||
* Current state, get updated via set_state method
|
||||
*
|
||||
* @type object
|
||||
*/
|
||||
state: undefined,
|
||||
|
||||
/**
|
||||
* Method to set state for JSON requests (jdots ajax_exec or et2 submits can NOT use egw.js script tag)
|
||||
*
|
||||
@ -1257,8 +1213,50 @@ app.classes.calendar = AppJS.extend(
|
||||
{
|
||||
if (typeof _state == 'object')
|
||||
{
|
||||
// If everything is loaded, handle the changes
|
||||
if(this.sidebox_et2 !== null)
|
||||
{
|
||||
this.update_state(_state);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Things aren't loaded yet, just set it
|
||||
this.state = _state;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Change only part of the current state.
|
||||
*
|
||||
* The passed state options (filters) are merged with the current state, so
|
||||
* this is the one that should be used for most calls, as setState() requires
|
||||
* the complete state.
|
||||
*
|
||||
* @param {Object} _set New settings
|
||||
*/
|
||||
update_state: function(_set)
|
||||
{
|
||||
var changed = [];
|
||||
var new_state = jQuery.extend({}, this.state);
|
||||
if (typeof _set == 'object')
|
||||
{
|
||||
for(var s in _set)
|
||||
{
|
||||
if (new_state[s] !== _set[s])
|
||||
{
|
||||
changed.push(s + ': ' + new_state[s] + ' -> ' + _set[s]);
|
||||
new_state[s] = _set[s];
|
||||
}
|
||||
}
|
||||
}
|
||||
if(changed.length && !this.state_update_in_progress)
|
||||
{
|
||||
console.log('Calendar state changed',changed.join("\n"));
|
||||
// Log
|
||||
this.egw.debug('navigation','Calendar state changed', changed.join("\n"));
|
||||
this.setState({state: new_state});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@ -1312,7 +1310,231 @@ app.classes.calendar = AppJS.extend(
|
||||
state = JSON.parse(state);
|
||||
}
|
||||
}
|
||||
if(typeof state.state != 'object' || !state.state.view)
|
||||
{
|
||||
state.state = {view: 'week'};
|
||||
}
|
||||
if(!state.state.date)
|
||||
{
|
||||
state.state.date = new Date();
|
||||
}
|
||||
|
||||
// Check for a supported client-side view
|
||||
if(this.views[state.state.view])
|
||||
{
|
||||
// Doing an update - this includes the selected view, and the sidebox
|
||||
// We set a flag to ignore changes from the sidebox which would
|
||||
// cause infinite loops.
|
||||
this.state_update_in_progress = true;
|
||||
|
||||
var view = this.views[state.state.view];
|
||||
|
||||
// Sanitize owner
|
||||
switch(typeof state.state.owner)
|
||||
{
|
||||
case 'undefined':
|
||||
state.state.owner = this.egw.user('account_id');
|
||||
break;
|
||||
case 'string':
|
||||
state.state.owner = state.state.owner.split(',');
|
||||
break;
|
||||
case 'number':
|
||||
state.state.owner = [state.state.owner];
|
||||
break;
|
||||
}
|
||||
// Keep sort order
|
||||
if(typeof this.state.owner === 'object')
|
||||
{
|
||||
var owner = [];
|
||||
this.state.owner.forEach(function(key) {
|
||||
var found = false;
|
||||
state.state.owner = state.state.owner.filter(function(item) {
|
||||
if(!found && item == key) {
|
||||
owner.push(item);
|
||||
found = true;
|
||||
return false;
|
||||
} else
|
||||
return true;
|
||||
});
|
||||
});
|
||||
// Add in any new owners
|
||||
state.state.owner = owner.concat(state.state.owner);
|
||||
}
|
||||
|
||||
|
||||
// Show the correct number of grids
|
||||
var grid_count = state.state.view == 'weekN' ? parseInt(this.egw.preference('multiple_weeks','calendar')) || 3 :
|
||||
state.state.view == 'month' ? 0 : // Calculate based on weeks in the month
|
||||
state.state.owner.length > (this.egw.config('calview_no_consolidate','phpgwapi') || 5) ? 1 : state.state.owner.length;
|
||||
|
||||
var grid = this.views[this.state.view] ? this.views[this.state.view].etemplates[0].widgetContainer.getWidgetById('view') : false;
|
||||
|
||||
/*
|
||||
If the count is different, we need to have the correct number (just remove all & re-create)
|
||||
If the count is > 1, it's either because there are multiple date spans (weekN, month) and we need the correct span
|
||||
per row, or there are multiple owners and we need the correct owner per row.
|
||||
*/
|
||||
if(state.state.view !== 'listview' && (!grid || grid_count != grid._children.length || grid_count > 1))
|
||||
{
|
||||
// Need to redo the number of grids
|
||||
var value = [];
|
||||
var date = view.set_start_date(state.state);
|
||||
|
||||
// Determine the different end date
|
||||
switch(state.state.view)
|
||||
{
|
||||
case 'month':
|
||||
var end = view.set_end_date(state.state);
|
||||
grid_count = Math.ceil((end - date) / (1000 * 60 * 60 * 24) / 7);
|
||||
// fall through
|
||||
case 'weekN':
|
||||
for(var week = 0; week < grid_count; week++)
|
||||
{
|
||||
var val = {
|
||||
id: ""+date.getUTCFullYear() + sprintf("%02d",date.getUTCMonth()) + sprintf("%02d",date.getUTCDate()),
|
||||
start_date: new Date(date),
|
||||
end_date: new Date(date),
|
||||
owner: state.state.owner
|
||||
};
|
||||
val.end_date.setUTCHours(24*7-1);
|
||||
value.push(val);
|
||||
date.setUTCHours(24*7);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
var end = view.set_end_date(state.state);
|
||||
for(var owner = 0; owner < grid_count && owner < state.state.owner.length; owner++)
|
||||
{
|
||||
value.push({
|
||||
id: ""+date.getUTCFullYear() + sprintf("%02d",date.getUTCMonth()) + sprintf("%02d",date.getUTCDate()),
|
||||
start_date: date,
|
||||
end_date: end,
|
||||
owner: state.state.owner[owner] || 0
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
if(view.etemplates[0].widgetContainer.getWidgetById('view'))
|
||||
{
|
||||
view.etemplates[0].widgetContainer.getWidgetById('view').set_value(
|
||||
{content: value}
|
||||
);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Simple, easy case - just one timegrid.
|
||||
// Update existing view's special attribute filters, defined in the view list
|
||||
for(var updater in view)
|
||||
{
|
||||
if(typeof view[updater] === 'function')
|
||||
{
|
||||
var value = view[updater].call(this,state.state);
|
||||
|
||||
// Set value
|
||||
for(var i = 0; i < view.etemplates.length; i++)
|
||||
{
|
||||
view.etemplates[i].widgetContainer.iterateOver(function(widget) {
|
||||
if(typeof widget[updater] === 'function')
|
||||
{
|
||||
widget[updater](value);
|
||||
}
|
||||
}, this, et2_valueWidget);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Hide other views
|
||||
for(var _view in this.views)
|
||||
{
|
||||
if(state.state.view != _view && this.views[_view])
|
||||
{
|
||||
for(var i = 0; i < this.views[_view].etemplates.length; i++)
|
||||
{
|
||||
$j(this.views[_view].etemplates[i].DOMContainer).hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Show the templates for the current view
|
||||
for(var i = 0; i < view.etemplates.length; i++)
|
||||
{
|
||||
$j(view.etemplates[i].DOMContainer).show();
|
||||
}
|
||||
// Toggle todos
|
||||
if(state.state.view == 'day')
|
||||
{
|
||||
if(state.state.owner.length !== 1)
|
||||
{
|
||||
$j(view.etemplates[1].DOMContainer).hide();
|
||||
view.etemplates[0].widgetContainer.set_width("");
|
||||
}
|
||||
else
|
||||
{
|
||||
view.etemplates[0].widgetContainer.set_width("70%");
|
||||
// TODO: Maybe some caching here
|
||||
this.egw.jsonq('calendar_uiviews::ajax_get_todos', [state.state.date, state.state.owner[0]], function(data) {
|
||||
this.getWidgetById('label').set_value(data.label||'');
|
||||
this.getWidgetById('todos').set_value(data.todos||'');
|
||||
},view.etemplates[1].widgetContainer)
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
view.etemplates[0].widgetContainer.set_width("");
|
||||
}
|
||||
this.state = jQuery.extend({},state.state);
|
||||
|
||||
if(state.state.view === 'listview')
|
||||
{
|
||||
state.state.startdate = state.state.date;
|
||||
state.state.col_filter = {participant: state.state.owner};
|
||||
var nm = this.views[_view].etemplates[0].widgetContainer.getWidgetById('nm');
|
||||
nm.applyFilters(state.state);
|
||||
}
|
||||
|
||||
/* Update re-orderable calendars */
|
||||
this._sortable();
|
||||
|
||||
/* Update sidebox widgets to show current value*/
|
||||
this.sidebox_et2.iterateOver(function(widget) {
|
||||
if(widget.id == 'view')
|
||||
{
|
||||
// View widget has a list of state settings, which require special handling
|
||||
for(var i = 0; i < widget.options.select_options.length; i++)
|
||||
{
|
||||
var option_state = JSON.parse(widget.options.select_options[i].value) || [];
|
||||
var match = true;
|
||||
for(var os_key in option_state)
|
||||
{
|
||||
match = match && option_state[os_key] == this.state[os_key];
|
||||
}
|
||||
if(match)
|
||||
{
|
||||
widget.set_value(widget.options.select_options[i].value);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(typeof state.state[widget.id] !== 'undefined' && state.state[widget.id] != widget.getValue())
|
||||
{
|
||||
// Update widget. This may trigger an infinite loop of
|
||||
// updates, so we do it after changing this.state and set a flag
|
||||
widget.set_value(state.state[widget.id]);
|
||||
}
|
||||
},this,et2_valueWidget);
|
||||
|
||||
// Sidebox is updated, we can clear the flag
|
||||
this.state_update_in_progress = false;
|
||||
|
||||
// Show / Hide weekends in sidebox calendar based on if weekends should be shown
|
||||
egw.css('#'+this.sidebox_et2.getWidgetById('date').input_date.attr('id') + ' .ui-datepicker-week-end',
|
||||
(parseInt(this.state.days && this.state.days > 1 ? this.state.days: egw.preference('days_in_weekview','calendar'))) === 5 ? 'display: none;' : 'display: table-cell;');
|
||||
|
||||
return;
|
||||
}
|
||||
// old calendar state handling on server-side (incl. switching to and from listview)
|
||||
var menuaction = 'calendar.calendar_uiviews.index';
|
||||
if (typeof state.state != 'undefined' && (typeof state.state.view == 'undefined' || state.state.view == 'listview'))
|
||||
@ -1399,6 +1621,28 @@ app.classes.calendar = AppJS.extend(
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Check to see if any of the selected is an event widget
|
||||
* Used to separate grid actions from event actions
|
||||
*
|
||||
* @param {egwAction} _egw
|
||||
* @param {egwActioObject[]} _widget
|
||||
* @returns {boolean} Is any of the selected an event widget
|
||||
*/
|
||||
is_event: function(_action, _selected)
|
||||
{
|
||||
var is_widget = false;
|
||||
for(var i = 0; i < _selected.length; i++)
|
||||
{
|
||||
if(_selected[i].iface.getWidget() && _selected[i].iface.getWidget().instanceOf(et2_calendar_event))
|
||||
{
|
||||
is_widget = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return is_widget;
|
||||
},
|
||||
|
||||
/**
|
||||
* Enable/Disable custom Date-time for set Alarm
|
||||
*
|
||||
@ -1484,5 +1728,77 @@ app.classes.calendar = AppJS.extend(
|
||||
event.set_value(_secs_to_label(60 * def_alarm));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Some handy date calculations
|
||||
* All take either a Date object or full date with timestamp (Z)
|
||||
*/
|
||||
date: {
|
||||
start_of_week: function(date)
|
||||
{
|
||||
var d = new Date(date);
|
||||
var day = d.getUTCDay();
|
||||
var diff = 0;
|
||||
switch(egw.preference('weekdaystarts','calendar'))
|
||||
{
|
||||
case 'Saturday':
|
||||
diff = day === 6 ? 0 : day === 0 ? -1 : day + 1;
|
||||
break;
|
||||
case 'Monday':
|
||||
diff = day === 0 ? 1 : 1-day;
|
||||
break;
|
||||
case 'Sunday':
|
||||
default:
|
||||
diff = -day;
|
||||
}
|
||||
d.setUTCHours(24*diff);
|
||||
return d;
|
||||
},
|
||||
end_of_week: function(date)
|
||||
{
|
||||
var d = app.calendar.date.start_of_week(date);
|
||||
d.setUTCHours(24*7);
|
||||
return d;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* The sidebox filters use some non-standard and not-exposed options. They
|
||||
* are set up here.
|
||||
*
|
||||
*/
|
||||
_setup_sidebox_filters: function()
|
||||
{
|
||||
// Further date customizations
|
||||
var date = this.sidebox_et2.getWidgetById('date');
|
||||
if(date)
|
||||
{
|
||||
date.input_date.datepicker("option", {
|
||||
showButtonPanel: false,
|
||||
// TODO: We could include tooltips for holidays
|
||||
})
|
||||
}
|
||||
// Show / Hide weekends based on preference of weekends should be shown
|
||||
egw.css('#'+date.input_date.attr('id') + ' .ui-datepicker-week-end',
|
||||
egw.preference('days_in_weekview', 'calendar') === "5" ? 'display: none;' : 'display: table-cell;'
|
||||
);
|
||||
|
||||
|
||||
// Clickable week numbers
|
||||
date.input_date.on('mouseenter','.ui-datepicker-week-col', function() {
|
||||
$j(this).siblings().find('a').addClass('ui-state-hover');
|
||||
})
|
||||
.on('mouseleave','.ui-datepicker-week-col', function() {
|
||||
$j(this).siblings().find('a').removeClass('ui-state-hover');
|
||||
})
|
||||
.on('click', '.ui-datepicker-week-col', function() {
|
||||
// Fake a click event on the first day to get the updated date
|
||||
$j(this).next().click();
|
||||
|
||||
// Set to week view
|
||||
app.calendar.update_state({view: 'week', date: date.getValue()});
|
||||
});
|
||||
|
||||
}
|
||||
});
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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;
|
||||
|
||||
// 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;
|
||||
},
|
||||
|
||||
/**
|
||||
@ -228,40 +380,48 @@ var et2_calendar_event = et2_valueWidget.extend([et2_IDetachedDOM],
|
||||
return result;
|
||||
},
|
||||
|
||||
_edit: function()
|
||||
/**
|
||||
* Show the recur prompt for this event
|
||||
*
|
||||
* @param {function} callback
|
||||
*/
|
||||
recur_prompt: function(callback)
|
||||
{
|
||||
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':
|
||||
et2_calendar_event.recur_prompt(this.options.value,callback);
|
||||
},
|
||||
|
||||
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);
|
||||
/**
|
||||
* 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);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -282,3 +442,121 @@ var et2_calendar_event = et2_valueWidget.extend([et2_IDetachedDOM],
|
||||
},
|
||||
});
|
||||
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;
|
||||
};
|
||||
|
@ -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;
|
||||
@ -174,9 +363,11 @@ var et2_calendar_timegrid = et2_valueWidget.extend([et2_IDetachedDOM, et2_IResiz
|
||||
* 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,10 +376,47 @@ 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) {
|
||||
@ -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();
|
||||
},
|
||||
|
||||
/**
|
||||
@ -230,6 +458,7 @@ var et2_calendar_timegrid = et2_valueWidget.extend([et2_IDetachedDOM, et2_IResiz
|
||||
var totalDisplayMinutes = wd_end - wd_start;
|
||||
var rowsToDisplay = (totalDisplayMinutes/granularity)+2+2*this.options.extra_rows;
|
||||
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,11 +540,17 @@ 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) + '%');
|
||||
|
||||
@ -323,10 +558,10 @@ var et2_calendar_timegrid = et2_valueWidget.extend([et2_IDetachedDOM, et2_IResiz
|
||||
$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;
|
||||
|
||||
// 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)
|
||||
for(var i = 0; i < parent.children.length; i++)
|
||||
{
|
||||
if(!event || !event.app_id) return;
|
||||
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) {
|
||||
|
||||
// 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]);
|
||||
});
|
||||
|
||||
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]});
|
||||
}
|
||||
objectManager.setAllSelected(false);
|
||||
obj.setSelected(true);
|
||||
objectManager.updateSelectedChildren(obj,true)
|
||||
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);
|
||||
}
|
||||
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;
|
||||
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)
|
||||
{
|
||||
@ -509,9 +1043,76 @@ var et2_calendar_timegrid = et2_valueWidget.extend([et2_IDetachedDOM, et2_IResiz
|
||||
this.options.end_date = this.date_helper.getValue();
|
||||
|
||||
if(old_date !== this.options.end_date && this.isAttached())
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
get_granularity: function()
|
||||
@ -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':
|
||||
/**
|
||||
* 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) {
|
||||
|
||||
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');
|
||||
}
|
||||
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);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -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'] =
|
||||
|
@ -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
|
||||
|
57
calendar/templates/default/sidebox.xet
Normal file
57
calendar/templates/default/sidebox.xet
Normal 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>
|
32
calendar/templates/default/todo.xet
Normal file
32
calendar/templates/default/todo.xet
Normal 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>
|
@ -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>
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
});
|
||||
}
|
||||
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);
|
||||
|
@ -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++)
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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'),
|
||||
|
@ -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",
|
||||
|
Loading…
Reference in New Issue
Block a user