Skip to content

Commit caf5612

Browse files
committed
fix: issue2551390 - Replace text input/calendar popup with native date input
Docs, code and test changes for the changeover to a native date element. See issue for details.
1 parent e5e527c commit caf5612

File tree

24 files changed

+1022
-14
lines changed

24 files changed

+1022
-14
lines changed

CHANGES.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,10 @@ Fixed:
7777
- issue2551391, partial fix for issue1513369. input fields were
7878
not getting id's assigned. Fixed automatic id assignment to
7979
input fields. Thinko in the code. (John Rouillard)
80+
- issue2551390 - Replace text input/calendar popup with native
81+
date input. Also add double-click and exit keyboard handlers to
82+
allow copy/paste/editing the text version of the date. (John
83+
Rouillard)
8084

8185
Features:
8286

doc/customizing.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,11 +90,11 @@ caches the schema).
9090
<td tal:content="structure context/due_date/field" />
9191
</tr>
9292

93-
If you want to show only the date part of due_date then do this instead::
93+
If you want to show the date and time for due_date then do this instead::
9494

9595
<tr>
9696
<th>Due Date</th>
97-
<td tal:content="structure python:context.due_date.field(format='%Y-%m-%d')" />
97+
<td tal:content="structure context/due_date/field_time" />
9898
</tr>
9999

100100
3. Add the property to the ``issue.index.html`` page::

doc/reference.txt

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3070,17 +3070,28 @@ There are several methods available on these wrapper objects:
30703070
tri-state yes/no/neither selection. This method may take some
30713071
arguments:
30723072

3073-
size
3073+
size (default 30)
30743074
Sets the width in characters of the edit field
30753075

30763076
format (Date properties only)
3077-
Sets the format of the date in the field - uses the same
3078-
format string argument as supplied to the ``pretty`` method
3079-
below.
3077+
Sets the format of the date in the field - uses the
3078+
same format string argument as supplied to the
3079+
``pretty`` method below. If you use this, it will
3080+
prevent the use of browser native date inputs. It is
3081+
useful if you want partial dates. For example using
3082+
``format="%Y-%m"`` with ``type="text"`` will display a
3083+
text edit box with the year and month part of your
3084+
date.
3085+
3086+
type (depends on property type)
3087+
Sets the type property of the input. To change a date
3088+
property field from a native date input to a text
3089+
input you would use ``type="text"``.
30803090

30813091
popcal (Date properties only)
3082-
Include the JavaScript-based popup calendar for date
3083-
selection. Defaults to on.
3092+
Include a link to the JavaScript-based popup calendar
3093+
for date selection. Defaults to off/False since native
3094+
date inputs supply popup calendars.
30843095

30853096
y_label, n_label, u_label (Boolean properties only)
30863097
Set the labels for the true/false/undefined
@@ -3104,6 +3115,10 @@ There are several methods available on these wrapper objects:
31043115
attribute without a value. This is useful for boolean
31053116
properties like ``required``.
31063117

3118+
field_time (Date properties only)
3119+
Create a browser native input for editing date and time.
3120+
The field method creates an input for editing
3121+
month/day/year (without time).
31073122

