Browse Source

Support for date range filter

jasper tredgold 7 years ago
parent
commit
47ac2b8fde
23 changed files with 6933 additions and 1934 deletions
  1. 40 33
      caboto-web/src/main/webapp/index.jsp
  2. 58 46
      caboto-web/src/main/webapp/js/annotations.js
  3. 555 0
      caboto-web/src/main/webapp/js/datepicker/.datepicker.pdoc.yaml
  4. BIN
      caboto-web/src/main/webapp/js/datepicker/calendar.png
  5. 103 0
      caboto-web/src/main/webapp/js/datepicker/datepicker.css
  6. 1 0
      caboto-web/src/main/webapp/js/datepicker/datepicker.js
  7. 1570 0
      caboto-web/src/main/webapp/js/datepicker/datepicker_src.js
  8. BIN
      caboto-web/src/main/webapp/js/datepicker/left-range.png
  9. BIN
      caboto-web/src/main/webapp/js/datepicker/right-range.png
  10. BIN
      caboto-web/src/main/webapp/js/datepicker/single-range.png
  11. 1 0
      caboto-web/src/main/webapp/js/protoplasm.js
  12. 3710 1849
      caboto-web/src/main/webapp/js/prototype.js
  13. 181 0
      caboto-web/src/main/webapp/js/timepicker/.timepicker.pdoc.yaml
  14. BIN
      caboto-web/src/main/webapp/js/timepicker/arrow-down.gif
  15. BIN
      caboto-web/src/main/webapp/js/timepicker/arrow-up.gif
  16. BIN
      caboto-web/src/main/webapp/js/timepicker/clock.png
  17. 54 0
      caboto-web/src/main/webapp/js/timepicker/timepicker.css
  18. 1 0
      caboto-web/src/main/webapp/js/timepicker/timepicker.js
  19. 513 0
      caboto-web/src/main/webapp/js/timepicker/timepicker_src.js
  20. 52 0
      caboto-web/src/main/webapp/sparql.html
  21. 2 6
      caboto-web/src/main/webapp/style.css
  22. 12 0
      caboto/src/main/java/org/caboto/filters/AnnotationFilterFactory.java
  23. 80 0
      caboto/src/main/java/org/caboto/filters/DateAnnotationFilter.java

+ 40 - 33
caboto-web/src/main/webapp/index.jsp

@@ -9,21 +9,22 @@
     <style type="text/css" media="screen">@import "./style.css";</style>
     <script type="text/javascript" src="./js/prototype.js"></script>
     <script type="text/javascript" src="./js/annotations.js"></script>
-    <title>Caboto Test Example</title>
+        <script language="javascript" src="./js/protoplasm.js"></script>
+        <script language="javascript">
+            // transform() calls can be chained together
+            Protoplasm.use('datepicker')
+                .transform('input.datepicker', { 'locale': 'en_GB' });
+        </script>    <title>Caboto Test Example</title>
 </head>
 <body onload="initializeAnnotations();">
 
