Monotone-Parent: 8f5116023daecdd209e3db884fc339164a4ff419

Monotone-Revision: 284fc46fc7a7b10eb3aecddeb12116108147b6d1

Monotone-Author: wsourdeau@inverse.ca
Monotone-Date: 2006-10-11T18:15:29
Monotone-Branch: ca.inverse.sogo
This commit is contained in:
Wolfgang Sourdeau
2006-10-11 18:15:29 +00:00
parent 046a45bc1e
commit 1c44c54502
5 changed files with 1931 additions and 0 deletions
+7
View File
@@ -1,5 +1,12 @@
2006-10-11 Wolfgang Sourdeau <wsourdeau@inverse.ca>
* UI/Scheduler/UIxTaskProposal.[hm],
UI/Scheduler/UIxTaskView.[hm], UI/Scheduler/UIxTaskEditor.[hm]:
clones of the UIxAppointment* classes for the handling of tasks.
* UI/WebServerResources/UIxTaskEditor.js: clone of
UIxAppointmentEditor adapted for the handling of tasks.
* UI/WebServerResources/SchedulerUI.js: added support for tasks.
Scroll the daily view to the appropriate hour when an appointment
is selected in the appointments list.
File diff suppressed because it is too large Load Diff
+488
View File
@@ -0,0 +1,488 @@
/*
Copyright (C) 2004 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
// $Id: UIxTaskEditor.m 181 2004-08-11 15:13:25Z helge $
#include <SOGoUI/UIxComponent.h>
@interface UIxTaskProposal : UIxComponent
{
id item;
id currentDay;
/* individual values */
id startDateHour;
id startDateMinute;
id endDateHour;
id endDateMinute;
id startDateDay;
id startDateMonth;
id startDateYear;
id endDateDay;
id endDateMonth;
id endDateYear;
NSArray *participants; /* array of iCalPerson's */
NSArray *resources; /* array of iCalPerson's */
id duration;
NSArray *blockedRanges;
NSMutableDictionary *currentQueryParameters;
}
- (NSMutableDictionary *)currentQueryParameters;
- (NSDictionary *)currentHourQueryParametersForFirstHalf:(BOOL)_first;
- (void)setICalPersons:(NSArray *)_ps asQueryParameter:(NSString *)_qp;
@end
#include <SoObjects/Appointments/SOGoAppointmentFolder.h>
#include <SoObjects/Appointments/SOGoFreeBusyObject.h>
#include <NGExtensions/NGCalendarDateRange.h>
#include <NGCards/NGCards.h>
#include "common.h"
@implementation UIxTaskProposal
- (void)dealloc {
[self->blockedRanges release];
[self->startDateHour release];
[self->startDateMinute release];
[self->startDateDay release];
[self->startDateMonth release];
[self->startDateYear release];
[self->endDateHour release];
[self->endDateMinute release];
[self->endDateDay release];
[self->endDateMonth release];
[self->endDateYear release];
[self->duration release];
[self->participants release];
[self->resources release];
[self->currentQueryParameters release];
[super dealloc];
}
/* notifications */
- (void)sleep {
[self->currentDay release]; self->currentDay = nil;
[self->item release]; self->item = nil;
[super sleep];
}
/* accessors */
- (void)setItem:(id)_item {
ASSIGN(self->item, _item);
}
- (id)item {
return self->item;
}
- (void)setStartDateHour:(id)_startDateHour {
ASSIGN(self->startDateHour, _startDateHour);
}
- (id)startDateHour {
return self->startDateHour;
}
- (void)setStartDateMinute:(id)_startDateMinute {
ASSIGN(self->startDateMinute, _startDateMinute);
}
- (id)startDateMinute {
return self->startDateMinute;
}
- (void)setStartDateDay:(id)_startDateDay {
ASSIGN(self->startDateDay, _startDateDay);
}
- (id)startDateDay {
return self->startDateDay;
}
- (void)setStartDateMonth:(id)_startDateMonth {
ASSIGN(self->startDateMonth, _startDateMonth);
}
- (id)startDateMonth {
return self->startDateMonth;
}
- (void)setStartDateYear:(id)_startDateYear {
ASSIGN(self->startDateYear, _startDateYear);
}
- (id)startDateYear {
return self->startDateYear;
}
- (void)setEndDateHour:(id)_endDateHour {
ASSIGN(self->endDateHour, _endDateHour);
}
- (id)endDateHour {
return self->endDateHour;
}
- (void)setEndDateMinute:(id)_endDateMinute {
ASSIGN(self->endDateMinute, _endDateMinute);
}
- (id)endDateMinute {
return self->endDateMinute;
}
- (void)setEndDateDay:(id)_endDateDay {
ASSIGN(self->endDateDay, _endDateDay);
}
- (id)endDateDay {
return self->endDateDay;
}
- (void)setEndDateMonth:(id)_endDateMonth {
ASSIGN(self->endDateMonth, _endDateMonth);
}
- (id)endDateMonth {
return self->endDateMonth;
}
- (void)setEndDateYear:(id)_endDateYear {
ASSIGN(self->endDateYear, _endDateYear);
}
- (id)endDateYear {
return self->endDateYear;
}
- (void)setStartDate:(NSCalendarDate *)_date {
[self setStartDateHour:[NSNumber numberWithInt:[_date hourOfDay]]];
[self setStartDateMinute:[NSNumber numberWithInt:[_date minuteOfHour]]];
[self setStartDateDay:[NSNumber numberWithInt:[_date dayOfMonth]]];
[self setStartDateMonth:[NSNumber numberWithInt:[_date monthOfYear]]];
[self setStartDateYear:[NSNumber numberWithInt:[_date yearOfCommonEra]]];
}
- (NSCalendarDate *)startDate {
return [NSCalendarDate dateWithYear:[[self startDateYear] intValue]
month:[[self startDateMonth] intValue]
day:[[self startDateDay] intValue]
hour:[[self startDateHour] intValue]
minute:[[self startDateMinute] intValue]
second:0
timeZone:[[self clientObject] userTimeZone]];
}
- (void)setEndDate:(NSCalendarDate *)_date {
[self setEndDateHour:[NSNumber numberWithInt:[_date hourOfDay]]];
[self setEndDateMinute:[NSNumber numberWithInt:[_date minuteOfHour]]];
[self setEndDateDay:[NSNumber numberWithInt:[_date dayOfMonth]]];
[self setEndDateMonth:[NSNumber numberWithInt:[_date monthOfYear]]];
[self setEndDateYear:[NSNumber numberWithInt:[_date yearOfCommonEra]]];
}
- (NSCalendarDate *)endDate {
return [NSCalendarDate dateWithYear:[[self endDateYear] intValue]
month:[[self endDateMonth] intValue]
day:[[self endDateDay] intValue]
hour:[[self endDateHour] intValue]
minute:[[self endDateMinute] intValue]
second:59
timeZone:[[self clientObject] userTimeZone]];
}
- (void)setDuration:(id)_duration {
ASSIGN(self->duration, _duration);
}
- (id)duration {
return self->duration;
}
- (int)durationInMinutes {
return [[self duration] intValue];
}
- (NSTimeInterval)durationAsTimeInterval {
return [self durationInMinutes] * 60;
}
- (NSString *)itemDurationText {
// TODO: use a formatter
// TODO: localize
switch ([[self item] intValue]) {
case 30: return @"30 minutes";
case 60: return @"1 hour";
case 120: return @"2 hours";
case 240: return @"4 hours";
case 480: return @"8 hours";
default:
return [NSString stringWithFormat:@"%@ minutes", [self item]];
}
}
- (void)setParticipants:(NSArray *)_parts {
ASSIGN(self->participants, _parts);
}
- (NSArray *)participants {
return self->participants;
}
- (void)setResources:(NSArray *)_res {
ASSIGN(self->resources, _res);
}
- (NSArray *)resources {
return self->resources;
}
- (NSArray *)attendees {
NSArray *a, *b;
a = [self participants];
b = [self resources];
if ([b count] == 0) return a;
if ([a count] == 0) return b;
return [a arrayByAddingObjectsFromArray:b];
}
- (void)setCurrentDay:(id)_day {
ASSIGN(self->currentDay, _day);
}
- (id)currentDay {
return self->currentDay;
}
- (NSArray *)hours {
// TODO: from 'earliest start' to 'latest endtime'
unsigned lStartHour = 9, lEndHour = 17, i;
NSMutableArray *ma;
lStartHour = [[self startDateHour] intValue];
lEndHour = [[self endDateHour] intValue];
if (lStartHour < 1) lStartHour = 1;
if (lEndHour < lStartHour) lEndHour = lStartHour + 1;
ma = [NSMutableArray arrayWithCapacity:lEndHour - lStartHour + 2];
for (i = lStartHour; i <= lEndHour; i++)
[ma addObject:[NSNumber numberWithInt:i]];
return ma;
}
- (NSArray *)days {
// TODO: from startdate to enddate
NSMutableArray *ma;
NSCalendarDate *base, *stop, *current;
base = [NSCalendarDate dateWithYear:[[self startDateYear] intValue]
month:[[self startDateMonth] intValue]
day:[[self startDateDay] intValue]
hour:12 minute:0 second:0
timeZone:[[self clientObject] userTimeZone]];
stop = [NSCalendarDate dateWithYear:[[self endDateYear] intValue]
month:[[self endDateMonth] intValue]
day:[[self endDateDay] intValue]
hour:12 minute:0 second:0
timeZone:[[self clientObject] userTimeZone]];
ma = [NSMutableArray arrayWithCapacity:16];
current = base;
while ([current compare:stop] != NSOrderedDescending) {
[current setTimeZone:[[self clientObject] userTimeZone]];
[ma addObject:current];
/* Note: remember the timezone behaviour of the method below! */
current = [current dateByAddingYears:0 months:0 days:1];
}
return ma;
}
- (NSArray *)durationSteps {
// TODO: make configurable
return [NSArray arrayWithObjects:
@"30", @"60", @"120", @"240", @"480", nil];
}
/* slots */
- (BOOL)isRangeGreen:(NGCalendarDateRange *)_range {
unsigned idx;
idx = [self->blockedRanges indexOfFirstIntersectingDateRange:_range];
if (idx != NSNotFound) {
[self debugWithFormat:@"blocked range:\n range: %@\n block: %@\nintersection:%@",
_range,
[self->blockedRanges objectAtIndex:idx],
[_range intersectionDateRange:[self->blockedRanges objectAtIndex:idx]]];
}
return idx == NSNotFound ? YES : NO;
}
- (BOOL)isSlotRangeGreen:(NGCalendarDateRange *)_slotRange {
NGCalendarDateRange *aptRange;
NSCalendarDate *aptStartDate, *aptEndDate;
if (_slotRange == nil)
return NO;
/* calculate the interval requested by the user (can be larger) */
aptStartDate = [_slotRange startDate];
// TODO: gives warning on MacOSX
aptEndDate = [[NSCalendarDate alloc] initWithTimeIntervalSince1970:
[aptStartDate timeIntervalSince1970]
+ [self durationAsTimeInterval]];
[aptStartDate setTimeZone:[[self clientObject] userTimeZone]];
[aptEndDate setTimeZone:[[self clientObject] userTimeZone]];
aptRange = [NGCalendarDateRange calendarDateRangeWithStartDate:aptStartDate
endDate:aptEndDate];
[aptEndDate release]; aptEndDate = nil;
return [self isRangeGreen:aptRange];
}
- (BOOL)isFirstHalfGreen {
/* currentday is the date, self->item the hour */
NSCalendarDate *from, *to;
NGCalendarDateRange *range;
from = [self->currentDay hour:[[self item] intValue] minute:0];
to = [self->currentDay hour:[[self item] intValue] minute:30];
range = [NGCalendarDateRange calendarDateRangeWithStartDate:from endDate:to];
return [self isSlotRangeGreen:range];
}
- (BOOL)isSecondHalfGreen {
/* currentday is the date, self->item the hour */
NSCalendarDate *from, *to;
NGCalendarDateRange *range;
from = [self->currentDay hour:[[self item] intValue] minute:30];
to = [self->currentDay hour:[[self item] intValue] + 1 minute:0];
range = [NGCalendarDateRange calendarDateRangeWithStartDate:from endDate:to];
return [self isSlotRangeGreen:range];
}
- (BOOL)isFirstHalfBlocked {
return [self isFirstHalfGreen] ? NO : YES;
}
- (BOOL)isSecondHalfBlocked {
return [self isSecondHalfGreen] ? NO : YES;
}
/* actions */
- (BOOL)shouldTakeValuesFromRequest:(WORequest *)_rq inContext:(WOContext*)_c{
return YES;
}
- (id<WOActionResults>)defaultAction {
NSCalendarDate *now;
now = [NSCalendarDate date];
[self setDuration:@"120"]; /* 1 hour as default */
[self setStartDate:[now hour:9 minute:0]];
[self setEndDate:[now hour:18 minute:0]];
return self;
}
- (id)proposalSearchAction {
NSArray *attendees, *uids, *fbos, *ranges;
unsigned i, count;
NSMutableArray *allInfos;
[self logWithFormat:@"search from %@ to %@",
[self startDate], [self endDate]];
attendees = [self attendees];
uids = [[self clientObject] uidsFromICalPersons:attendees];
if ([uids count] == 0) {
[self logWithFormat:@"Note: no UIDs selected."];
return self;
}
fbos = [[self clientObject] lookupFreeBusyObjectsForUIDs:uids
inContext:[self context]];
count = [fbos count];
// wild guess at capacity
allInfos = [[[NSMutableArray alloc] initWithCapacity:count * 10] autorelease];
for (i = 0; i < count; i++) {
SOGoFreeBusyObject *fb;
NSArray *infos;
fb = [fbos objectAtIndex:i];
if (fb != (SOGoFreeBusyObject *)[NSNull null]) {
infos = [fb fetchFreebusyInfosFrom:[self startDate] to:[self endDate]];
[allInfos addObjectsFromArray:infos];
}
}
[self debugWithFormat:@" processing: %d infos", [allInfos count]];
ranges = [allInfos arrayByCreatingDateRangesFromObjectsWithStartDateKey:
@"startDate"
andEndDateKey:@"endDate"];
ranges = [ranges arrayByCompactingContainedDateRanges];
[self debugWithFormat:@" ranges: %@", ranges];
ASSIGNCOPY(self->blockedRanges, ranges);
return self;
}
/* URLs */
- (NSMutableDictionary *)currentQueryParameters {
if(!self->currentQueryParameters) {
self->currentQueryParameters = [[self queryParameters] mutableCopy];
[self->currentQueryParameters setObject:[self duration] forKey:@"dur"];
[self setICalPersons:[self resources] asQueryParameter:@"rs"];
[self setICalPersons:[self participants] asQueryParameter:@"ps"];
}
return self->currentQueryParameters;
}
- (void)setICalPersons:(NSArray *)_ps asQueryParameter:(NSString *)_qp {
NSMutableString *s;
unsigned i, count;
s = [[NSMutableString alloc] init];
count = [_ps count];
for(i = 0; i < count; i++) {
iCalPerson *p = [_ps objectAtIndex:i];
[s appendString:[p rfc822Email]];
if(i != (count - 1))
[s appendString:@","];
}
[[self currentQueryParameters] setObject:s forKey:_qp];
[s release];
}
- (NSDictionary *)currentHourQueryParametersForFirstHalf:(BOOL)_first {
NSMutableDictionary *qp;
NSString *hmString;
NSCalendarDate *date;
unsigned minute;
minute = _first ? 0 : 30;
qp = [self currentQueryParameters];
date = [self currentDay];
hmString = [NSString stringWithFormat:@"%02d%02d",
[item intValue], minute];
[self setSelectedDateQueryParameter:date inDictionary:qp];
[qp setObject:hmString forKey:@"hm"];
return qp;
}
- (NSDictionary *)currentFirstHalfQueryParameters {
return [self currentHourQueryParametersForFirstHalf:YES];
}
- (NSDictionary *)currentSecondHalfQueryParameters {
return [self currentHourQueryParametersForFirstHalf:NO];
}
@end /* UIxTaskProposal */
+40
View File
@@ -0,0 +1,40 @@
// $Id: UIxTaskView.h 768 2005-07-15 00:13:01Z helge $
#ifndef __SOGo_UIxTaskView_H__
#define __SOGo_UIxTaskView_H__
#include <SOGoUI/UIxComponent.h>
@class NSCalendarDate;
@class iCalToDo;
@class iCalPerson;
@class SOGoDateFormatter;
@interface UIxTaskView : UIxComponent
{
iCalToDo* task;
iCalPerson* attendee;
SOGoDateFormatter *dateFormatter;
id item;
}
- (iCalToDo *) task;
/* permissions */
- (BOOL)canAccessApt;
- (BOOL)canEditApt;
- (SOGoDateFormatter *)dateFormatter;
- (NSCalendarDate *)startTime;
- (NSCalendarDate *)endTime;
- (NSString *)attributesTabLink;
- (NSString *)participantsTabLink;
- (NSString *)completeHrefForMethod:(NSString *)_method
withParameter:(NSString *)_param
forKey:(NSString *)_key;
@end
#endif /* __SOGo_UIxTaskView_H__ */
+296
View File
@@ -0,0 +1,296 @@
/*
Copyright (C) 2004-2005 SKYRIX Software AG
This file is part of OpenGroupware.org.
OGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with OGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#import "UIxTaskView.h"
#import <NGCards/NGCards.h>
#import <SOGo/WOContext+Agenor.h>
#import <Appointments/SOGoTaskObject.h>
#import <SOGoUI/SOGoDateFormatter.h>
#import "UIxComponent+Agenor.h"
#import "common.h"
@interface UIxTaskView (PrivateAPI)
- (BOOL)isAttendeeActiveUser;
@end
@implementation UIxTaskView
- (void)dealloc {
[task release];
[attendee release];
[dateFormatter release];
[item release];
[super dealloc];
}
/* accessors */
- (NSString *)tabSelection {
NSString *selection;
selection = [self queryParameterForKey:@"tab"];
if (selection == nil)
selection = @"attributes";
return selection;
}
- (void)setAttendee:(id)_attendee {
ASSIGN(attendee, _attendee);
}
- (id)attendee {
return attendee;
}
- (BOOL)isAttendeeActiveUser {
NSString *email, *attEmail;
email = [[[self context] activeUser] email];
attendee = [self attendee];
attEmail = [attendee rfc822Email];
return [email isEqualToString: attEmail];
}
- (BOOL)showAcceptButton {
return [[self attendee] participationStatus] != iCalPersonPartStatAccepted;
}
- (BOOL)showRejectButton {
return [[self attendee] participationStatus] != iCalPersonPartStatDeclined;
}
- (NSString *)attendeeStatusColspan {
return [self isAttendeeActiveUser] ? @"1" : @"2";
}
- (void)setItem:(id)_item {
ASSIGN(item, _item);
}
- (id)item {
return item;
}
- (SOGoDateFormatter *)dateFormatter {
if (dateFormatter == nil) {
dateFormatter =
[[SOGoDateFormatter alloc] initWithLocale:[self locale]];
[dateFormatter setFullWeekdayNameAndDetails];
}
return dateFormatter;
}
- (NSCalendarDate *)startTime {
NSCalendarDate *date;
date = [[self task] startDate];
[date setTimeZone:[[self clientObject] userTimeZone]];
return date;
}
- (NSCalendarDate *)endTime {
NSCalendarDate *date;
date = [[self task] due];
[date setTimeZone:[[self clientObject] userTimeZone]];
return date;
}
- (NSString *)resourcesAsString {
NSArray *resources, *cns;
resources = [[self task] resources];
cns = [resources valueForKey:@"cnForDisplay"];
return [cns componentsJoinedByString:@"<br />"];
}
- (NSString *) categoriesAsString
{
NSEnumerator *categories;
NSArray *rawCategories;
NSMutableArray *l10nCategories;
NSString *currentCategory, *l10nCategory;
rawCategories
= [[task categories] componentsSeparatedByString: @","];
l10nCategories = [NSMutableArray arrayWithCapacity: [rawCategories count]];
categories = [rawCategories objectEnumerator];
currentCategory = [categories nextObject];
while (currentCategory)
{
l10nCategory
= [self labelForKey: [currentCategory stringByTrimmingSpaces]];
if (l10nCategory)
[l10nCategories addObject: l10nCategory];
currentCategory = [categories nextObject];
}
return [l10nCategories componentsJoinedByString: @", "];
}
// task.organizer.cnForDisplay
- (NSString *) eventOrganizer
{
CardElement *organizer;
organizer = [[self task] uniqueChildWithTag: @"organizer"];
return [organizer value: 0 ofAttribute: @"cn"];
}
- (NSString *) priorityLabelKey
{
return [NSString stringWithFormat: @"prio_%@", [task priority]];
}
/* backend */
- (iCalToDo *) task
{
NSString *iCalString;
iCalCalendar *calendar;
SOGoTaskObject *clientObject;
if (task != nil)
return task;
clientObject = [self clientObject];
iCalString = [[self clientObject] valueForKey:@"iCalString"];
if (![iCalString isNotNull] || [iCalString length] == 0) {
[self errorWithFormat:@"(%s): missing iCal string!",
__PRETTY_FUNCTION__];
return nil;
}
calendar = [iCalCalendar parseSingleFromSource: iCalString];
task = [clientObject firstTaskFromCalendar: calendar];
[task retain];
return task;
}
/* hrefs */
- (NSString *)attributesTabLink {
return [self completeHrefForMethod:[self ownMethodName]
withParameter:@"attributes"
forKey:@"tab"];
}
- (NSString *)participantsTabLink {
return [self completeHrefForMethod:[self ownMethodName]
withParameter:@"participants"
forKey:@"tab"];
}
- (NSString *)debugTabLink {
return [self completeHrefForMethod:[self ownMethodName]
withParameter:@"debug"
forKey:@"tab"];
}
- (NSString *)completeHrefForMethod:(NSString *)_method
withParameter:(NSString *)_param
forKey:(NSString *)_key
{
NSString *href;
[self setQueryParameter:_param forKey:_key];
href = [self completeHrefForMethod:[self ownMethodName]];
[self setQueryParameter:nil forKey:_key];
return href;
}
/* access */
- (BOOL)isMyApt {
NSString *email;
iCalPerson *organizer;
email = [[[self context] activeUser] email];
organizer = [[self task] organizer];
if (!organizer) return YES; // assume this is correct to do, right?
return [[organizer rfc822Email] isEqualToString:email];
}
- (BOOL)canAccessApt {
NSString *email;
NSArray *partMails;
if ([self isMyApt])
return YES;
/* not my apt - can access if it's public */
if ([[[self task] accessClass] isEqualToString: @"PUBLIC"])
return YES;
/* can access it if I'm invited :-) */
email = [[[self context] activeUser] email];
partMails = [[[self task] participants] valueForKey:@"rfc822Email"];
return [partMails containsObject:email];
}
- (BOOL)canEditApt {
return [self isMyApt];
}
/* action */
- (id<WOActionResults>)defaultAction {
if ([self task] == nil) {
return [NSException exceptionWithHTTPStatus:404 /* Not Found */
reason:@"could not locate task"];
}
return self;
}
- (BOOL)isDeletableClientObject {
return [[self clientObject] respondsToSelector:@selector(delete)];
}
- (id)deleteAction {
NSException *ex;
id url;
if ([self task] == nil) {
return [NSException exceptionWithHTTPStatus:404 /* Not Found */
reason:@"could not locate task"];
}
if (![self isDeletableClientObject]) {
/* return 400 == Bad Request */
return [NSException exceptionWithHTTPStatus:400
reason:@"method cannot be invoked on "
@"the specified object"];
}
if ((ex = [[self clientObject] delete]) != nil) {
// TODO: improve error handling
[self debugWithFormat:@"failed to delete: %@", ex];
return ex;
}
url = [[[self clientObject] container] baseURLInContext:[self context]];
return [self redirectToLocation:url];
}
@end /* UIxTaskView */