31083123
rst only on String properties - render the value of the property
31093124
as ReStructuredText (requires the :ref:`Docutils
@@ -3164,7 +3179,11 @@ There are several methods available on these wrapper objects:
31643179
is returned if the value is ``None`` otherwise it
31653180
is converted to a string.
31663181

3167-
popcal Generate a link to a popup calendar which may be used to
3182+
popcal This is deprecated with Roundup 2.5 which uses the
3183+
native HTML5 date input. The native date input
3184+
includes a calendar popup on modern broswers.
3185+
3186+
Generate a link to a popup calendar which may be used to
31683187
edit the date field, for example::
31693188

31703189
<span tal:replace="structure context/due/popcal" />
@@ -4027,6 +4046,17 @@ This example indicates that the value sent back to the user is actually
40274046
comma-separated value content (i.e. something to load into a
40284047
spreadsheet or database).
40294048

4049+
CSS for the web interface
4050+
-------------------------
4051+
4052+
The web interface can be completely redesigned by the admin, However
4053+
some parts of Roundup use classes or set attributes that can be
4054+
selected by css to change the look of the element.
4055+
4056+
The ``datecopy.js`` module used to allow editing a date value with a
4057+
text input assigns the ``mode_textdate`` class to the input when it is
4058+
in text mode. The class is removed when it is not in text mode.
4059+
40304060

40314061
8-bit character set support in Web interface
40324062
--------------------------------------------

doc/upgrading.txt

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,148 @@ defusedxml.
224224

225225
.. _defusedxml: https://pypi.org/project/defusedxml/
226226

227+
Use native date inputs (optional)
228+
---------------------------------
229+
230+
Roundup now uses native date or datetime-local inputs for Date()
231+
properties. These inputs take the place of the text input and
232+
calendar popup from earlier Roundup versions. Modern browsers
233+
come with a built-in calendar for date selection, so the
234+
``(cal)`` calendar link is no longer needed. These native inputs
235+
show the date based on the browser's locale and translate terms
236+
into the local language.
237+
238+
If you do nothing, simple uses of the field() method will
239+
generate date inputs to allow selection of a date. Input fields
240+
for Date() properties will not have the ``(cal)`` link
241+
anymore. Complex uses will not be upgraded and will operate like
242+
earlier Roundup versions.
243+
244+
To upgrade all date properties, there are four changes to make:
245+
246+
1. Replace ``field`` calls with ``field_time`` where needed.
247+
248+
2. Remove the format argument from field() calls on Date()
249+
properties.
250+
251+
3. Remove popcal() calls.
252+
253+
4. Include datecopy.js in page.html.
254+
255+
Use field_time() where needed
256+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
257+
258+
The format used by ``field`` does not include hours, minutes or
259+
seconds. If your users need to enter times, you should change
260+
these calls to use ``field_time``. The arguments are the same as
261+
for field.
262+
263+
Remove format argument
264+
~~~~~~~~~~~~~~~~~~~~~~
265+
266+
Speaking of arguments, avoid setting the date format if you want
267+
to use native date inputs. The date value needs a specific format
268+
for date or datetime-local inputs. If you include the `format`
269+
argument in the `field` method, it should be removed.
270+
271+
The `field` method uses the format ``%Y-%m-%d``. The
272+
``field_time`` method uses the format ``%Y-%m-%dT%H:%M:%S``. If
273+
you use these exact formats, Roundup will accept them and use a
274+
native date input.
275+
276+
.. highlight:: text
277+
278+
If you use an format that doesn't match, you will see a text
279+
input and a logged warning message like::
280+
281+
Format '%Y-%m' prevents use of modern date input.
282+
Remove format from field() call in template test.item.
283+
Using text input.
284+
285+
.. highlight:: default
286+
287+
The log message will appear if your logging level is set to
288+
WARNING or lower. (Refer to your tracker's :ref:`config.ini
289+
logging section <config-ini-section-logging>` for details on
290+
logging levels.)
291+
292+
If you want to use a text input for a specific date format, you
293+
can add ``type="text"`` to the field() argument list to suppress
294+
the warning. By default using a format argument will show the
295+
popup calendar link. You can disable the link by setting
296+
``popcal=False`` in the field() call. If you have::
297+
298+
tal:content="structure python:context.duedate.field(
299+
placeholder='YYYY-MM, format='%Y-%m')"
300+
301+
changing it to::
302+
303+
tal:content="structure python:context.duedate.field(
304+
type='text',
305+
placeholder='YYYY-MM, format='%Y-%m',
306+
popcal=False)"
307+
308+
will generate the input as in Roundup 2.4 or earlier without a
309+
popcal link.
310+
311+
If you are using a path expression like::
312+
313+
tal:content="context/duedate/field"
314+
315+
change it to::
316+
317+
tal:content="structure python:context.duedate.field(
318+
type='text')"
319+
320+
to get the input from before Roundup 2.5 with a popcal link.
321+
322+
Remove popcal
323+
~~~~~~~~~~~~~
324+
325+
If you use the ``popcal()`` method directly in your templates, you
326+
can remove them. The browser's native date selection calendar can
327+
be used instead.
328+
329+
Add copy/paste/edit on double-click using datecopy.js
330+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
331+
332+
There is no way to copy/paste using a native datetime-local or
333+
date input. With the datecopy.js file installed, double-clicking
334+
on the input turns it into a normal text input with the ability
335+
to copy, paste, or manually edit the date.
336+
337+
To set this up, take either ``datecopy.js`` or the smaller
338+
version, ``datecopy.min.js``, from the ``html`` folder of the
339+
classic tracker template. Put the file in the ``html`` folder of
340+
your tracker home.
341+
342+
After you install the datecopy file, you can add the script
343+
directly to a page using::
344+
345+
<script tal:attributes="nonce request/client/client_nonce"
346+
tal:content="structure python:utils.readfile('datecopy.min.js')">
347+
</script>
348+
349+
or get the file in a separate download using a regular script
350+
tag::
351+
352+
<script type="text/javascript" src="@@file/datecopy.js">
353+
</script>
354+
355+
You can place these at the end of ``page.html`` just before the
356+
close body ``</body>`` tag. This is the method used in the
357+
classic template. This forces the file to be run for every page
358+
even those that don't have any date inputs. However, it is cached
359+
after the first download.
360+
361+
Alternatively you can inline or link to it using a script tag
362+
only on pages that will have a date input. For example
363+
``issue.item.html``.
364+
365+
There is no support for activating text mode using the
366+
keyboard. Tablet/touch support is mixed. Chrome supports
367+
double-tap to activate text mode input. Firefox does not.
368+
227369
Change in REST response for invalid CORS requests (info)
228370
--------------------------------------------------------
229371

doc/user_guide.txt

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,14 @@ Classhelper`_ for details.
177177
Date properties
178178
~~~~~~~~~~~~~~~
179179

180+
Date properties are usually shown using a native HTML date
181+
element. This provides a calendar button for choosing the
182+
date. The date is shown in the normal format for your location.
183+
184+
Native date inputs do not allow the use of partial forms as
185+
defined below. For this reason, you may edit a date/time
186+
stamp directly.
187+
180188
Date-and-time stamps are specified with the date in
181189
international standard format (``yyyy-mm-dd``) joined to the time
182190
(``hh:mm:ss``) by a period ``.``. Dates in this form can be easily
@@ -191,7 +199,7 @@ just the year may be omitted. If the time is given, the time is
191199
interpreted in the user's local time zone. The Date constructor takes
192200
care of these conversions. In the following examples, suppose that
193201
``yyyy`` is the current year, ``mm`` is the current month, and ``dd`` is
194-
the current day of the month.
202+
the current day of the month and the local timezone is GMT-5.
195203

196204
- "2000-04-17" means <Date 2000-04-17.00:00:00>
197205
- "01-25" means <Date yyyy-01-25.00:00:00>
@@ -202,6 +210,30 @@ the current day of the month.
202210
- "8:47:11" means <Date yyyy-mm-dd.13:47:11>
203211
- the special date "." means "right now"
204212

213+
The native date input doesn't allow copy or paste. Roundup
214+
enhances the native date field. If you double-click on a native
215+
date field, it changes to a text input mode with the date already
216+
selected. You can use control-C to copy the date or control-V to
217+
paste into the field. Double-clicking also lets you add seconds
218+
in a date-time value if you need to.
219+
220+
It will switch back to a date input and save the value when:
221+
222+
- you move to another field using the mouse or the Tab key.
223+
- you press enter/return (press return again if you want to
224+
submit the form).
225+
226+
If you press Escape, it will restore the original value and
227+
change back to a date input.
228+
229+
When using native date elements in text input mode, the date
230+
looks like a full data with ``T`` replacing ``.``. If the ``T``
231+
is missing, the native date elements will not recognize the value
232+
as a date.
233+
234+
There is no support for activating text mode using the
235+
keyboard. Tablet/touch support is mixed. Chrome supports
236+
double-tap to activate text mode input. Firefox does not.
205237

206238
When searching, a plain date entered as a search field will match that date
207239
exactly in the database. We may also accept ranges of dates. You can

roundup/cgi/templating.py

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2252,7 +2252,16 @@ def now(self, str_interval=None):
22522252
return DateHTMLProperty(self._client, self._classname, self._nodeid,
22532253
self._prop, self._formname, ret)
22542254

2255-
def field(self, size=30, default=None, format=_marker, popcal=True,
2255+
2256+
def field_time(self, size=30, default=None, format=_marker, popcal=None,
2257+
**kwargs):
2258+
2259+
kwargs.setdefault("type", "datetime-local")
2260+
field = self.field(size=size, default=default, format=format,
2261+
popcal=popcal, **kwargs)
2262+
return field
2263+
2264+
def field(self, size=30, default=None, format=_marker, popcal=None,
22562265
**kwargs):
22572266
"""Render a form edit field for the property
22582267
@@ -2269,6 +2278,47 @@ def field(self, size=30, default=None, format=_marker, popcal=True,
22692278
else:
22702279
return self.pretty(format)
22712280

2281+
kwargs.setdefault("type", "date")
2282+
2283+
if kwargs["type"] in ["date", "datetime-local"]:
2284+
acceptable_formats = {
2285+
"date": "%Y-%m-%d",
2286+
"datetime-local": "%Y-%m-%dT%H:%M:%S"
2287+
}
2288+
2289+
if format is not self._marker: # user set format
2290+
if format != acceptable_formats[kwargs["type"]]:
2291+
# format is incompatible with date type
2292+
kwargs['type'] = "text"
2293+
if popcal is not False:
2294+
popcal = True
2295+
logger.warning(self._(
2296+
"Format '%(format)s' prevents use of modern "
2297+
"date input. Remove format from field() call in "
2298+
"template %(class)s.%(template)s. "
2299+
"Using text input.") % {
2300+
"format": format,
2301+
"class": self._client.classname,
2302+
"template": self._client.template
2303+
})
2304+
2305+
"""
2306+
raise ValueError(self._(
2307+
"When using input type of '%(field_type)s', the "
2308+
"format must not be set, or must be "
2309+
"'%(format_string)s' to match RFC3339 date "
2310+
"or date-time. Current format is '%(format)s'.") % {
2311+
"field_type": kwargs["type"],
2312+
"format_string":
2313+
acceptable_formats[kwargs["type"]],
2314+
"format": format,
2315+
})"""
2316+
else:
2317+
# https://developer.mozilla.org/en-US/docs/Web/HTML/Date_and_time_formats#local_date_and_time_strings
2318+
# match date-time format in
2319+
# https://www.rfc-editor.org/rfc/rfc3339
2320+
format = acceptable_formats[kwargs['type']]
2321+
22722322
value = self._value
22732323

22742324
if value is None:

roundup/date.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ def cmp(a, b):
4646
date_re = re.compile(r'''^
4747
((?P<y>\d\d\d\d)([/-](?P<m>\d\d?)([/-](?P<d>\d\d?))?)? # yyyy[-mm[-dd]]
4848
|(?P<a>\d\d?)[/-](?P<b>\d\d?))? # or mm-dd
49-
(?P<n>\.)? # .
49+
(?P<n>[.T])? # . or T
5050
(((?P<H>\d?\d):(?P<M>\d\d))?(:(?P<S>\d\d?(\.\d+)?))?)? # hh:mm:ss
5151
(?:(?P<tz>\s?[+-]\d{4})|(?P<o>[\d\smywd\-+]+))? # time-zone offset, offset
5252
$''', re.VERBOSE)

0 commit comments

Comments
 (0)