branding/templates/header.pxt | 1
java/code/src/com/redhat/rhn/frontend/taglibs/FormatDateTag.java | 390 ++++++++++
java/code/src/com/redhat/rhn/frontend/taglibs/rhn-taglib.tld | 44 +
rel-eng/packages/spacewalk-web | 2
web/html/javascript/spacewalk-essentials.js | 37
web/modules/pxt/PXT/HTML.pm | 29
web/modules/sniglets/Sniglets/HTML.pm | 18
web/modules/sniglets/Sniglets/ListView/List.pm | 15
web/modules/sniglets/Sniglets/ListView/Parser.pm | 2
web/spacewalk-web.spec | 7
10 files changed, 541 insertions(+), 4 deletions(-)
New commits:
commit 143c581d897b91c6693ea9d01728dcfce04a4322
Author: Michael Mraka <michael.mraka(a)redhat.com>
Date: Mon Jan 27 14:56:51 2014 +0100
Automatic commit of package [spacewalk-web] release [2.1.51-1].
diff --git a/rel-eng/packages/spacewalk-web b/rel-eng/packages/spacewalk-web
index 062c215..1773ec4 100644
--- a/rel-eng/packages/spacewalk-web
+++ b/rel-eng/packages/spacewalk-web
@@ -1 +1 @@
-2.1.50-1 web/
+2.1.51-1 web/
diff --git a/web/spacewalk-web.spec b/web/spacewalk-web.spec
index 4e9db98..dcef557 100644
--- a/web/spacewalk-web.spec
+++ b/web/spacewalk-web.spec
@@ -4,7 +4,7 @@ Name: spacewalk-web
Summary: Spacewalk Web site - Perl modules
Group: Applications/Internet
License: GPLv2
-Version: 2.1.50
+Version: 2.1.51
Release: 1%{?dist}
URL:
https://fedorahosted.org/spacewalk/
Source0:
https://fedorahosted.org/releases/s/p/spacewalk/%{name}-%{version}.tar.gz
@@ -269,6 +269,11 @@ rm -rf $RPM_BUILD_ROOT
%doc LICENSE
%changelog
+* Mon Jan 27 2014 Michael Mraka <michael.mraka(a)redhat.com> 2.1.51-1
+- Add a rhn-date tag
+- Implement rhn:formatDate that uses moment.js on the client side. It supports
+ also most of fmt:formatDate options.
+
* Fri Jan 24 2014 Jan Dobes 2.1.50-1
- delete old pages
- porting system group monitoring probes page to java
commit ee0ffead6316245affc0f9375e540be342483b5c
Author: Duncan Mac-Vicar P <dmacvicar(a)suse.de>
Date: Thu Jan 16 15:57:19 2014 +0100
Add a rhn-date tag: <rhn-date>2014-01-09 06:42:56.414196-05</rhn-date>
will produce:
<time datetime="2014-01-09 06:42:56.414196-05">01/ 9/14 6:42:56 AM
EST</time>
If you set humanStyle="from" it will add the css class that javascript will
convert the
formatted text inside the time tag to language "1 hour ago...".
You can also use "calendar".
diff --git a/branding/templates/header.pxt b/branding/templates/header.pxt
index c567b3a..97aaf62 100644
--- a/branding/templates/header.pxt
+++ b/branding/templates/header.pxt
@@ -19,6 +19,7 @@
<script src="/javascript/jquery.js"></script>
<script src="/javascript/bootstrap.js"></script>
+ <script src="/javascript/momentjs/moment.min.js"></script>
<script src="/javascript/spacewalk-essentials.js"></script>
<script src="/javascript/spacewalk-checkall.js"></script>
diff --git a/web/modules/pxt/PXT/HTML.pm b/web/modules/pxt/PXT/HTML.pm
index dde1c7a..ffa0b32 100644
--- a/web/modules/pxt/PXT/HTML.pm
+++ b/web/modules/pxt/PXT/HTML.pm
@@ -600,4 +600,33 @@ sub icon {
return $icon;
}
+#date(
+# -value => iso_formatted_date,
+# -formatted => date_in_user_format
+# -humanStyle => true|false|1|0 whether to show the date
+# in a natural language format too
+# );
+sub date {
+ my $class = shift;
+ my %params = @_;
+
+ my $cssclass = "";
+ die "empty value attribute" if not exists $params{-value};
+ my $formatted = $params{-value};
+ if (exists $params{-formatted}) {
+ $formatted = $params{-formatted};
+ }
+
+ if (exists $params{-humanStyle} && ($params{-humanStyle} eq "from"))
{
+ $cssclass = " class=\"human-from\"";
+ }
+ elsif (exists $params{-humanStyle} && ($params{-humanStyle} eq
"calendar")) {
+ $cssclass = " class=\"human-calendar\"";
+ }
+
+ my $date = qq{<time$cssclass
datetime=\"$params{-value}\">$formatted</time>};
+
+ return $date;
+}
+
1;
diff --git a/web/modules/sniglets/Sniglets/HTML.pm
b/web/modules/sniglets/Sniglets/HTML.pm
index 421abb5..f1a04d0 100644
--- a/web/modules/sniglets/Sniglets/HTML.pm
+++ b/web/modules/sniglets/Sniglets/HTML.pm
@@ -43,6 +43,7 @@ sub register_tags {
$pxt->register_tag("rhn-autorefresh-widget",
\&rhn_autorefresh_widget);
$pxt->register_tag("rhn-return-link", \&return_link);
$pxt->register_tag("rhn-icon", \&rhn_icon);
+ $pxt->register_tag("rhn-date", \&rhn_date);
}
@@ -345,4 +346,21 @@ sub rhn_icon {
%params = map {("-$_" => $params{$_})} keys %params;
return PXT::HTML->icon(%params);
}
+
+sub rhn_date {
+ my $pxt = shift;
+ my %params = validate(@_, {'humanStyle' => 0, '__block__' =>
0});
+
+ %params = map {("-$_" => $params{$_})} keys %params;
+
+ my $datetime = $params{-__block__};
+ $datetime =~ s/^\s+//;
+ $datetime =~ s/\s+$//;
+
+ my $formatted = $pxt->user->convert_time($datetime);
+
+ $params{-value} .= $datetime;
+ $params{-formatted} .= $formatted;
+ return PXT::HTML->date(%params);
+}
1;
diff --git a/web/modules/sniglets/Sniglets/ListView/List.pm
b/web/modules/sniglets/Sniglets/ListView/List.pm
index f5cada4..1e62114 100644
--- a/web/modules/sniglets/Sniglets/ListView/List.pm
+++ b/web/modules/sniglets/Sniglets/ListView/List.pm
@@ -811,6 +811,17 @@ sub render_action_items {
return $ret;
}
+sub render_date {
+ my $self = shift;
+ my $pxt = shift;
+ my $col = shift;
+ my $datetime = shift;
+ my $formatted = shift;
+ return PXT::HTML->date(-value => $datetime,
+ -formatted => $formatted,
+ -humanStyle => $col->is_date());
+}
+
sub render_url {
my $self = shift;
my $pxt = shift;
@@ -1131,7 +1142,9 @@ sub render {
my $col_data = defined $row->{uc $label} ? $row->{uc $label} : '';
if ($col->is_date()) {
- $col_data = $pxt->user->convert_time($col_data);
+ my $formatted = $pxt->user->convert_time($col_data);
+ my $isotime = $pxt->user->convert_time($col_data, "%FT%T%z");
+ $col_data = $self->render_date($pxt, $col, $isotime, $formatted);
}
diff --git a/web/modules/sniglets/Sniglets/ListView/Parser.pm
b/web/modules/sniglets/Sniglets/ListView/Parser.pm
index be7d634..895df84 100644
--- a/web/modules/sniglets/Sniglets/ListView/Parser.pm
+++ b/web/modules/sniglets/Sniglets/ListView/Parser.pm
@@ -125,7 +125,7 @@ sub process_column {
my $nowrap = $xml_node->getAttribute('nowrap') || '';
my $acl = $xml_node->getAttribute('acl') || '';
my $htmlify = $xml_node->getAttribute('htmlify') ? 1 : 0;
- my $is_date = $xml_node->getAttribute('is_date') ? 1 : 0;
+ my $is_date = $xml_node->getAttribute('is_date') || 0;
my $column = new Sniglets::ListView::ParsedColumn name => $name, label =>
$label;
$column->sort_by($sort_by);
commit 5a468909ac58763ffb50a64e9a71fae04523f51e
Author: Duncan Mac-Vicar P <dmacvicar(a)suse.de>
Date: Wed Dec 11 15:29:56 2013 +0100
Implement rhn:formatDate that uses moment.js on the client side. It supports also most
of fmt:formatDate options.
diff --git a/java/code/src/com/redhat/rhn/frontend/taglibs/FormatDateTag.java
b/java/code/src/com/redhat/rhn/frontend/taglibs/FormatDateTag.java
new file mode 100644
index 0000000..a485584
--- /dev/null
+++ b/java/code/src/com/redhat/rhn/frontend/taglibs/FormatDateTag.java
@@ -0,0 +1,390 @@
+/**
+ * Copyright (c) 2013 SUSE
+ *
+ * This software is licensed to you under the GNU General Public License,
+ * version 2 (GPLv2). There is NO WARRANTY for this software, express or
+ * implied, including the implied warranties of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2
+ * along with this software; if not, see
+ *
http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
+ *
+ * Red Hat trademarks are not licensed under GPLv2. No permission is
+ * granted to use or replicate Red Hat trademarks that are incorporated
+ * in this software or its documentation.
+ */
+package com.redhat.rhn.frontend.taglibs;
+
+import com.redhat.rhn.common.localization.LocalizationService;
+import com.redhat.rhn.domain.user.User;
+import com.redhat.rhn.frontend.struts.RequestContext;
+import java.util.Date;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Locale;
+import javax.servlet.ServletRequest;
+import javax.servlet.http.HttpServletRequest;
+
+import javax.servlet.jsp.JspException;
+import javax.servlet.jsp.JspWriter;
+import javax.servlet.jsp.tagext.TagSupport;
+
+/**
+ * <strong>FormatDateTag</strong>
+ * Displays a value in a human format (eg. 3 minutes ago)
+ * <pre>
+ * <rhn:human-value value="${bean.value}">
+ * </pre> Outputs a human readable text for the value relative to now.<br>
+ *
+ */
+public class FormatDateTag extends TagSupport {
+
+ private static final String SHORT = "short";
+ private static final String MEDIUM = "medium";
+ private static final String LONG = "long";
+ private static final String FULL = "full";
+
+ private static final String DATE = "date";
+ private static final String TIME = "time";
+ private static final String BOTH = "both";
+
+ private static final String FROM = "from";
+ private static final String CALENDAR = "calendar";
+ private static final String NONE = "none";
+
+ // ISO format usable by moment.js parsing
+ private static final String ISO_FORMAT =
"yyyy-MM-dd'T'HH:mm:ssXXX";
+ private final DateFormat isoFormatter = new SimpleDateFormat(ISO_FORMAT);
+
+ protected Date value;
+ protected Date reference;
+ protected Locale locale;
+
+ // cache
+ private Locale bestLocale;
+
+ // fmt:formatDate equivalents
+ protected String pattern;
+ protected String dateStyle;
+ protected String timeStyle;
+ protected String type;
+
+ protected String humanStyle;
+
+ /**
+ * @return Current set reference
+ */
+ public Date getReference() {
+ return reference;
+ }
+
+ /**
+ * The reference date used instead of now
+ * when calculating intervals or durations
+ *
+ * @param ref date value
+ */
+ public void setReference(Date ref) {
+ this.reference = ref;
+ }
+
+ /**
+ * @return The current set human style
+ */
+ public String getHumanStyle() {
+ return humanStyle;
+ }
+
+ /**
+ * Set human style
+ * @param style value
+ *
+ * Valid values:
+ * "none" (default)
+ * "from" date from now or reference
+ * "calendar" calendar date
+ */
+ public void setHumanStyle(String style) {
+ this.humanStyle = style;
+ }
+
+ /**
+ * Constructor
+ */
+ public FormatDateTag() {
+ reset();
+ }
+
+ /**
+ * @return The current pattern for the formatter
+ * @see fmt:FormatDate tag
+ */
+ public String getPattern() {
+ return pattern;
+ }
+
+ /**
+ * @param pat The desired pattern for the formatter
+ * @see fmt:FormatDate tag
+ */
+ public void setPattern(String pat) {
+ this.pattern = pat;
+ }
+
+ /**
+ * @return The date style for the formatter
+ * @see fmt:FormatDate tag
+ */
+ public String getDateStyle() {
+ return dateStyle;
+ }
+
+ /**
+ * @param style Desired date style for the formatter
+ *
+ * Valid values: default, shot, medium, long, full
+ *
+ * @see fmt:FormatDate tag
+ */
+ public void setDateStyle(String style) {
+ this.dateStyle = style;
+ }
+
+ /**
+ * @return The time style for the formatter
+ * @see fmt:FormatDate
+ */
+ public String getTimeStyle() {
+ return timeStyle;
+ }
+
+ /**
+ * @param style Desired time style for the formatter
+ * @see fmt:FormatDate
+ */
+ public void setTimeStyle(String style) {
+ this.timeStyle = style;
+ }
+
+ /**
+ * @return Type of the formatter (display date, time or both)
+ *
+ * @see fmt:FormatDate
+ */
+ public String getType() {
+ return type;
+ }
+
+ /**
+ * @param typ Desired type for the formatter
+ *
+ * Valid values: date, time or both
+ *
+ * @see fmt:FormatDate
+ */
+ public void setType(String typ) {
+ this.type = typ;
+ }
+
+ /**
+ * @return The locale for the formatting, defaults to the application
+ * settings
+ */
+ public Locale getLocale() {
+ return locale;
+ }
+
+ /**
+ * @param loc Locale to do the formatting, defaults to the application
+ * settings
+ */
+ public void setLocale(Locale loc) {
+ this.locale = loc;
+ }
+
+ /**
+ * @return value to be formatted
+ */
+ public Date getValue() {
+ return value;
+ }
+
+ /**
+ * @param val the value to be formatted
+ */
+ public void setValue(Date val) {
+ this.value = val;
+ }
+
+ /**
+ * renders code that includes the moment.js library, only
+ * if it has not been included before by the same tag
+ * @param out Where to render to
+ * @throws IOException
+ */
+ protected void renderMomentInclude(Writer out) throws IOException {
+ if (pageContext.getAttribute("__spacewalk_momentjs_included") == null)
{
+ out.append("<script type=\"text/javascript\"
src=\"" +
+
"/javascript/momentjs/moment-with-langs.min.js\"></script>");
+
+ out.append("<script type=\"text/javascript\">");
+ out.append(" moment.lang(\"" + getBestLocale() +
"\");");
+ out.append("</script>");
+ pageContext.setAttribute("__spacewalk_momentjs_included", true);
+ }
+ }
+
+ /**
+ * @return the date formatted into the applications preferred
+ * string format
+ */
+ protected String getFormattedDate() {
+ String fmtDate;
+ // use spacewalk defaults if the formatter is not customized
+ if (!isFormatCustomized()) {
+ fmtDate = LocalizationService.getInstance().formatDate(getValue());
+ }
+ else {
+ DateFormat fmt = getFormatter();
+ if (getPattern() != null) {
+ SimpleDateFormat simplefmt = (SimpleDateFormat) fmt;
+ simplefmt.applyPattern(pattern);
+ }
+ fmtDate = fmt.format(value);
+ }
+ return fmtDate;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @throws JspException
+ */
+ @Override
+ public int doStartTag() throws JspException {
+ try {
+ JspWriter out = pageContext.getOut();
+
+ renderMomentInclude(out);
+
+ out.append(" <time");
+ out.append(getCssClass());
+ out.append(" data-reference-date=\"" +
+ isoFormatter.format(getReference()) + "\"");
+ out.append(" datetime=\"" +
+ isoFormatter.format(getValue()) + "\">");
+ out.append(getFormattedDate());
+ out.append(" </time>");
+ }
+ catch (IOException ioe) {
+ throw new JspException("IO error writing to JSP file:", ioe);
+ }
+ return (SKIP_BODY);
+ }
+
+ protected String getCssClass() {
+ if (FROM.equalsIgnoreCase(getHumanStyle())) {
+ return " class=\"human-from\"";
+ }
+ else if (CALENDAR.equalsIgnoreCase(getHumanStyle())) {
+ return " class=\"human-calendar\"";
+ }
+ else {
+ return "";
+ }
+ }
+
+ private int getStyle(String style) {
+ int ret = DateFormat.DEFAULT;
+ if (SHORT.equalsIgnoreCase(style)) {
+ ret = DateFormat.SHORT;
+ }
+ else if (MEDIUM.equalsIgnoreCase(style)) {
+ ret = DateFormat.MEDIUM;
+ }
+ else if (LONG.equalsIgnoreCase(style)) {
+ ret = DateFormat.LONG;
+ }
+ else if (FULL.equalsIgnoreCase(style)) {
+ ret = DateFormat.FULL;
+ }
+ return ret;
+ }
+
+ protected boolean isFormatCustomized() {
+ return (getPattern() != null ||
+ getType() != null ||
+ getDateStyle() != null ||
+ getTimeStyle() != null);
+ }
+
+ /**
+ * @return The final locale used to format the date
+ *
+ * If the tag did not supply one, use the application user
+ * configured locale.
+ */
+ protected Locale getBestLocale() {
+ if (bestLocale == null) {
+ if (getLocale() == null) {
+ ServletRequest req = pageContext.getRequest();
+ RequestContext ctx = new RequestContext((HttpServletRequest) req);
+ User user = ctx.getCurrentUser();
+ String userLocale = user.getPreferredLocale();
+ if (userLocale != null) {
+ bestLocale = new Locale(userLocale);
+ }
+ else {
+ bestLocale = Locale.getDefault();
+ }
+ }
+ else {
+ bestLocale = getLocale();
+ }
+ }
+ return bestLocale;
+ }
+
+ protected DateFormat getFormatter() {
+ if (DATE.equalsIgnoreCase(getType())) {
+ return DateFormat.getDateInstance(getStyle(getDateStyle()),
getBestLocale());
+ }
+ else if (TIME.equalsIgnoreCase(getType())) {
+ return DateFormat.getTimeInstance(getStyle(getTimeStyle()),
getBestLocale());
+ }
+ else if (BOTH.equalsIgnoreCase(getType())) {
+ return DateFormat.getDateTimeInstance(getStyle(getDateStyle()),
+ getStyle(getTimeStyle()), getBestLocale());
+ }
+ else {
+ return DateFormat.getInstance();
+ }
+ }
+
+ // to share with the constructor
+ private void reset() {
+ value = new Date();
+ reference = new Date();
+ humanStyle = null;
+ locale = null;
+ // cached
+ bestLocale = null;
+
+ // compatibility with fmt:formatDate
+ dateStyle = null;
+ timeStyle = null;
+ type = null;
+ pattern = null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void release() {
+ reset();
+ super.release();
+ }
+}
diff --git a/java/code/src/com/redhat/rhn/frontend/taglibs/rhn-taglib.tld
b/java/code/src/com/redhat/rhn/frontend/taglibs/rhn-taglib.tld
index 851c66e..7bce754 100644
--- a/java/code/src/com/redhat/rhn/frontend/taglibs/rhn-taglib.tld
+++ b/java/code/src/com/redhat/rhn/frontend/taglibs/rhn-taglib.tld
@@ -10,6 +10,50 @@
<
uri>http://rhn.redhat.com/rhn</uri>
<!-- Other tags defined earlier -->
<tag>
+ <name>formatDate</name>
+ <tag-class>com.redhat.rhn.frontend.taglibs.FormatDateTag</tag-class>
+ <attribute>
+ <name>value</name>
+ <required>true</required>
+ <rtexprvalue>true</rtexprvalue>
+ </attribute>
+ <attribute>
+ <name>humanStyle</name>
+ <required>false</required>
+ <rtexprvalue>false</rtexprvalue>
+ </attribute>
+ <attribute>
+ <name>reference</name>
+ <required>false</required>
+ <rtexprvalue>true</rtexprvalue>
+ </attribute>
+ <attribute>
+ <name>locale</name>
+ <required>false</required>
+ <rtexprvalue>true</rtexprvalue>
+ </attribute>
+ <attribute>
+ <name>timeStyle</name>
+ <required>false</required>
+ <rtexprvalue>false</rtexprvalue>
+ </attribute>
+ <attribute>
+ <name>dateStyle</name>
+ <required>false</required>
+ <rtexprvalue>false</rtexprvalue>
+ </attribute>
+ <attribute>
+ <name>pattern</name>
+ <required>false</required>
+ <rtexprvalue>false</rtexprvalue>
+ </attribute>
+ <attribute>
+ <name>type</name>
+ <required>false</required>
+ <rtexprvalue>false</rtexprvalue>
+ </attribute>
+ </tag>
+ <tag>
<name>menu</name>
<tag-class>com.redhat.rhn.frontend.taglibs.NavMenuTag</tag-class>
<body-content>JSP</body-content>
diff --git a/web/html/javascript/spacewalk-essentials.js
b/web/html/javascript/spacewalk-essentials.js
index 220e7b4..ea7d0d9 100644
--- a/web/html/javascript/spacewalk-essentials.js
+++ b/web/html/javascript/spacewalk-essentials.js
@@ -115,3 +115,40 @@ function formFocus(form, name) {
}
}
+// Humanizes all the time elements with the human class
+$(document).on("ready", function() {
+ $("time.human-from, time.human-calendar").each(function (index) {
+ var datetime = $(this).attr('datetime');
+ if (datetime == undefined) {
+ // if the attribute is not set, the content
+ // should be a valid date
+ datetime = $(this).html();
+ }
+ var parsed = moment(datetime);
+ if (parsed.isValid()) {
+ var originalContent = $(this).html();
+ if ($(this).hasClass("human-from")) {
+ var ref = $(this).attr("data-reference-date");
+ if (ref) {
+ var refParsed = moment(ref);
+ if (refParsed.isValid()) {
+ $(this).html(parsed.from(refParsed));
+ }
+ }
+ else {
+ $(this).html(parsed.fromNow());
+ }
+ }
+ if ($(this).hasClass("human-calendar")) {
+ $(this).html(parsed.calendar());
+ }
+ // if the original did not had a datetime attribute, add it
+ var datetimeAttr = $(this).attr('datetime');
+ if (datetimeAttr == undefined) {
+ $(this).attr('datetime', datetime);
+ }
+ // add a tooltip
+ $(this).wrap('<a href="#" data-toggle="tooltip"
title="' + originalContent + '"></a>');
+ }
+ });
+});
\ No newline at end of file