[Kolab-devel] [issue3847] usability improvement while editing start/end dates for events

issues at kolab.org issues at kolab.org
Thu Sep 10 10:07:40 CEST 2009


New submission from Sönke Schwardt-Krummrich <schwardt at univention.de>:

This patch consists of two parts:
1) Event's start and end time had to be entered as hour and minutes seperately.
This patch tries to improve usability by hiding those two text input fields
if browser is dom-capable. Additionaly a new widget is shown that combines
the function of the hidden fields:
- hours and minutes can be entered in one text field
- time can be selected via drop down list by clicking new button next to
  text field.

2) If checkbox "whole day" is activated, this patch hides all time input
   field (start time, end time and hrs/mins for duration). Duration is then
   counted in days.

----------
files: t_kronolith_HK_UV_timepicker_and_timespan_wholeday.diff
keyword: web client
messages: 21580
nosy: schwardt
priority: feature
status: unread
title: usability improvement while editing start/end dates for events

______________________________________
Kolab issue tracker <issues at kolab.org>
<https://issues.kolab.org/issue3847>
______________________________________
-------------- next part --------------
Patch by schwardt at univention.de (Wed Sep 9 11:01:23 2009 +0200):

kronolith: usability improvement while editing start/end dates for events

This patch consists of two parts:
1) Event's start and end time had to be entered as hour and minutes seperately.
This patch tries to improve usability by hiding those two text input fields
if browser is dom-capable. Additionaly a new widget is shown that combines
the function of the hidden fields:
- hours and minutes can be entered in one text field
- time can be selected via drop down list by clicking new button next to
  text field.

2) If checkbox "whole day" is activated, this patch hides all time input
   field (start time, end time and hrs/mins for duration). Duration is then
   counted in days.