-<security:authorize ifAnyGranted="ROLE_USER,ROLE_ADMIN">
-    <h3 class="logout"><a href="./logout.jsp">Logout</a></h3>
-</security:authorize>
-
 <%
     Cookie uid = new Cookie("uid", null);
     Cookie admin = new Cookie("admin", null);
 
     if (request.getUserPrincipal() != null) {
         uid.setValue(request.getUserPrincipal().getName());
-        if (request.isUserInRole("ADMIN")) {
+        if (request.isUserInRole("ROLE_ADMIN")) {
             admin.setValue("true");
         }
     }
@@ -35,49 +36,55 @@
 <div class="annotations">
 
     <fieldset class="fieldSet">
-        <legend>Annotations</legend>
-        <div id="annotations-results"><p>Sorry, you need a JavaScript enabled browser.</p></div>
-
+    
+    <security:authorize ifAnyGranted="ROLE_USER,ROLE_ADMIN">
+	    <div class="logout"><a href="./logout.jsp">Logout</a></div>
+	</security:authorize>
 
-        <p>Add your own annotation...</p>
-
-        <div id="annotation-messages"></div>
+        <legend>Annotations</legend>
 
         <%-- show form if they are logged in --%>
         <security:authorize ifAnyGranted="ROLE_USER,ROLE_ADMIN">
-            <form id="annotation-comment-form"
-                  action="javascript:processForm('<%=request.getUserPrincipal().getName()%>')"
-                  method="post">
+            <form id="date-query-form"
+                  action="javascript:processDateForm()"
+                  method="get">
                 <p>
-                    <label><strong>Title:</strong></label><br/>
-                    <input id="annotation-title" type="text" name="title" size="50"/><br/>
-                    <label><strong>Description:</strong></label><br/>
-                    <textarea id="annotation-description1" rows="5" cols="50"
-                              name="description"></textarea><br/>
-                    <label><strong>Description (repeated field):</strong></label><br/>
-                    <textarea id="annotation-description2" rows="5" cols="50"
-                              name="description"></textarea><br/>
-                    <input type="radio" name="privacy" value="public" checked="checked"> Public
-                    <input type="radio" name="privacy" value="private"> Private
-                    <input type="hidden" name="type" value="SimpleComment"/>
-                    <input type="hidden" name="annotates" value="http://caboto.org"/><br />
-                    <input id="annotation-submit" type="submit" name="submit" value="Submit"
+                    <label><strong>Start Date:</strong></label>
+                    <input id="start-date" type="date" name="from" class="datepicker"/>
+                    <label><strong>End Date:</strong></label>
+                    <input id="end-date" type="date" name="to" class="datepicker"/>
+                    <input id="date-query-submit" type="submit" name="submit" value="Submit"
                            disabled="disabled"/>
                 </p>
             </form>
+
+            <form id="search-query-form"
+                  action="javascript:processSearchForm()"
+                  method="get">
+                <p>
+                    <label><strong>Search term:</strong></label>
+                    <input id="search-term" type="text" name="search" size="50"/>
+                    <input id="search-query-submit" type="submit" name="submit" value="Submit"
+                           disabled="disabled"/>
+                </p>
+            </form>
+            
+            <a href="#" onclick="clearForm();">clear form</a>
+            
         </security:authorize>
 
         <%-- message if not logged in --%>
         <security:authorize ifNotGranted="ROLE_USER,ROLE_ADMIN">
             <p>You must be <a href="secured/">logged in</a> to add an annotation.</p>
-
-            <p>The test accounts are mike/potato, damian/carrot, jasper/turnip, nikki/pea,
-                simon/radish and admin/boss</p>
-
         </security:authorize>
 
+        <div id="annotation-messages"></div>
+
     </fieldset>
+
+    <div id="annotations-results"><p>Sorry, you need a JavaScript enabled browser.</p></div>
+
 </div>
 
 </body>
-</html>
+</html>

+ 58 - 46
caboto-web/src/main/webapp/js/annotations.js

@@ -91,10 +91,11 @@ function formatAnnotation(annotation, uid, admin) {
     var type = findType(annotation.id);
     
     var body = "";
-    for(i=0; i<annotation.body.description.length; i++) {
-    	body += annotation.body.description[i] + "<br/>";
+    for(var key in annotation.body) {
+    	if(key == 'id') continue;
+    	body += key + ": " + annotation.body[key] + "<br/>";
     }
-
+    
     var output = "<div class='annotation-entry'>";
 
     if (type == "private") {
@@ -103,7 +104,9 @@ function formatAnnotation(annotation, uid, admin) {
         output += "<div class='annotation-entry-title-public'>"
     }
 
-    output += annotation.body.title + "</div>" +
+    output += "<a href='" + getVCLink(annotation.annotates) + "'>" + 
+    			getVCLink(annotation.annotates) + "</a><br/>" + annotation.type +
+    			"</div>" +
               "<div class='annotation-entry-description'>" + body + "</div>" +
               "<div><div class='annotation-entry-created'>" +
               "Created on " + date.toLocaleString() + " by " + author + "</div>";
@@ -120,14 +123,22 @@ function formatAnnotation(annotation, uid, admin) {
     return output;
 }
 
+function getVCLink(uri) {
+	regex = /\/([^\/]*)#(.*)$/;
+	matches = regex.exec(uri);
+	return 'http://visualisingchina.net/#' + matches[1] + "-" + matches[2];
+}
 
 function clearForm() {
 
-    if (document.getElementById('annotation-comment-form')) {
-        Form.Element.enable('annotation-submit');
-        Form.Element.clear('annotation-title');
-        Form.Element.clear('annotation-description1');
-        Form.Element.clear('annotation-description2');
+    if (document.getElementById('search-query-form')) {
+        Form.Element.enable('search-query-submit');
+        Form.Element.clear('search-term');
+    }
+    if (document.getElementById('date-query-form')) {
+        Form.Element.enable('date-query-submit');
+        Form.Element.clear('start-date');
+        Form.Element.clear('end-date');
     }
     document.getElementById("annotation-messages").innerHTML = "";
 }
@@ -157,9 +168,8 @@ function displayAnnotations(transport) {
         output = "<p>There are no annotations.</p>";
     }
 
-
     displayMessage(output);
-    clearForm();
+//    clearForm();
 }
 
 
@@ -199,45 +209,47 @@ function findAnnotations() {
 
 }
 
-function processForm(username) {
+function processDateForm() {
 
-    var uri = "./annotation/person/" + username + "/";
+    var uri = "./annotation/about/";
 
     var message = "";
 
-    // http://rexchung.com/2007/02/22/getting-radio-buttons-value-with-prototypejs/
-    var privacy = Form.getInputs('annotation-comment-form', 'radio', 'privacy').find(function(radio)
-    {
-        return radio.checked;
-    }).value;
+    if (!Form.Element.present("start-date") && !Form.Element.present("end-date")) {
+        message += "You need to provide at least one of start date and end date.";
+    }
 
-    // default to public
-    if (privacy == null) {
-        privacy = "public";
+    if (message.length > 0) {
+        document.getElementById("annotation-messages").innerHTML = "<p>" + message + "</p>";
+        return;
     }
 
-    uri += privacy + "/";
+    // serialize the form data
+    var s = Form.serialize("date-query-form", true);
 
-    if (!Form.Element.present("annotation-title")) {
-        message += "You need to provide a title.";
-    }
+    // we don't need to send the submit
+    delete s.submit;
 
-    if (!Form.Element.present("annotation-description1") && !Form.Element.present("annotation-description2")) {
-        message += "You need to provide at least one description.";
-    } else {
+    // send the form details
+    var req = new Ajax.Request(uri, {
+        method:'get',
+        parameters: s,
+        requestHeaders: {Accept: APPLICATION_JSON},
+        onSuccess: displayAnnotations,
+        onFailure: annotationFailure
+    });
 
-    	for(i=1;i<3;i++) {
-    		var desc = Form.Element.getValue("annotation-description" + i);
+}
 
-    		// <http://haacked.com/archive/2004/10/25/usingregularexpressionstomatchhtml.aspx>
-    		var matches = desc.match(/<\/?\w+((\s+\w+(\s*=\s*(?:\".*?\"|\'.*?\'|[^\'\">\s]+))?)+\s*|\s*)\/?>/);
+function processSearchForm() {
 
-    		if (matches !== null) {
-    			message += "It looks like you have markup in your comment #"+i+" - not supported, sorry!";
-    		}
-        }
-    }
+    var uri = "./annotation/about/";
+
+    var message = "";
 
+    if (!Form.Element.present("search-term")) {
+        message += "You need to provide a search term.";
+    }
 
     if (message.length > 0) {
         document.getElementById("annotation-messages").innerHTML = "<p>" + message + "</p>";
@@ -245,19 +257,18 @@ function processForm(username) {
     }
 
     // serialize the form data
-    var s = Form.serialize("annotation-comment-form", true);
+    var s = Form.serialize("search-query-form", true);
 
     // we don't need to send the submit
     delete s.submit;
 
     // send the form details
     var req = new Ajax.Request(uri, {
-        method:'post',
+        method:'get',
         parameters: s,
-        onSuccess: findAnnotations,
-        onFailure: function(transport) {
-            alert(transport.responseText);
-        }
+        requestHeaders: {Accept: APPLICATION_JSON},
+        onSuccess: displayAnnotations,
+        onFailure: annotationFailure
     });
 
 }
@@ -265,13 +276,14 @@ function processForm(username) {
 function initializeAnnotations() {
 
     // hides the js support message..
-    displayMessage("<p>Loading annotations..</p>");
-
+//    displayMessage("<p>Loading annotations..</p>");
+    displayMessage("");
+    
     // enable the form
     clearForm();
 
     // get the annotations
-    findAnnotations();
+//    findAnnotations();
 }
 
 function deleteAnnotation(uri) {

+ 555 - 0
caboto-web/src/main/webapp/js/datepicker/.datepicker.pdoc.yaml

@@ -0,0 +1,555 @@
+---
+id: "Control.DatePicker"
+parent_id: "Control"
+type: class
+line_number: 25
+description: |
+  Transforms an ordinary input textbox into an interactive date picker.
+  When the textbox is clicked (or the down arrow is pressed), a calendar
+  appears that the user can browse through and select a date.
+  
+  Control ID: `datepicker`
+  
+  Features:
+  
+  * Allows user to specify a date format
+  * Easy to localize
+  
+  Example: <a href="http://jongsma.org/software/protoplasm/control/datepicker">Date
+  Picker demo</a>
+
+file: ./src/datepicker/datepicker.js
+
+---
+id: "new Control.DatePicker"
+parent_id: "Control.DatePicker"
+type: constructor
+line_number: 60
+description: |
+  Create a new date picker from the given `<input type="text">`
+  element.
+  
+  Additional options:
+  
+  * icon: The URL of the icon to display on the control
+  * monthCount: The number of calendar months to display at one time
+  * layout: Layout mode for multiple calendars: 'horizontal' (default) or 'vertical'
+  * range: Use date range selection instead of a single date.
+    Requires multiple `<input>` elements (see rangeEnd below).
+  * rangeEnd: The element for storing a date range's end date in. If a rangeEnd
+    element is not specified, it will automatically look for one as a next
+    sibling of `element`.
+  * minDate: The minimum date that is allowed to be selected
+  * maxDate: The maximum date that is allowed to be selected
+  * locale: Set the internationalization locale code
+  * manual: Allow manual date entry by typing (default true)
+  * epoch: The date posted to the server will be as a unix
+    timestamp representing the milliseconds since 1-1-1970
+  * timePicker: Display a time picker (default false)
+  * use24hrs: Show 24 hours in the time picker instead of
+    AM/PM (default false)
+  * onSelect: Callback function when a date/time is selected.
+    A Date object is passed as the parameter.
+  * onHover: Callback function when the active date changes
+    via keyboard navigation.  A Date object is passed as
+    the parameter.
+
+signatures:
+ -
+  signature: "new Control.DatePicker(element[, options])"
+arguments:
+ -
+  name: element
+  types: [String, Element]
+  description: >
+    A `<input type="text">` element (or DOM ID).
+
+ -
+  name: options
+  types: [Hash]
+  description: >
+    Additional options for the control.
+
+file: ./src/datepicker/datepicker.js
+
+---
+id: "Control.DatePicker#element"
+parent_id: "Control.DatePicker"
+type: instance property
+line_number: 84
+description: |
+  The underlying `<input>` element passed to the constructor.
+
+signatures:
+ -
+  signature: "Control.DatePicker#element"
+  return_value: "Element"
+file: ./src/datepicker/datepicker.js
+
+---
+id: "Control.DatePicker#panel"
+parent_id: "Control.DatePicker"
+type: instance property
+line_number: 93
+description: |
+  The panel dialog box linked to this control.  This may be
+  null if the control is not open.
+
+signatures:
+ -
+  signature: "Control.DatePicker#panel"
+  return_value: "Control.DatePicker.Panel"
+file: ./src/datepicker/datepicker.js
+
+---
+id: "Control.DatePicker#destroy"
+parent_id: "Control.DatePicker"
+type: instance method
+line_number: 225
+description: |
+  Destroy this control and return the underlying element to
+  its original behavior.
+
+signatures:
+ -
+  signature: "Control.DatePicker#destroy()"
+  return_value: "null"
+file: ./src/datepicker/datepicker.js
+
+---
+id: "Control.DatePicker#toggle"
+parent_id: "Control.DatePicker"
+type: instance method
+line_number: 280
+description: |
+  Toggle the visibility of the picker panel for this control.
+
+signatures:
+ -
+  signature: "Control.DatePicker#toggle()"
+  return_value: "null"
+file: ./src/datepicker/datepicker.js
+
+---
+id: "Control.DatePicker#close"
+parent_id: "Control.DatePicker"
+type: instance method
+line_number: 347
+description: |
+  Hide the picker panel for this control.
+
+signatures:
+ -
+  signature: "Control.DatePicker#close()"
+  return_value: "null"
+file: ./src/datepicker/datepicker.js
+
+---
+id: "Control.DatePicker#open"
+parent_id: "Control.DatePicker"
+type: instance method
+line_number: 364
+description: |
+  Show the picker panel for this control.
+
+signatures:
+ -
+  signature: "Control.DatePicker#open()"
+  return_value: "null"
+file: ./src/datepicker/datepicker.js
+
+---
+id: "Control.DatePicker.activePicker"
+parent_id: "Control.DatePicker"
+type: class property
+line_number: 403
+description: |
+  A reference to the last opened date picker.
+
+signatures:
+ -
+  signature: "Control.DatePicker.activePicker"
+  return_value: "Control.DatePicker"
+file: ./src/datepicker/datepicker.js
+
+---
+id: "Control.DatePicker.create"
+parent_id: "Control.DatePicker"
+type: class method
+line_number: 429
+description: |
+  Creates a new date picker from scratch instead of transforming
+  an existing DOM element.  Returns the root element for the
+  date picker control, suitable for inserting into your page.
+  You can retrieve the Control.DatePicker instance behind the
+  returned element with `element.retrieve('datepicker')`.
+  
+  Options:
+  
+  * `className`: The CSS class to assign the &lt;input&gt; element
+  * `name`: The field name to assign the &lt;input&gt; element
+  
+  Additional options are passed through to `new Control.DatePicker()`.
+  See [[Control.DatePicker]] constructor for available options.
+  
+  Example:
+  
+  	var dp = Control.DatePicker.create();
+  	panel.appendChild(dp);
+  	dp.open();
+
+signatures:
+ -
+  signature: "Control.DatePicker.create(options)"
+  return_value: "Element"
+arguments:
+ -
+  name: options
+  types: [Hash]
+  description: >
+    Additional options for the control.
+
+file: ./src/datepicker/datepicker.js
+
+---
+id: "Control.DatePicker.i18n"
+parent_id: "Control.DatePicker"
+type: class
+line_number: 445
+description: |
+  Internationalization settings for a [[Control.DatePicker]] instance.
+
+file: ./src/datepicker/datepicker.js
+
+---
+id: "Control.DatePicker.i18n.createLocale"
+parent_id: "Control.DatePicker.i18n"
+type: class method
+line_number: 480
+description: |
+  Create a new locale combining a standard base locale ("us", "eu", "iso8601")
+  and a new language code.
+
+signatures:
+ -
+  signature: "Control.DatePicker.i18n.createLocale(base, lang)"
+  return_value: "Object"
+arguments:
+ -
+  name: base
+  types: [String]
+  description: >
+    The base locale (one of "us", "eu", "iso8601").
+
+ -
+  name: lang
+  types: [String]
+  description: >
+    The language to use.
+
+file: ./src/datepicker/datepicker.js
+
+---
+id: "new Control.DatePicker.i18n"
+parent_id: "Control.DatePicker.i18n"
+type: constructor
+line_number: 521
+description: |
+  Create a new internationalization settings  based on the
+  specified locale code (i.e. "en_US").
+  
+  Locales that are supported by default are:
+  
+  * es
+  * en
+  * en_US
+  * en_GB
+  * de
+  * es_iso8601
+  * en_iso8601
+  * de_iso8601
+  
+  Additionally, there are other locales which are not included
+  but will be loaded on demand via an AJAX call if requested:
+  
+  * cs_CZ
+  * el_GR
+  * fr_FR
+  * it_IT
+  * lt_LT
+  * nl_NL
+  * pl_PL
+  * pt_BR
+  * ru_RU
+  
+  For details on creating new locales, see the
+  <a href="http://jongsma.org/software/protoplasm/datepicker#locales">Protoplasm
+  web site</a>.
+
+signatures:
+ -
+  signature: "new Control.DatePicker.i18n([code])"
+arguments:
+ -
+  name: code
+  types: [String]
+  description: >
+    The locale code.  This can be a language code ("es") or a full locale ("es_AR").
+
+file: ./src/datepicker/datepicker.js
+
+---
+id: "Control.DatePicker.i18n#tr"
+parent_id: "Control.DatePicker.i18n"
+type: instance method
+line_number: 547
+description: |
+  Translates the given text into the language linked to
+  this instance.
+
+signatures:
+ -
+  signature: "Control.DatePicker.i18n#tr(text)"
+  return_value: "String"
+arguments:
+ -
+  name: text
+  types: [String]
+  description: >
+    The string to translate.
+
+file: ./src/datepicker/datepicker.js
+
+---
+id: "Control.DatePicker.Locale"
+parent_id: "Control.DatePicker"
+type: class property
+line_number: 560
+description: |
+  Hash object that stores available locale definitions.
+  
+  Bundled locales: es, en, en\_US, en\_GB, de, es\_iso8601,
+  en\_iso8601, de\_iso8601
+
+signatures:
+ -
+  signature: "Control.DatePicker.Locale"
+  return_value: "Hash"
+file: ./src/datepicker/datepicker.js
+
+---
+id: "Control.DatePicker.Language"
+parent_id: "Control.DatePicker"
+type: class property
+line_number: 581
+description: |
+  Hash object that stores available language definitions.
+  
+  Bundled languages: en, es, de
+
+signatures:
+ -
+  signature: "Control.DatePicker.Language"
+  return_value: "Hash"
+file: ./src/datepicker/datepicker.js
+
+---
+id: "Control.DatePicker.Panel"
+parent_id: "Control.DatePicker"
+type: class
+line_number: 615
+description: |
+  The dialog panel that is displayed when the date picker is opened.
+
+file: ./src/datepicker/datepicker.js
+
+---
+id: "new Control.DatePicker.Panel"
+parent_id: "Control.DatePicker.Panel"
+type: constructor
+line_number: 650
+description: |
+  Create a new date picker panel.
+  
+  Additional options:
+  
+  * className: The CSS class of the panel element
+  * monthCount: The number of calendar months to display at one time
+  * layout: Layout mode for multiple calendars: 'horizontal' (default) or 'vertical'
+  * range: Use date range selection instead of single dates
+  * minDate: The minimum date that is allowed to be selected
+  * maxDate: The maximum date that is allowed to be selected
+  * timePicker: Display a time picker (default false) - <i>not
+    compatible with "range" option</i>
+  * use24hrs: Show 24 hours in the time picker instead of
+    AM/PM (default false)
+  * firstWeekDay: The first day of the week (default 0 - Sunday)
+  * weekend: An array of day numbers that are considered the
+    weekend (default [0,6] - Saturday/Sunday)
+  * months: An array of 12 month names
+  * days: ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa']
+  * closeOnToday: Close picker when the "Today" link is clicked
+    (default true)
+  * selectToday: Automatically select today's date initially
+
+signatures:
+ -
+  signature: "new Control.DatePicker.Panel([options])"
+arguments:
+ -
+  name: options
+  types: [Hash]
+  description: >
+    Additional options for the panel.
+
+file: ./src/datepicker/datepicker.js
+
+---
+id: "Control.DatePicker.Panel#element"
+parent_id: "Control.DatePicker.Panel"
+type: instance property
+line_number: 694
+description: |
+  The root Element of this dialog panel.
+
+signatures:
+ -
+  signature: "Control.DatePicker.Panel#element"
+  return_value: "Element"
+file: ./src/datepicker/datepicker.js
+
+---
+id: "Control.DatePicker.DateFormat"
+parent_id: "Control.DatePicker"
+type: class
+line_number: 1282
+description: |
+  A date formatting utility class.
+
+file: ./src/datepicker/datepicker.js
+
+---
+id: "new Control.DatePicker.DateFormat"
+parent_id: "Control.DatePicker.DateFormat"
+type: constructor
+line_number: 1290
+description: |
+  Create a new DateFormat object the uses the specified format string.
+
+signatures:
+ -
+  signature: "new Control.DatePicker.DateFormat(format)"
+arguments:
+ -
+  name: format
+  types: [String]
+  description: >
+    The format string (see [[Control.DatePicker.DateFormat.format]]).
+
+file: ./src/datepicker/datepicker.js
+
+---
+id: "Control.DatePicker.DateFormat#parse"
+parent_id: "Control.DatePicker.DateFormat"
+type: instance method
+line_number: 1299
+description: |
+  Attempt to parse a string into a Date object according to this
+  object's formatting rules.
+
+signatures:
+ -
+  signature: "Control.DatePicker.DateFormat#parse(text)"
+  return_value: "Date"
+arguments:
+ -
+  name: text
+  types: [String]
+  description: >
+    The text to parse into a Date.
+
+file: ./src/datepicker/datepicker.js
+
+---
+id: "Control.DatePicker.DateFormat#format"
+parent_id: "Control.DatePicker.DateFormat"
+type: instance method
+line_number: 1308
+description: |
+  Format a date into a string according to this object's formatting
+  rules.
+
+signatures:
+ -
+  signature: "Control.DatePicker.DateFormat#format(date)"
+  return_value: "String"
+arguments:
+ -
+  name: date
+  types: [Date]
+  description: >
+    The date to format.
+
+file: ./src/datepicker/datepicker.js
+
+---
+id: "Control.DatePicker.DateFormat.format"
+parent_id: "Control.DatePicker.DateFormat"
+type: class method
+line_number: 1334
+description: |
+  Convert a date to a string representation according to the specified
+  format string.
+  
+  Formatting tokens:
+  TODO
+
+signatures:
+ -
+  signature: "Control.DatePicker.DateFormat.format(date, format)"
+  return_value: "String"
+arguments:
+ -
+  name: date
+  types: [Date]
+  description: >
+    The date to format.
+
+ -
+  name: format
+  types: [String]
+  description: >
+    The format definition.
+
+file: ./src/datepicker/datepicker.js
+
+---
+id: "Control.DatePicker.DateFormat.parse"
+parent_id: "Control.DatePicker.DateFormat"
+type: class method
+line_number: 1548
+description: |
+  Attempt to parse a string to a Date, given the specified
+  format string.  If `format` is omitted, DateFormat will
+  try some common formats.
+  
+  See [[Control.DatePicker.DateFormat.format]] for formatting string details.
+
+signatures:
+ -
+  signature: "Control.DatePicker.DateFormat.parse(date[, format])"
+  return_value: "Date"
+arguments:
+ -
+  name: text
+  types: [String]
+  description: >
+    The text to parse.
+
+ -
+  name: format
+  types: [String]
+  description: >
+    The format definition to match against.
+
+file: ./src/datepicker/datepicker.js

BIN
caboto-web/src/main/webapp/js/datepicker/calendar.png


+ 103 - 0
caboto-web/src/main/webapp/js/datepicker/datepicker.css

@@ -0,0 +1,103 @@
+/**
+ * Styles for DatePicker
+ */
+
+._pp_datepicker {
+	line-height: 15px;
+	display: inline-block;
+}
+
+._pp_datepicker_navigation {
+	text-align: center;
+	width: 180px;
+	margin: auto;
+}
+._pp_datepicker_today:hover,
+._pp_datepicker_next:hover,
+._pp_datepicker_previous:hover {
+	text-decoration: underline;
+	cursor: pointer;
+}
+._pp_datepicker_next {
+	float: right;
+	width: 25px;
+}
+._pp_datepicker_previous {
+	float: left;
+	width: 25px;
+}
+
+._pp_datepicker_table {
+	border-collapse: collapse;
+	width: 180px;
+}
+._pp_datepicker_table th,
+._pp_datepicker_table td {
+	text-align: center;
+	padding: 1px;
+}
+
+._pp_datepicker td.day,
+._pp_datepicker td.dayothermonth {
+	cursor: pointer;
+	background-color: #FFFFFF;
+	border: 1px solid #EEEEEE;
+	width: 2em;
+}
+
+._pp_datepicker td.dayothermonth {
+	color: #999999;
+	font-style: italic;
+}
+
+._pp_datepicker td.day:hover {
+	background-color: #EBE4C0;
+} 
+
+._pp_datepicker td.weekend {
+	background-color: #CCCCCC;
+	font-style: italic;
+}
+
+._pp_datepicker td.today {
+	font-weight: bold;
+}
+
+._pp_datepicker td.current,
+._pp_datepicker td.current:hover {
+	font-weight: bold;
+	background-color: #EBC2C0;
+}
+._pp_datepicker td.rightrange {
+	background: url(right-range.png) #EBC2C0 right center no-repeat;
+}
+._pp_datepicker td.leftrange {
+	background: url(left-range.png) #EBC2C0 left center no-repeat;
+}
+._pp_datepicker td.singlerange {
+	background: url(single-range.png) #EBC2C0 center center no-repeat;
+}
+._pp_datepicker_weekselect {
+	width: 3px;
+	background-color: #999;
+	cursor: pointer;
+	border-radius: 5px 0 0 5px;
+	-moz-border-radius: 5px 0 0 5px;
+	-webkit-border-radius: 5px 0 0 5px;
+}
+
+._pp_datepicker button {
+	display: block;
+	width: 174px;
+	margin: 3px auto;
+	font-size: 11px;
+}
+._pp_datepicker td.currenthint {
+	background-color: #f2e0df;
+}
+._pp_datepicker td.disabled,
+._pp_datepicker td.disabled:hover {
+	background-color: #EEE;
+	color: #999999;
+	cursor: default;
+}

File diff suppressed because it is too large
+ 1 - 0
caboto-web/src/main/webapp/js/datepicker/datepicker.js


File diff suppressed because it is too large
+ 1570 - 0
caboto-web/src/main/webapp/js/datepicker/datepicker_src.js


BIN
caboto-web/src/main/webapp/js/datepicker/left-range.png


BIN
caboto-web/src/main/webapp/js/datepicker/right-range.png


BIN
caboto-web/src/main/webapp/js/datepicker/single-range.png


File diff suppressed because it is too large
+ 1 - 0
caboto-web/src/main/webapp/js/protoplasm.js


File diff suppressed because it is too large
+ 3710 - 1849
caboto-web/src/main/webapp/js/prototype.js


+ 181 - 0
caboto-web/src/main/webapp/js/timepicker/.timepicker.pdoc.yaml

@@ -0,0 +1,181 @@
+---
+id: "Control.TimePicker"
+parent_id: "Control"
+type: class
+line_number: 21
+description: |
+  Transforms an ordinary input textbox into a time picker.
+  
+  Control ID: `timepicker`
+  
+  Features:
+  
+  * Allows user to specify a time format
+  
+  Example: <a href="http://jongsma.org/software/protoplasm/control/timepicker">Time
+  Picker demo</a>
+
+file: ./src/timepicker/timepicker.js
+
+---
+id: "new Control.TimePicker"
+parent_id: "Control.TimePicker"
+type: constructor
+line_number: 42
+description: |
+  Create a new time picker from the given `<input type="text">`
+  element.
+  
+  Additional options:
+  
+  * icon: The URL of the icon to display on the control
+  * format: The time format (default 'HH:mm:ss')
+  * use24hrs: Show 24 hours in the time picker instead of
+    AM/PM (default false)
+  * onChange: Callback function when the time is changed.
+    A Date object is passed as the parameter.
+  * onSelect: Callback function when the time is selected.
+    A Date object is passed as the parameter.
+
+signatures:
+ -
+  signature: "new Control.TimePicker(element[, options])"
+arguments:
+ -
+  name: element
+  types: [String, Element]
+  description: >
+    A `<input type="text">` element (or DOM ID).
+
+ -
+  name: options
+  types: [Hash]
+  description: >
+    Additional options for the control.
+
+file: ./src/timepicker/timepicker.js
+
+---
+id: "Control.TimePicker#element"
+parent_id: "Control.TimePicker"
+type: instance property
+line_number: 64
+description: |
+  The underlying `<input>` element passed to the constructor.
+
+signatures:
+ -
+  signature: "Control.TimePicker#element"
+  return_value: "Element"
+file: ./src/timepicker/timepicker.js
+
+---
+id: "Control.TimePicker#panel"
+parent_id: "Control.TimePicker"
+type: instance property
+line_number: 78
+description: |
+  The panel dialog box linked to this control.  This may be
+  null if the control is not open.
+
+signatures:
+ -
+  signature: "Control.TimePicker#panel"
+  return_value: "Control.TimePicker.Panel"
+file: ./src/timepicker/timepicker.js
+
+---
+id: "Control.TimePicker#destroy"
+parent_id: "Control.TimePicker"
+type: instance method
+line_number: 117
+description: |
+  Destroy this control and return the underlying element to
+  its original behavior.
+
+signatures:
+ -
+  signature: "Control.TimePicker#destroy()"
+  return_value: "null"
+file: ./src/timepicker/timepicker.js
+
+---
+id: "Control.TimePicker#toggle"
+parent_id: "Control.TimePicker"
+type: instance method
+line_number: 178
+description: |
+  Toggle the visibility of the picker panel for this control.
+
+signatures:
+ -
+  signature: "Control.TimePicker#toggle()"
+  return_value: "null"
+file: ./src/timepicker/timepicker.js
+
+---
+id: "Control.DatePicker#open"
+parent_id: "Control.DatePicker"
+type: instance method
+line_number: 225
+description: |
+  Show the picker panel for this control.
+
+signatures:
+ -
+  signature: "Control.DatePicker#open()"
+  return_value: "null"
+file: ./src/timepicker/timepicker.js
+
+---
+id: "Control.TimePicker#close"
+parent_id: "Control.TimePicker"
+type: instance method
+line_number: 280
+description: |
+  Hide the picker panel for this control.
+
+signatures:
+ -
+  signature: "Control.TimePicker#close()"
+  return_value: "null"
+file: ./src/timepicker/timepicker.js
+
+---
+id: "Control.TimePicker.Panel"
+parent_id: "Control.TimePicker"
+type: class
+line_number: 297
+description: |
+  The dialog panel that is displayed when the time picker is opened.
+
+file: ./src/timepicker/timepicker.js
+
+---
+id: "new Control.TimePicker.Panel"
+parent_id: "Control.TimePicker.Panel"
+type: constructor
+line_number: 314
+description: |
+  Create a new time picker panel.
+  
+  Additional options:
+  
+  * use24hrs: Show 24 hours in the time picker instead of
+    AM/PM (default false)
+  * onChange: Callback function when the time is changed.
+    A Date object is passed as the parameter.
+  * onSelect: Callback function when the time is selected.
+    A Date object is passed as the parameter.
+
+signatures:
+ -
+  signature: "new Control.TimePicker.Panel([options])"
+arguments:
+ -
+  name: options
+  types: [Hash]
+  description: >
+    Additional options for the panel.
+
+file: ./src/timepicker/timepicker.js

BIN
caboto-web/src/main/webapp/js/timepicker/arrow-down.gif


BIN
caboto-web/src/main/webapp/js/timepicker/arrow-up.gif


BIN
caboto-web/src/main/webapp/js/timepicker/clock.png


+ 54 - 0
caboto-web/src/main/webapp/js/timepicker/timepicker.css

@@ -0,0 +1,54 @@
+._pp_timepicker {
+	font-size: 16px;
+}
+._pp_timepicker td {
+	vertical-align: middle;
+}
+._pp_timepicker_input[type=text] {
+	border: 1px solid #999;
+	font-size: 16px;
+	width: 30px;
+	margin: 0 1px;
+	padding: 0;
+	text-align: center;
+}
+._pp_timepicker_down,
+._pp_timepicker_up {
+	cursor: pointer;
+	margin: 0 1px;
+	border: 1px solid #999;
+	background-position: center center;
+	background-repeat: no-repeat;
+	width: 30px;
+	height: 10px;
+}
+._pp_timepicker_up {
+	border-bottom: 0;
+	border-radius: 4px 4px 0 0;
+	-moz-border-radius: 4px 4px 0 0;
+	-webkit-border-radius: 4px 4px 0 0;
+	background-image: url(arrow-up.gif);
+}
+._pp_timepicker_down {
+	border-top: 0;
+	border-radius: 0 0 4px 4px;
+	-moz-border-radius: 0 0 4px 4px;
+	-webkit-border-radius: 0 0 4px 4px;
+	background-image: url(arrow-down.gif);
+}
+._pp_timepicker_ampm {
+	border: 1px solid #999;
+	border-radius: 4px;
+	-moz-border-radius: 4px;
+	-webkit-border-radius: 4px;
+	font-size: 11px;
+	margin: 2px;
+	padding: 3px;
+	cursor: pointer;
+	font-weight: bold;
+}
+._pp_timepicker_active {
+	background-color: #999;
+	border-color: #666;
+	color: #FFF;
+}

File diff suppressed because it is too large
+ 1 - 0
caboto-web/src/main/webapp/js/timepicker/timepicker.js


+ 513 - 0
caboto-web/src/main/webapp/js/timepicker/timepicker_src.js

@@ -0,0 +1,513 @@
+if (typeof Protoplasm == 'undefined')
+	throw('protoplasm.js not loaded, could not intitialize timepicker');
+if (typeof Control == 'undefined') Control = {};
+
+Protoplasm.loadStylesheet('timepicker.css', 'timepicker');
+
+/**
+ * class Control.TimePicker
+ * 
+ * Transforms an ordinary input textbox into a time picker.
+ *
+ * Control ID: `timepicker`
+ *
+ * Features:
+ *
+ * * Allows user to specify a time format
+ *
+ * Example: <a href="http://jongsma.org/software/protoplasm/control/timepicker">Time
+ * Picker demo</a>
+**/
+Control.TimePicker = Class.create({
+
+/**
+ * new Control.TimePicker(element[, options])
+ * - element (String | Element): A `<input type="text">` element (or DOM ID).
+ * - options (Hash): Additional options for the control.
+ *
+ * Create a new time picker from the given `<input type="text">`
+ * element.
+ *
+ * Additional options:
+ *
+ * * icon: The URL of the icon to display on the control
+ * * format: The time format (default 'HH:mm:ss')
+ * * use24hrs: Show 24 hours in the time picker instead of
+ *   AM/PM (default false)
+ * * onChange: Callback function when the time is changed.
+ *   A Date object is passed as the parameter.
+ * * onSelect: Callback function when the time is selected.
+ *   A Date object is passed as the parameter.
+**/
+	initialize: function (element, options) {
+
+		element = $(element);
+
+		if (tp = element.retrieve('timepicker'))
+			tp.destroy();
+
+		// Wrap to avoid positioning errors from padding/margins
+		var wrapper = element.wrap('span', {'style': 'position:relative;'});
+
+		options = Object.extend({
+			format: 'HH:mm:ss'
+			}, options || {});
+
+		if (!options.icon)
+			options.icon = Protoplasm.base('timepicker')+'clock.png';
+
+/**
+ * Control.TimePicker#element -> Element
+ *
+ * The underlying `<input>` element passed to the constructor.
+**/
+		this.element = element;
+		this.label = element;
+		this.wrapper = wrapper;
+		this.options = options;
+		this.changeHandler = options.onChange;
+		this.selectHandler = options.onSelect;
+		options.onSelect = this.onSelect.bind(this);
+		options.onChange = this.onChange.bind(this);
+/**
+ * Control.TimePicker#panel -> Control.TimePicker.Panel
+ *
+ * The panel dialog box linked to this control.  This may be
+ * null if the control is not open.
+**/
+		// Lazy load to avoid excessive CPU usage with lots of controls on one page
+		this.panel = null;
+		this.dialog = null;
+
+		this.listeners = [
+			element.on('click', this.toggle.bindAsEventListener(this)),
+			element.on('keydown', this.keyHandler.bindAsEventListener(this)),
+			Event.on(window, 'unload', this.destroy.bind(this))
+		];
+
+		if (options.icon) {
+			element.style.background = 'url('+options.icon+') right center no-repeat #FFF';
+			this.oldPadding = element.style.paddingRight;
+			element.style.paddingRight = '20px';
+		}
+
+		this.hideListener = null;
+		this.keyListener = null;
+		this.active = false;
+
+		this.element.store('timepicker', this);
+
+		// Extend element with public API
+		this.element = Protoplasm.extend(element, {
+			show: wrapper.show.bind(wrapper),
+			hide: wrapper.hide.bind(wrapper),
+			open: this.open.bind(this),
+			toggle: this.toggle.bind(this),
+			close: this.close.bind(this),
+			destroy: this.destroy.bind(this)
+		});
+
+	},
+
+/**
+ * Control.TimePicker#destroy() -> null
+ *
+ * Destroy this control and return the underlying element to
+ * its original behavior.
+**/
+	destroy: function() {
+		Protoplasm.revert(this.element);
+		this.listeners.invoke('stop');
+		if (this.hideListener)
+			this.hideListener.stop();
+		if (this.keyListener)
+			this.keyListener.stop();
+		this.wrapper.parentNode.replaceChild(this.element, this.wrapper);
+		this.element.style.paddingRight = this.oldPadding;
+		this.element.store('timepicker', null);
+	},
+
+	clickHandler: function(e) {
+		var element = Event.element(e);
+		do {
+			if (element == this.element || element == this.label
+					|| element == this.dialog)
+				return;
+		} while (element = element.parentNode);
+		this.close();
+	},
+
+	setValue: function(time) {
+		var h = time.getHours();
+		var m = time.getMinutes();
+		var s = time.getSeconds();
+		var a = '';
+		if (!this.options.use24hrs) {
+			a = ' AM';
+			if (h == 0) {
+				h = 12;
+			} else if (h > 11) {
+				if (h > 12)
+					h -= 12;
+				a = ' PM';
+			}
+		}
+		h = h < 10 ? '0'+h : h;
+		m = m < 10 ? '0'+m : m;
+		s = s < 10 ? '0'+s : s;
+		this.element.value = h+':'+m+':'+s+a;
+	},
+
+	onSelect: function(time) {
+		this.setValue(time);
+		this.close();
+		if (this.selectHandler)
+			this.selectHandler(time);
+	},
+
+	onChange: function(time) {
+		this.setValue(time);
+		if (this.changeHandler)
+			this.changeHandler(time);
+	},
+
+/**
+ * Control.TimePicker#toggle() -> null
+ *
+ * Toggle the visibility of the picker panel for this control.
+**/
+	toggle: function(e) {
+		if (this.active)
+			this.close();
+		else
+			setTimeout(this.open.bind(this));
+	},
+
+	keyHandler: function(e) {
+		switch (e.keyCode) {
+			case Event.KEY_ESC:
+				this.close();
+				return;
+			case Event.KEY_RETURN:
+				this.close();
+				return;
+			case Event.KEY_TAB:
+				this.close();
+				return;
+			case Event.KEY_DOWN:	
+				if (!this.dialog || !this.dialog.parentNode) {
+					this.open();
+					Event.stop(e);
+				}
+		}
+		if (this.pickerActive)
+			return false;
+	},
+
+	docKeyHandler: function(e) {
+		switch (e.keyCode) {
+			case Event.KEY_ESC:
+				this.close();
+				return;
+			case Event.KEY_RETURN:
+				this.close();
+				Event.stop(e);
+				return;
+		}
+		if (this.pickerActive)
+			return false;
+	},
+
+/**
+ * Control.DatePicker#open() -> null
+ *
+ * Show the picker panel for this control.
+**/
+	open: function () {
+		if (!this.active) {
+			if (!this.dialog) {
+				this.panel = new Control.TimePicker.Panel(this.options);
+				this.dialog = new Element('div', { 'class': '_pp_frame_small',
+					'style': 'position:absolute;' });
+				this.dialog.insert(this.panel.element);
+			}
+
+			var layout = this.label.getLayout();
+			var offsetTop = layout.get('border-box-height') - layout.get('border-bottom');
+			document.body.appendChild(this.dialog);
+			this.dialog.clonePosition(this.label, {
+				'setWidth': false,
+				'setHeight': false,
+				'offsetTop': offsetTop,
+				'offsetLeft': -3
+				});
+			this.dialog.style.zIndex = '99';
+			this.panel.setTime(this.parse(this.element.value));
+			this.panel.hours.focus();
+			this.hideListener = document.on('click', this.clickHandler.bindAsEventListener(this));
+			this.keyListener = document.on('keydown', this.docKeyHandler.bindAsEventListener(this));
+			this.active = true;
+		}
+	},
+
+	parse: function(str) {
+		var p, h, m, s, ap;
+		if (!this.options.use24hrs) {
+			p = str.split(/ /);
+			if (p.length > 1)
+				ap = p[1].toUpperCase();
+			str = p[0];
+		}
+		p = str.split(':');
+		h = p[0] || 0;
+		m = p[1] || 0;
+		s = p.length > 2 ? p[2] : 0;
+		if (!this.options.use24hrs) {
+			if (ap == 'AM' && h == 12)
+				h = 0;
+			else if (h < 12 && ap == 'PM')
+				h += 12;
+		}
+		d = new Date();
+		d.setHours(h);
+		d.setMinutes(m);
+		d.setSeconds(s);
+		return d;
+	},
+
+/**
+ * Control.TimePicker#close() -> null
+ *
+ * Hide the picker panel for this control.
+**/
+	close: function() {
+		if(this.active) {
+			this.dialog.remove();
+			this.active = false;
+			if (this.hideListener)
+				this.hideListener.stop();
+			if (this.keyListener)
+				this.keyListener.stop();
+		}
+	}
+
+});
+
+/**
+ * class Control.TimePicker.Panel
+ *
+ * The dialog panel that is displayed when the time picker is opened.
+**/
+Control.TimePicker.Panel = Class.create({
+
+/**
+ * new Control.TimePicker.Panel([options])
+ * - options (Hash): Additional options for the panel.
+ *
+ * Create a new time picker panel.
+ *
+ * Additional options:
+ *
+ * * use24hrs: Show 24 hours in the time picker instead of
+ *   AM/PM (default false)
+ * * onChange: Callback function when the time is changed.
+ *   A Date object is passed as the parameter.
+ * * onSelect: Callback function when the time is selected.
+ *   A Date object is passed as the parameter.
+**/
+	initialize: function(options) {
+
+		this.options = Object.extend({
+			}, options || {});
+
+		this.time = this.options.time || new Date();
+		this.ampm = 'AM';
+		this.element = this.createPicker();
+
+		this.element.on('selectstart', function(e) {
+			Event.stop(e); }.bindAsEventListener(this));
+	},
+
+	createPicker: function() {
+
+		var picker = new Element('div', {'class': '_pp_timepicker '+this.options.className});
+
+		this.hours = new Element('input', {'type': 'text', 'class': '_pp_timepicker_input', 'value': '00'});
+		this.minutes = new Element('input', {'type': 'text', 'class': '_pp_timepicker_input', 'value': '00'});
+		this.seconds = new Element('input', {'type': 'text', 'class': '_pp_timepicker_input', 'value': '00'});
+
+		var table = new Element('table', {'cellPadding': 0, 'cellSpacing': 0, 'border': 0});
+		var row = table.insertRow(0);
+
+		row.appendChild(this.createCell(this.hours, this.options.use24hrs ? 23 : 12,
+			this.options.use24hrs ? 0 : 1));
+		row.appendChild(new Element('td').update(':'));
+		row.appendChild(this.createCell(this.minutes, 59, 0));
+		row.appendChild(new Element('td').update(':'));
+		row.appendChild(this.createCell(this.seconds, 59, 0));
+
+		if (!this.options.use24hrs) {
+			var td = new Element('td');
+			this.am = new Element('div', {'class': '_pp_timepicker_ampm _pp_highlight'}).update('AM');
+			this.am.on('click', function() {
+				this.ampm = 'AM';
+				this.pm.removeClassName('_pp_highlight');
+				this.am.addClassName('_pp_highlight');
+				this.onChange();
+				}.bindAsEventListener(this));
+			this.pm = new Element('div', {'class': '_pp_timepicker_ampm'}).update('PM');
+			this.pm.on('click', function() {
+				this.ampm = 'PM';
+				this.am.removeClassName('_pp_highlight');
+				this.pm.addClassName('_pp_highlight');
+				this.onChange();
+				}.bindAsEventListener(this));
+			td.appendChild(this.am);
+			td.appendChild(this.pm);
+			row.appendChild(td);
+		}
+
+		picker.appendChild(table);
+
+		return picker;
+
+	},
+
+	createCell: function(input, maxValue, minValue) {
+
+		var td = new Element('td');
+
+		input.on('keydown', function(e) {
+			if (e.keyCode == Event.KEY_UP) {
+				this.increment(input, maxValue, minValue);
+				this.onChange();
+				Event.stop(e);
+			} else if (e.keyCode == Event.KEY_DOWN) {
+				this.decrement(input, maxValue, minValue);
+				this.onChange();
+				Event.stop(e);
+			} else if (e.keyCode == Event.KEY_RETURN) {
+				this.onSelect();
+				Event.stop(e);
+				return false;
+			}}.bindAsEventListener(this));
+		input.on('change', function(e) {
+			var current = input.value*1;
+			input.value = current < 10 ? '0'+current : current;
+			this.onChange();
+			}.bindAsEventListener(this));
+		input.on('focus', function(e) {
+			input.select();
+			}.bindAsEventListener(this));
+
+		var up = new Element('div', {'class': '_pp_highlight _pp_timepicker_up'});
+		this.setBehavior(up, input, this.increment, maxValue, minValue);
+
+		var down = new Element('div', {'class': '_pp_highlight _pp_timepicker_down'});
+		this.setBehavior(down, input, this.decrement, maxValue, minValue);
+
+		td.appendChild(up);
+		td.appendChild(input);
+		td.appendChild(down);
+
+		return td;
+
+	},
+
+	setBehavior: function(button, input, action, maxValue, minValue) {
+		var pressed = false;
+		var multiple = (maxValue - minValue > 30) ? 5 : 1;
+		button.on('mousedown', function(e) {
+				pressed = true;
+				var repeater;
+				setTimeout(function() {
+					if (pressed)
+						repeater = setInterval(function() {
+							if (pressed) {
+								action(input, maxValue, minValue, multiple);
+								this.onChange();
+							} else
+								clearInterval(repeater);
+						}.bind(this), 200);
+				}.bind(this), 500);
+				action(input, maxValue, minValue);
+				this.onChange();
+			}.bindAsEventListener(this));
+		button.on('mouseup', function(e) {
+			pressed = false;
+			}.bindAsEventListener(this));
+		button.on('mouseout', function(e) {
+			pressed = false;
+			}.bindAsEventListener(this));
+	},
+
+	increment: function(input, maxValue, minValue, multiple) {
+		if (!multiple) multiple = 1;
+		var current = input.value*1;
+		current += multiple;
+		current = current - current % multiple;
+		if (current > maxValue)
+			current = minValue;
+		input.value = current < 10 ? '0'+current : current;
+	},
+
+	decrement: function(input, maxValue, minValue, multiple) {
+		if (!multiple) multiple = 1;
+		var current = input.value*1;
+		current -= multiple;
+		current = current - current % multiple;
+		if (current < minValue)
+			current = maxValue;
+		input.value = current < 10 ? '0'+current : current;
+	},
+
+	onChange: function() {
+		if (this.options.onChange)
+			this.options.onChange(this.getTime());
+	},
+
+	onSelect: function() {
+		if (this.options.onSelect)
+			this.options.onSelect(this.getTime());
+	},
+
+	getTime: function() {
+		var d = new Date();
+		var h = this.hours.value * 1;
+		if (!this.options.use24hrs) {
+			if (h == 12 && this.ampm == 'AM')
+				h = 0;
+			else if (h < 12 && this.ampm == 'PM')
+				h += 12;
+		}
+		d.setHours(h);
+		d.setMinutes(this.minutes.value*1);
+		d.setSeconds(this.seconds.value*1);
+		return d;
+	},
+
+	setTime: function(time) {
+		var h = time.getHours();
+		var m = time.getMinutes();
+		var s = time.getSeconds();
+		if (!this.options.use24hrs) {
+			this.ampm = 'AM';
+			this.am.addClassName('_pp_highlight');
+			this.pm.removeClassName('_pp_highlight');
+			if (h > 12) {
+				h -= 12;
+				this.ampm = 'PM';
+				this.pm.addClassName('_pp_highlight');
+				this.am.removeClassName('_pp_highlight');
+			} else if (h == 0) {
+				h = 12;
+			}
+		}
+		this.hours.value = h >= 10 ? h : '0'+h;
+		this.minutes.value = m >= 10 ? m : '0'+m;
+		this.seconds.value = s >= 10 ? s : '0'+s;
+	}
+
+});
+
+Protoplasm.register('timepicker', Control.TimePicker);

+ 52 - 0
caboto-web/src/main/webapp/sparql.html

@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title>Start Caboto SPARQLer</title>
+</head>
+
+<body>
+<h1>Stars Caboto Endpoint SPARQLer</h1>
+
+<div class="moreindent">
+
+<form action="annotation/query/relations" method="get">
+<h2>Keyword search</h2>
+<p><textarea style="background-color: #F0F0F0;" name="query"
+	cols="70" rows="5">
+PREFIX pf: &lt;http://jena.hpl.hp.com/ARQ/property#&gt;
+SELECT distinct ?s 
+{ graph ?g
+{ ?s pf:textMatch 'test' }
+}
+</textarea><br />
+<input type="hidden" name="output" value="xml"/>
+<input type="hidden" name="stylesheet" size="25" value="xml-to-html.xsl"/>
+<input type="submit" value="Get Results" /></p>
+</form>
+
+<form action="annotation/query/relations" method="get">
+<h2>General SPARQL query</h2>
+<p><textarea style="background-color: #F0F0F0;" name="query"
+	cols="70" rows="5"></textarea> <br />
+Target graph URI (or use <code>FROM</code> in the query) <input
+	name="default-graph-uri" size="25" value="" /> <br />
+Output XML: <input type="radio" name="output" value="xml" checked />
+with XSLT style sheet (leave blank for none): <input name="stylesheet"
+	size="25" value="xml-to-html.xsl" /> <br />
+or JSON output: <input type="radio" name="output" value="json" /> <br />
+or text output: <input type="radio" name="output" value="text" /> <br />
+or CSV output: <input type="radio" name="output" value="csv" /> <br />
+or TSV output: <input type="radio" name="output" value="tsv" /> <br />
+Force the accept header to <tt>text/plain</tt> regardless <input
+	type="checkbox" name="force-accept" value="text/plain" /> <br />
+<input type="submit" value="Get Results" /></p>
+</form>
+</div>
+
+<hr />
+
+</body>
+</html>

+ 2 - 6
caboto-web/src/main/webapp/style.css

@@ -2,7 +2,7 @@ body {
     font-family: Arial, Helvetica, sans-serif;
     font-size: 76%;
     padding: 0;
-    margin: 0;
+    margin: 5px;
 }
 
 #container {
@@ -11,7 +11,7 @@ body {
 }
 
 .logout {
-    text-align: center;
+    float: right;
 }
 
 .annotation-entry {
@@ -20,10 +20,6 @@ body {
     clear: both;
 }
 
-#annotations-results {
-    border-bottom: darkgray dashed 1px;
-}
-
 .annotation-entry-title-public {
     padding: 0.4em;
     background-color: bisque;

+ 12 - 0
caboto/src/main/java/org/caboto/filters/AnnotationFilterFactory.java

@@ -59,6 +59,18 @@ public class AnnotationFilterFactory {
         			filters.add(new TypeAnnotationFilter(value));
         		}
         	}
+                else if (attVal.getKey().equalsIgnoreCase("from")) {
+        		for (String value: attVal.getValue()) {
+                            if(value != null && value.trim().length() > 0)
+        			filters.add(new DateAnnotationFilter(value, true));
+        		}
+        	}
+                else if (attVal.getKey().equalsIgnoreCase("to")) {
+        		for (String value: attVal.getValue()) {
+                            if(value != null && value.trim().length() > 0)
+        			filters.add(new DateAnnotationFilter(value, false));
+        		}
+        	}
         	else if (attVal.getKey().contains(":")) {
                 for (String value: attVal.getValue()) {
                     filters.add(new PropValAnnotationFilter(attVal.getKey(),

+ 80 - 0
caboto/src/main/java/org/caboto/filters/DateAnnotationFilter.java

@@ -0,0 +1,80 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package org.caboto.filters;
+
+import com.hp.hpl.jena.datatypes.xsd.XSDDateTime;
+import com.hp.hpl.jena.query.Query;
+import com.hp.hpl.jena.sparql.core.Var;
+import com.hp.hpl.jena.sparql.expr.*;
+import com.hp.hpl.jena.sparql.expr.nodevalue.NodeValueDateTime;
+import com.hp.hpl.jena.sparql.syntax.ElementFilter;
+import com.hp.hpl.jena.sparql.syntax.ElementGroup;
+import com.hp.hpl.jena.sparql.syntax.TripleCollector;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ *
+ * @author ecjet
+ */
+class DateAnnotationFilter extends AnnotationFilterBase {
+
+    private static final String DATETIME_FORMAT = "dd-MM-yyyy";
+    private Date date;
+    private boolean isFrom;
+
+    public DateAnnotationFilter(String valueS, boolean isFrom) {
+        this.isFrom = isFrom;
+        // Define date format
+        SimpleDateFormat format = new SimpleDateFormat(DATETIME_FORMAT);
+        try {
+            // Create a date object
+            date = format.parse(valueS);
+        } catch (ParseException ex) {
+            throw new IllegalArgumentException("Illegal date: " + valueS);
+        }
+    }
+
+    @Override
+    public void augmentBlock(TripleCollector arg0, String annotationBodyVar, String annotationHeadVar) {
+    }
+
+    @Override
+    public void visit(ElementGroup arg0) {
+        super.visit(arg0);
+        if (inDefaultGraph()) {
+            Var varCreated = Var.alloc("created");
+            // Create dateTime expression
+            Expr exprDate = new NodeValueDateTime(dateToXSDDateTime(date));
+            // Adds a filter.  Need to wrap variable in a NodeVar.
+            Expr exprCreated = new ExprVar(varCreated);
+            Expr expr;
+            if(isFrom) {
+                expr = new E_GreaterThanOrEqual(exprCreated, exprDate);
+            } else {
+                expr = new E_LessThan(exprCreated, exprDate);
+            }
+            ElementFilter filter = new ElementFilter(expr);
+            arg0.addElementFilter(filter);
+        }
+    }
+    
+    private XSDDateTime dateToXSDDateTime(Date date) {
+        // Get a calendar
+        Calendar cal = Calendar.getInstance();
+        // Set time of the calendar to specified date
+        cal.setTime(date);
+        // Increment if 'to'
+        // <= 2011-11-11T00:00:00Z becomes < 2011-11-12T00:00:00Z
+        if(!isFrom)
+            cal.add(Calendar.DAY_OF_MONTH, +1);
+        // return the newly created object using calendar
+        return new XSDDateTime(cal);
+    }
+}