--- a/horde-webmail/kronolith/lib/Driver.php
+++ b/horde-webmail/kronolith/lib/Driver.php
@@ -1264,6 +1264,13 @@ class Kronolith_Event {
 
         if ($this->isInitialized()) {
             require_once 'Date/Calc.php';
+            if ($this->end->hour == 0 && $this->start->hour == 0
+                && $this->end->min == 0 && $this->start->min == 0) {
+                $whole_day_match = true;
+            } else {
+                $whole_day_match = false;
+            }
+
             $dur_day_match = Date_Calc::dateDiff($this->start->mday,
                                                  $this->start->month,
                                                  $this->start->year,
@@ -1280,14 +1287,9 @@ class Kronolith_Event {
                 $dur_hour_match += 24;
                 --$dur_day_match;
             }
-            if ($dur_hour_match == 0 && $dur_min_match == 0
-                && $this->end->mday - $this->start->mday == 1) {
-                $dur_day_match = 0;
-                $dur_hour_match = 23;
-                $dur_min_match = 60;
-                $whole_day_match = true;
-            } else {
-                $whole_day_match = false;
+            if ($whole_day_match) {
+                $this->end->mday -= 1;
+                $this->end->correct();
             }
         } else {
             $dur_day_match = 0;
@@ -1781,8 +1783,8 @@ class Kronolith_Event {
             if (Util::getFormData('whole_day') == 1) {
                 $start_hour = 0;
                 $start_min = 0;
-                $dur_day = 0;
-                $dur_hour = 24;
+                $dur_day = (int)Util::getFormData('dur_day');
+                $dur_hour = 0;
                 $dur_min = 0;
             } else {
                 $dur_day = (int)Util::getFormData('dur_day');
@@ -1816,6 +1818,10 @@ class Kronolith_Event {
             $end_min = Util::getFormData('end_min');
             $end_am_pm = Util::getFormData('end_am_pm');
 
+            if (Util::getFormData('whole_day') == 1) {
+               $end_day += 1;
+            }
+
             if (!$prefs->getValue('twentyFour')) {
                 if ($end_am_pm == 'PM') {
                     if ($end_hour != 12) {
@@ -1964,7 +1970,7 @@ class Kronolith_Event {
             for ($i = $hour_min; $i < $hour_max; ++$i) {
                 $options[$i] = $i;
             }
-            $attributes = ' onchange="document.eventform.whole_day.checked = false; updateEndDate();"';
+            $attributes = ' onchange="updateEndDate();"';
             $label = _("Start Hour");
             break;
 
@@ -1974,7 +1980,7 @@ class Kronolith_Event {
                 $min = sprintf('%02d', $i * 5);
                 $options[$min] = $min;
             }
-            $attributes = ' onchange="document.eventform.whole_day.checked = false; updateEndDate();"';
+            $attributes = ' onchange="updateEndDate();"';
             $label = _("Start Minute");
             break;
 
@@ -2116,7 +2122,7 @@ class Kronolith_Event {
         case 'start[year]':
         case 'start[day]':
         case 'start':
-            return 'updateWday(\'start_wday\'); document.eventform.whole_day.checked = false; updateEndDate();';
+            return 'updateWday(\'start_wday\'); updateEndDate();';
 
         case 'end[month]':
         case 'end[year]':
@@ -2133,7 +2139,7 @@ class Kronolith_Event {
         case 'dur_day':
         case 'dur_hour':
         case 'dur_min':
-            return 'document.eventform.whole_day.checked = false; updateEndDate(); document.eventform.end_or_dur[1].checked = true;';
+            return 'updateEndDate(); document.eventform.end_or_dur[1].checked = true;';
         }
     }
 
--- a/horde-webmail/kronolith/templates/edit/edit_timespan.inc
+++ b/horde-webmail/kronolith/templates/edit/edit_timespan.inc
@@ -5,6 +5,16 @@
  </td>
 </tr>
 
+<!-- whole day -->
+<tr>
+ <td> </td>
+ <td> </td>
+ <td colspan="3">
+   <input id="allday" name="whole_day" type="checkbox" class="checkbox" value="1" onclick="setWholeDay(this.checked); updateEndDate(); $('duration').checked = true"<?php $dur = $event->getDuration(); if ($dur->wholeDay) echo ' checked="checked"' ?> />
+     <?php echo Horde::label('allday', _("All day")) ?><br />
+ </td>
+</tr>
+
 <!-- start date -->
 <tr>
  <td class="rightAlign">
@@ -24,6 +34,7 @@
   </script>
  </td>
  <td>
+<?php Horde::addScriptFile('time_picker.js', 'horde'); ?>
 <?php Horde::addScriptFile('open_calendar.js', 'horde'); ?>
   <div id="goto" style="display:none"></div>
 <?php echo Horde::link('#', _("Select a date"), '', '', 'openCalendar(\'startimg\', \'start\', \'' . addslashes($event->js('start')) . '\'); return false;') . Horde::img('calendar.png', _("Set start date"), 'id="startimg"', $GLOBALS['registry']->getImageDir('horde')) . '</a>'; endif; ?>
@@ -31,12 +42,18 @@
 </tr>
 
 <!-- start time -->
-<tr>
+<tr id="start_time_row">
  <td> </td>
  <td colspan="2" class="rightAlign"><?php echo Horde::label('start_hour', _("At")) ?> </td>
  <td colspan="2">
-  <?php echo $event->html('start_hour') ?> : <?php echo $event->html('start_min') ?>
-  <?php
+  <?php if (!$GLOBALS['browser']->hasFeature('dom')) {
+   echo $event->html('start_hour') . " : " . $event->html('start_min');
+  } else  {
+     echo "<input type='text' size='5' maxlength='5' id='start_time' value='" . sprintf("%02d", $event->start->hour) . ":" . sprintf("%02d", $event->start->min) . "' onchange='javascript:updateTimeHourMin(\"start_time\",\"start_hour\",\"start_min\", \"" . _("Please enter correct start time!") . "\"); updateEndDate(); updateHourMinTime(\"end_hour\",\"end_min\",\"end_time\");' />";
+     echo Horde::link('#', _("Select a time"), '', '', 'openTimePicker(\'start_time\', \'start_time\', \'' . $GLOBALS['prefs']->getValue('twentyFour') . '\'); return false;') . Horde::img('dropdownbtn.png', _("Set start time"), 'id="startimg"', $GLOBALS['registry']->getImageDir('horde')) . '</a>';
+
+
+  } 
   if (!$GLOBALS['prefs']->getValue('twentyFour')) {
       if ($event->start->hour < 12) {
           $am = ' checked="checked"';
@@ -48,7 +65,10 @@
   ?>
   <input id="am" type="radio" class="checkbox" name="am_pm" value="AM"<?php echo $am ?> onclick="$('allday').checked=false;updateEndDate();" /><label for="am" onclick="$('allday').checked=false;updateEndDate();">AM</label>
   <input id="pm" type="radio" class="checkbox" name="am_pm" value="PM"<?php echo $pm ?> onclick="$('allday').checked=false;updateEndDate();" /><label for="pm" onclick="$('allday').checked=false;updateEndDate();">PM</label>
-  <?php } ?>
+  <?php
+   }
+    echo str_replace('<select ', '<select style="visibility: hidden;" ', $event->html('start_hour'))  . str_replace('<select ', '<select style="visibility: hidden;" ', $event->html('start_min'));
+    ?>
  </td>
 </tr>
 
@@ -73,7 +93,7 @@
    updateWday('end_wday');
   </script>
   </td>
-  <td rowspan="2" valign="top">
+  <td valign="top">
 <?php
     Horde::addScriptFile('open_calendar.js', 'horde');
     echo Horde::link('#', _("Select a date"), '', '', 'openCalendar(\'endimg\', \'end\', \'' . addslashes($event->js('end')) . '\'); return false;') . Horde::img('calendar.png', _("Set end date"), 'id="endimg"', $GLOBALS['registry']->getImageDir('horde')) . '</a>';
@@ -83,15 +103,21 @@ endif;
 </tr>
 
 <!-- end time -->
-<tr>
+<tr id="end_time_row">
  <td> </td>
  <td> </td>
  <td class="rightAlign">
   <?php echo _("At") ?> 
  </td>
  <td colspan="2">
-  <?php echo $event->html('end_hour') ?> : <?php echo $event->html('end_min') ?>
-  <?php
+  <?php if (!$GLOBALS['browser']->hasFeature('dom')) {
+         echo $event->html('end_hour') . " : " . $event->html('end_min');
+  } else  {
+     echo "<input type='text' size='5' maxlength='5' id='end_time' value='" . sprintf("%02d", $event->end->hour) . ":" . sprintf("%02d", $event->end->min) . "' onchange='javascript:updateTimeHourMin(\"end_time\",\"end_hour\",\"end_min\", \"" . _("Please enter correct end time!") . "\"); updateDuration(); document.eventform.end_or_dur[0].checked = true' />";
+
+     echo Horde::link('#', _("Select a time"), '', '', 'openTimePicker(\'end_time\', \'end_time\', \'' . $GLOBALS['prefs']->getValue('twentyFour') . '\'); updateTimeHourMin(\'end_time\',\'end_hour\',\'end_min\', \'' . _("Please enter correct end time!") . '\'); updateDuration(); document.eventform.end_or_dur[0].checked = true; return false;') . Horde::img('dropdownbtn.png', _("Set start time"), 'id="endimg"', $GLOBALS['registry']->getImageDir('horde')) . '</a>';
+
+  } 
   if (!$GLOBALS['prefs']->getValue('twentyFour')) {
       if ($event->end->hour < 12) {
           $am = ' checked="checked"';
@@ -103,7 +129,10 @@ endif;
   ?>
   <input id="eam" type="radio" class="checkbox" name="end_am_pm" value="AM"<?php echo $am ?> onclick="$('end').checked=true;updateDuration()" /><label for="eam" onclick="$('end').checked=true;updateDuration()">AM</label>
   <input id="epm" type="radio" class="checkbox" name="end_am_pm" value="PM"<?php echo $pm ?> onclick="$('end').checked=true;updateDuration()" /><label for="epm" onclick="$('end').checked=true;updateDuration()">PM</label>
-  <?php } ?>
+  <?php
+   }
+   echo str_replace('<select ', '<select style="visibility: hidden;" ', $event->html('end_hour'))  . str_replace('<select ', '<select style="visibility: hidden;" ', $event->html('end_min')); 
+   ?>
  </td>
 </tr>
 
@@ -115,12 +144,11 @@ endif;
  </td>
  <td> </td>
  <td valign="top">
-  <input id="allday" name="whole_day" type="checkbox" class="checkbox" value="1" onclick="setWholeDay(this.value); updateEndDate(); $('duration').checked = true"<?php $dur = $event->getDuration(); if ($dur->wholeDay) echo ' checked="checked"' ?> />
-  <?php echo Horde::label('allday', _("All day")) ?><br />
-  <?php printf(_("%s Day(s) %s Hour(s) %s Minutes"), $event->html('dur_day'), $event->html('dur_hour'), $event->html('dur_min')) ?>
+   <?php $duration_div_start = '<div id="duration_div_hrs_mins">'; ?>
+<?php printf(_("%s Day(s) %s%s Hour(s) %s Minutes%s"), $event->html('dur_day'), $duration_div_start, $event->html('dur_hour'), $event->html('dur_min'), "</div>") ?>
 <?php if ($GLOBALS['browser']->hasFeature('dom')): ?>
  </td>
- <td rowspan="2"> 
+ <td> 
 <?php endif; ?>
  </td>
 </tr>
--- a/horde-webmail/kronolith/templates/edit/javascript.inc
+++ b/horde-webmail/kronolith/templates/edit/javascript.inc
@@ -4,6 +4,31 @@
 
 <?php if (!Util::nonInputVar('issearch')): ?>
 
+
+function formatTime5char(hrs,mins) {
+<?php if (!$GLOBALS['prefs']->getValue('twentyFour')): ?>
+  if (hrs < 12) {
+      if (hrs == 0) {
+          hrs = 12;
+      }
+  } else {
+      if (hrs > 12) {
+          hrs -= 12;
+      }
+  }
+<?php endif; ?>
+
+  res = mins;
+  if (mins < 10) {
+	res = "0" + res;
+  }
+  res = hrs + ":" + res;
+  if (hrs < 10) {
+	res = "0" + res;
+  }
+  return res;
+}
+
 function setInterval(field)
 {
     elt = document.eventform[field];
@@ -45,18 +70,28 @@ function clearFields(index)
 
 function setWholeDay(wholeDay)
 {
-    if (wholeDay == 1) {
+    if (wholeDay == true) {
+        $('duration_div_hrs_mins', 'end_time_row', 'start_time_row').invoke('hide');
 <?php if ($GLOBALS['prefs']->getValue('twentyFour')): ?>
         $('start_hour').selectedIndex = 0;
+        $('start_time').value = '00:00';
+        $('end_time').value = '00:00';
 <?php else: ?>
         $('start_hour').selectedIndex = 11;
+        $('start_time').value = '12:00';
+        $('end_time').value = '12:00';
         $('am').checked = true;
 <?php endif; ?>
         $('start_min').selectedIndex = 0;
-        $('dur_day').value = 0;
-        $('dur_hour').selectedIndex = 23;
-        $('dur_min').selectedIndex = 12;
+        if ($('dur_day').value < 1) {
+            $('dur_day').value = 1;
+            $('dur_hour').selectedIndex = 0;
+            $('dur_min').selectedIndex = 0;
+        }
+    } else {
+            $('duration_div_hrs_mins', 'end_time_row', 'start_time_row').invoke('show');
     }
+
 }
 
 function updateWday(span)
@@ -161,14 +196,21 @@ function updateDuration()
 <?php endif; ?>
     }
     var duration = (endDate - startDate) / 1000;
-    $('dur_day').value = Math.floor(duration / 86400);
-    duration %= 86400;
-    var durHour = Math.floor(duration / 3600);
-    duration %= 3600;
-    var durMin = Math.floor(duration / 60 / 5);
-    $('dur_hour').selectedIndex = durHour;
-    $('dur_min').selectedIndex = durMin;
-    $('allday').checked = false;
+	var durHour = 0;
+	var durMin = 0;
+	if ($('allday').checked == true) {
+	  // whole day event
+	  $('dur_day').value = Math.floor(duration / 86400) + 1;
+	} else {
+	  // no whole day event
+	  $('dur_day').value = Math.floor(duration / 86400);
+	  duration %= 86400;
+	  var durHour = Math.floor(duration / 3600);
+	  duration %= 3600;
+	  var durMin = Math.floor(duration / 60 / 5);
+	}
+	$('dur_hour').selectedIndex = durHour;
+	$('dur_min').selectedIndex = durMin;
     if (failed) {
         updateEndDate();
     }
@@ -187,7 +229,16 @@ function updateEndDate()
                              startHour,
                              $F('start_min'));
     var endDate = new Date();
-    var minutes = $F('dur_day') * 1440;
+	if ($('allday').checked == true) {
+	  // whole day event
+	  if ( $('dur_day').value < 1 ) {
+		alert('<?php echo addslashes(_("Duration must be at least 1 day.")) ?>');
+		$('dur_day').value = 1;
+	  }
+	  var minutes = ( $F('dur_day') - 1) * 1440;
+	} else {
+	  var minutes = $F('dur_day') * 1440;
+	}
     minutes += $F('dur_hour') * 60;
     minutes += parseInt($F('dur_min'));
     var msecs = minutes * 60000;
@@ -216,6 +267,7 @@ function updateEndDate()
 <?php endif; ?>
     $('end_hour').selectedIndex = endHour;
     $('end_min').selectedIndex = endDate.getMinutes() / 5;
+	$('end_time').value = formatTime5char(endDate.getHours(),endDate.getMinutes());
     updateWday('end_wday');
 }
 
@@ -272,6 +324,7 @@ Event.observe(window, 'load', function() {
 <?php if ($GLOBALS['conf']['metadata']['keywords']): ?>
     toggleSection('keywords');
 <?php endif; ?>
+    setWholeDay( $('allday').checked );
 });
 
 </script>
new file mode 100644
--- /dev/null
+++ b/horde-webmail/templates/javascript/time_picker.js
@@ -0,0 +1,196 @@
+var TimePicker = {
+	targetId: null,
+	useTwentyFour: true,
+	callback_running: false,
+
+	draw: function(posId, targetId)
+	{
+		TimePicker.targetId = targetId;
+		var div = document.getElementById('goto');
+		if (div.firstChild) {
+			div.removeChild(div.firstChild);
+		}
+
+		var select = document.createElement('SELECT');
+		select.id = "TimePickerSelect";
+		select.size = 8;
+		select.onchange = function() {
+			for (var i = 0; i < this.options.length; ++i) {
+				if (this.options[i].selected === true) {
+					var targetitem = document.getElementById( TimePicker.targetId );
+					targetitem.value = this.options[i].value;
+				}
+			}
+			targetitem.onchange();
+
+			var div = document.getElementById('goto');
+			div.style.display = 'none';
+			if (div.firstChild) {
+				div.removeChild(div.firstChild);
+			}
+			var iefix = document.getElementById('goto_iefix');
+			if (iefix) {
+				iefix.style.display = 'none';
+			}
+		};
+
+		minhrs = 1;
+		maxhrs = 12;
+		if (TimePicker.useTwentyFour) {
+			minhrs = 0;
+			maxhrs = 23;
+		}
+		for (var hrs = minhrs; hrs <= maxhrs; ++hrs) {
+			for (var mins = 0; mins < 60; mins = mins + 5) {
+				cell = document.createElement('OPTION');
+
+				cell.value = formatTime5char(hrs, mins);
+				cell.innerHTML = cell.value;
+
+				select.appendChild(cell);
+			}
+		}
+		div.appendChild(select);
+		div.style.display = '';
+		div.style.position = 'absolute';
+		div.style.visibility = 'visible';
+
+		// select current item
+		select.selectedIndex = 0;
+		select.options[0].selected = true;
+		var targetval = $( targetId ).value;
+		for (var i = 0; i <= select.options.length; ++i) {
+			if (select.options[i].value >= targetval) {
+				select.selectedIndex = i;
+				select.options[i].selected = true;
+				select.value = select.options[select.selectedIndex].value;
+				i = select.options.length;
+			}
+		}
+
+
+		// Position the popup every time in case of a different input,
+		// window sizing changes, etc.
+		var el = document.getElementById(posId);
+		var p = TimePicker.getAbsolutePosition(el);
+
+		if (p.x + div.offsetWidth > document.body.offsetWidth) {
+			l = (document.body.offsetWidth - 10 - div.offsetWidth);
+			if (l < 0) {
+				l = 0;
+			}
+			div.style.left = l + 'px';
+		} else {
+			div.style.left = p.x + 'px';
+		}
+		if (p.y + div.offsetHeight > document.body.offsetHeight) {
+			t = (document.body.offsetHeight - 10 - div.offsetHeight);
+			if (t < 0) {
+				t = 0;
+			}
+			div.style.top = t + 'px';
+		} else {
+			div.style.top = p.y + 'px';
+		}
+
+		// Browser sniff for IE taken from Prototype.
+		if (!!(window.attachEvent && !window.opera)) {
+			// Fix for IE and select elements.
+			iefix = document.getElementById('goto_iefix');
+			if (!iefix) {
+				iefix = document.createElement('IFRAME');
+				iefix.id = 'goto_iefix';
+				iefix.src = 'javascript:return false;';
+				iefix.scrolling = 'no';
+				iefix.frameborder = 0;
+				iefix.style.display = 'none';
+				iefix.style.position = 'absolute';
+				document.body.appendChild(iefix);
+			}
+			iefix.style.width = div.offsetWidth;
+			iefix.style.height = div.offsetHeight;
+			iefix.style.top = div.style.top;
+			iefix.style.left = div.style.left;
+
+			if (div.style.zIndex === '') {
+				div.style.zIndex = 2;
+				iefix.style.zIndex = 1;
+			} else {
+				iefix.style.zIndex = div.style.zIndex - 1;
+			}
+			iefix.style.display = '';
+		}
+	},
+
+	getAbsolutePosition: function(el)
+	{
+		var r = { x: el.offsetLeft, y: el.offsetTop };
+		if (el.offsetParent) {
+			var tmp = TimePicker.getAbsolutePosition(el.offsetParent);
+			r.x += tmp.x;
+			r.y += tmp.y;
+		}
+		return r;
+	},
+
+	setSelectValue: function(select, value)
+	{
+		select.value = value;
+		if (select.value != value) {
+			for (var i = 0; i < select.options.length; ++i) {
+				if (select.options[i].value == value) {
+					select.selectedIndex = i;
+					return true;
+				}
+			}
+		}
+		return false;
+	}
+};
+
+function updateHourMinTime(hourId, minId, timeId) {
+	timeitem = document.getElementById(timeId);
+	houritem = document.getElementById(hourId);
+	minitem = document.getElementById(minId);
+
+	hrs = houritem.value;
+	if (!TimePicker.useTwentyFour) {
+		if (hrs > 12) {
+			hrs = hrs - 12;
+		}
+	}
+
+	timeitem.value = formatTime5char(hrs, minitem.value);
+}
+
+function updateTimeHourMin(timeId, hourId, minId, msg) {
+	timeitem = document.getElementById(timeId);
+	houritem = document.getElementById(hourId);
+	minitem = document.getElementById(minId);
+
+	Ergebnis = timeitem.value.match(/^(2[0123]|[01]\d|\d):[0-5]\d$/);
+	if (!Ergebnis) {
+		if (!msg) {
+			msg = "Please enter correct time!";
+		}
+		alert(msg);
+		return ;
+	}
+
+	items = timeitem.value.split(':');
+	if (items[0].charAt(0) == '0') {
+		items[0] = items[0].charAt(1);
+	}
+	if (items[1].charAt(0) == '0') {
+		items[1] = items[1].charAt(1);
+	}
+	TimePicker.setSelectValue(houritem, parseInt(items[0], 10));
+	TimePicker.setSelectValue(minitem, parseInt(items[1], 10));
+}
+
+function openTimePicker(posId, targetId, useTwentyFour) {
+	if (!(useTwentyFour)) {
+		TimePicker.useTwentyFour = false;
+	}
+	TimePicker.draw(posId, targetId);
+}


More information about the devel mailing list