Skip to content
Browse files

No commit message

  • Loading branch information...
1 parent c1ea5dc commit 627dd68f06b46956e3c23c146b1ad8f9038c2509 Jason Johnston committed Feb 6, 2010
View
30 cforms-webmail/login.definition.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<fd:form xmlns:fd="http://apache.org/cocoon/forms/1.0#definition">
+ <fd:widgets>
+
+ <fd:messages id="messages" />
+
+ <fd:field id="host">
+ <fd:datatype base="string" />
+ <fd:label>Host</fd:label>
+ <fd:initial-value>localhost</fd:initial-value>
+ </fd:field>
+
+ <fd:field id="port">
+ <fd:datatype base="integer" />
+ <fd:label>Port</fd:label>
+ <!--<fd:initial-value>443</fd:initial-value>-->
+ </fd:field>
+
+ <fd:field id="username">
+ <fd:datatype base="string" />
+ <fd:label>User Name</fd:label>
+ </fd:field>
+
+ <fd:field id="password">
+ <fd:datatype base="string" />
+ <fd:label>Password</fd:label>
+ </fd:field>
+
+ </fd:widgets>
+</fd:form>
View
39 cforms-webmail/login.template.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<html xmlns:ft="http://apache.org/cocoon/forms/1.0#template"
+ xmlns:fi="http://apache.org/cocoon/forms/1.0#instance"
+ xmlns:jx="http://apache.org/cocoon/templates/jx/1.0">
+<head>
+ <title>Webmail - Login</title>
+</head>
+<body>
+ <jx:import uri="resource://org/apache/cocoon/forms/generation/jx-macros.xml"/>
+
+ <ft:form-template action="" method="post" ajax="true">
+ <ft:continuation-id />
+
+ <ft:widget id="messages" />
+
+ <p>
+ <label for="host"><ft:widget-label id="host" /></label>
+ <ft:widget id="host" />
+ </p>
+ <p>
+ <label for="port"><ft:widget-label id="port" /></label>
+ <ft:widget id="port" />
+ </p>
+ <p>
+ <label for="username"><ft:widget-label id="username" /></label>
+ <ft:widget id="username" />
+ </p>
+ <p>
+ <label for="password"><ft:widget-label id="password" /></label>
+ <ft:widget id="password">
+ <fi:styling type="password" />
+ </ft:widget>
+ </p>
+ <p><input type="submit" /></p>
+
+ </ft:form-template>
+
+</body>
+</html>
View
BIN cforms-webmail/resources/arrow-down.gif
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN cforms-webmail/resources/arrow-up.gif
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN cforms-webmail/resources/folder.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
48 cforms-webmail/resources/webmail.css
@@ -0,0 +1,48 @@
+html, body {font-family:sans-serif; font-size:11px; margin:0; padding:0;}
+
+a {color:blue;} /* Make all links always blue */
+
+#folderList {position:absolute; top:0; left:0; bottom:0; width:200px; overflow:auto;}
+#folderList ul {margin:0; padding:0;}
+#folderList li {
+ display:block; list-style-type:none;
+ margin:0; margin-top:.2em; padding:0; padding-left:18px;
+ background:url(folder.png) no-repeat;
+}
+
+#messageList {position:absolute; top:0; left:200px; right:0; height:210px; border:1px solid;}
+#messageListCounts {margin:0 0 12px; padding:2px 15px; background:#CCC;}
+#messageListActions {position:absolute; top:0; right:0; margin:0; padding:0 .5em;}
+#messageList table {border-spacing:0; border-collapse:collapse; width:100%; table-layout:fixed;}
+ #selectedCol {width:30px;}
+ #subjectCol {width:auto;}
+ #senderCol {width:20ex;}
+ #dateCol {width:21ex;}
+ #sizeCol {width:10ex;}
+#messageList thead th {font-weight:bold; text-align:left; padding:0 .5em; background:#EEE; font-weight:normal; border:1px solid;}
+#messageList tbody td {padding:0 .5em; border-top:1px solid #EEE; white-space:nowrap;}
+#messageList tbody td span, #messageList tbody td a {display:block; overflow:hidden;}
+#messageList table input {margin:0;} /* checkboxes */
+
+.hasUnread {font-weight:bold;}
+
+input#messages\.previous, input#messages\.next {
+ position:absolute; left:0; width:100%; height:12px; margin:0; padding:0; cursor:pointer; cursor:hand;
+ background:#EEE no-repeat 50% 2px; color:#EEE; font-size:0; /*hide text*/
+}
+input#messages\.previous {top:18px; background-image:url(arrow-up.gif);}
+input#messages\.next {bottom:0; background-image:url(arrow-down.gif);}
+
+#messageDetails {position:absolute; top:210px; left:200px; bottom:0; right:0; border:1px solid; overflow:auto;}
+#messageDetails table {background:#CCC; width:100%;}
+#messageDetails th {font-weight:bold; text-align:right; vertical-align:top; padding:2px 4px;}
+#messageDetails td {padding:2px 4px; width:100%;}
+#messageButtons {margin:0; padding:.2em 1em; text-align:right; background:#CCC;}
+#message\.content {display:block; padding:1em; font-family:monospace;}
+
+.isUnread {font-weight:bold;}
+.isSelected {background:#EEE;}
+
+.throbber {position:absolute; left:1em; bottom:1em; padding:1em 2em; border:1px solid; width:15ex;}
+
+.alert {position:absolute; top:30%; left:50%; z-index:1000; width:40ex; margin-left:-20ex; border:1px solid; background:#FFF; padding:.5em;}
View
100 cforms-webmail/resources/webmail.js
@@ -0,0 +1,100 @@
+
+function Throbber() {
+ this._running = false;
+};
+Throbber.prototype = {
+ text : "Please Wait",
+ start : function() {
+ if(!this._running) {
+ this.element = document.createElement("div");
+ this.element.className = "throbber";
+ this.element.appendChild(document.createTextNode(this.text));
+ document.body.appendChild(this.element);
+ this._running = true;
+ this.animate();
+ new OpacityFader(this.element).animate();
+ }
+ },
+ _dots : 0,
+ animate : function() {
+ if(this._running) {
+ this._dots++;
+ if(this._dots > 3) {
+ this._dots = 0;
+ this.element.firstChild.nodeValue = this.text;
+ } else {
+ this.element.firstChild.nodeValue += ".";
+ }
+ var thisRef = this;
+ setTimeout(function(){thisRef.animate();}, 200);
+ }
+ },
+ stop : function() {
+ if(this._running) {
+ document.body.removeChild(this.element);
+ this._running = false;
+ }
+ }
+};
+Throbber.getInstance = function() { //singleton
+ if(Throbber._instance == null) {
+ Throbber._instance = new Throbber();
+ }
+ return Throbber._instance;
+};
+
+
+
+function OpacityFader(elt) {
+ this.element = elt;
+ this.step = 10;
+ this.fps = 10;
+ this.opacity = 0;
+};
+OpacityFader.prototype = {
+ animate : function() {
+ this.element.style.opacity = this.element.style.MozOpacity = this.opacity / 100;
+ this.element.style.filter = "alpha(opacity=" + this.opacity + ")"; //MSIE
+ if(this.opacity < 100) {
+ var thisRef = this;
+ setTimeout(function(){thisRef.animate();}, 1000 / this.fps);
+ }
+ this.opacity += this.step;
+ }
+};
+
+// Override the form submit function to add functionality:
+CForms._origSubmitForm = CForms.submitForm;
+forms_submitForm = CForms.submitForm = function(element, name) {
+ // Start the activity indicator:
+ Throbber.getInstance().start();
+ CForms._origSubmitForm(element, name);
+};
+
+// Override the browser-update processing function to add functionality:
+BrowserUpdate._origProcessResponse = BrowserUpdate.processResponse;
+BrowserUpdate.processResponse = function(doc, request) {
+ // Kill the activity indicator:
+ Throbber.getInstance().stop();
+ BrowserUpdate._origProcessResponse(doc, request);
+};
+
+
+// Set automatic refresh interval:
+setInterval("forms_submitForm(document.getElementById('refreshFolderList'), 'refreshFolderList');", 300000);
+
+
+function toggleAllCheckboxes(inElt) {
+ var inputs = inElt.getElementsByTagName("input");
+ var hasUnchecked = false;
+ for(var i=0; i<inputs.length; i++) {
+ if(inputs[i].type == "checkbox") {
+ if(!inputs[i].checked) hasUnchecked = true;
+ }
+ }
+ for(var i=0; i<inputs.length; i++) {
+ if(inputs[i].type == "checkbox") {
+ inputs[i].checked = hasUnchecked;
+ }
+ }
+}
View
105 cforms-webmail/sitemap.xmap
@@ -0,0 +1,105 @@
+<?xml version="1.0"?>
+
+<map:sitemap xmlns:map="http://apache.org/cocoon/sitemap/1.0">
+
+ <map:components>
+ <map:generators default="file">
+ <map:generator name="file" src="org.apache.cocoon.generation.FileGenerator"/>
+ </map:generators>
+
+ <map:transformers default="xsltc">
+ <map:transformer logger="sitemap.transformer.xslt" name="xslt" pool-max="32" src="org.apache.cocoon.transformation.TraxTransformer">
+ <use-request-parameters>false</use-request-parameters>
+ <use-session-parameters>false</use-session-parameters>
+ <use-cookie-parameters>false</use-cookie-parameters>
+ <xslt-processor-role>xslt</xslt-processor-role>
+ <check-includes>true</check-includes>
+ </map:transformer>
+ <map:transformer logger="sitemap.transformer.xsltc" name="xsltc" pool-max="32" src="org.apache.cocoon.transformation.TraxTransformer">
+ <use-request-parameters>false</use-request-parameters>
+ <use-session-parameters>false</use-session-parameters>
+ <use-cookie-parameters>false</use-cookie-parameters>
+ <xslt-processor-role>xsltc</xslt-processor-role>
+ <check-includes>true</check-includes>
+ </map:transformer>
+ <map:transformer name="forms" src="org.apache.cocoon.forms.transformation.FormsTemplateTransformer"/>
+ <map:transformer name="browser-update" src="org.apache.cocoon.ajax.BrowserUpdateTransformer"/>
+ <map:transformer name="i18n" src="org.apache.cocoon.transformation.I18nTransformer">
+ <catalogues default="forms">
+ <catalogue id="forms" name="FormsMessages" location="messages"/>
+ </catalogues>
+ <cache-at-startup>true</cache-at-startup>
+ </map:transformer>
+ </map:transformers>
+
+ <map:serializers default="html">
+ <map:serializer logger="sitemap.serializer.xml" mime-type="text/xml" name="xml" src="org.apache.cocoon.serialization.XMLSerializer"/>
+ <map:serializer logger="sitemap.serializer.html" mime-type="text/html" name="html" pool-max="32" src="org.apache.cocoon.serialization.HTMLSerializer">
+ <doctype-public>-//W3C//DTD HTML 4.01 Transitional//EN</doctype-public>
+ <doctype-system>http://www.w3.org/TR/html4/loose.dtd</doctype-system>
+ </map:serializer>
+ </map:serializers>
+
+ <map:matchers default="wildcard">
+ <map:matcher logger="sitemap.matcher.wildcard" name="wildcard" src="org.apache.cocoon.matching.WildcardURIMatcher"/>
+ <map:matcher logger="sitemap.matcher.request-parameter" name="request-parameter" src="org.apache.cocoon.matching.RequestParameterMatcher"/>
+ </map:matchers>
+
+ <map:selectors default="exception">
+ <map:selector logger="sitemap.selector.exception" name="exception" src="org.apache.cocoon.selection.ExceptionSelector">
+ <exception class="org.apache.cocoon.ResourceNotFoundException" name="not-found"/>
+ <exception class="org.apache.cocoon.components.flow.InvalidContinuationException" name="invalid-continuation"/>
+ <!-- The statement below tells the selector to unroll as much exceptions as possible -->
+ <exception class="java.lang.Throwable" unroll="true"/>
+ </map:selector>
+ </map:selectors>
+
+ </map:components>
+
+ <map:flow language="javascript">
+ <map:script src="webmail.flow.js"/>
+ </map:flow>
+
+ <map:pipelines>
+ <map:pipeline>
+
+ <map:match pattern="">
+ <map:match type="request-parameter" pattern="continuation-id">
+ <map:call continuation="{1}" />
+ </map:match>
+ <map:call function="webmail"/>
+ </map:match>
+
+ <map:match pattern="*.display">
+ <map:generate type="jx" src="{1}.template.xml"/>
+ <map:transform type="browser-update"/>
+ <map:transform src="webmail-styling.xsl" type="xslt">
+ <map:parameter name="resources-uri" value="resources/forms"/>
+ </map:transform>
+ <map:select type="request-parameter">
+ <map:parameter name="parameter-name" value="cocoon-ajax"/>
+ <map:when test="true">
+ <map:serialize type="xml"/>
+ </map:when>
+ <map:otherwise>
+ <map:serialize type="html"/>
+ </map:otherwise>
+ </map:select>
+ </map:match>
+
+ <map:match pattern="resources/forms/**">
+ <map:read src="resource://org/apache/cocoon/forms/resources/{1}" />
+ </map:match>
+
+ <map:match pattern="resources/**">
+ <map:read src="resources/{1}" />
+ </map:match>
+
+ <!-- Attachments: -->
+ <map:match pattern="attachment">
+ <map:read src="module:flow-attr:content" />
+ </map:match>
+
+ </map:pipeline>
+ </map:pipelines>
+</map:sitemap>
View
174 cforms-webmail/webmail-styling.xsl
@@ -0,0 +1,174 @@
+<?xml version="1.0"?>
+
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:ft="http://apache.org/cocoon/forms/1.0#template"
+ xmlns:fi="http://apache.org/cocoon/forms/1.0#instance">
+
+
+ <xsl:include href="resource://org/apache/cocoon/forms/resources/forms-page-styling.xsl"/>
+ <xsl:include href="resource://org/apache/cocoon/forms/resources/forms-field-styling.xsl"/>
+
+ <!-- Location of the resources directory, where JS libs and icons are stored -->
+ <xsl:param name="resources-uri">resources</xsl:param>
+
+ <xsl:template match="head">
+ <head>
+ <xsl:apply-templates select="." mode="forms-page"/>
+ <xsl:apply-templates select="." mode="forms-field"/>
+ <xsl:apply-templates/>
+ </head>
+ </xsl:template>
+
+ <xsl:template match="body">
+ <body>
+ <!--+ !!! If template with mode 'forms-page' adds text or elements
+ | template with mode 'forms-field' can no longer add attributes!!!
+ +-->
+ <xsl:apply-templates select="." mode="forms-page"/>
+ <xsl:apply-templates select="." mode="forms-field"/>
+ <xsl:apply-templates/>
+ </body>
+ </xsl:template>
+
+ <!-- link-to-action styling: wrap value in a link that submits as if it were the specified action widget -->
+ <xsl:template match="fi:field[fi:styling/@link-to-action]" priority="101">
+ <!-- Find the full id of the target action widget: -->
+ <xsl:variable name="actionFullId">
+ <xsl:call-template name="replaceMostSpecific">
+ <xsl:with-param name="into" select="@id" />
+ <xsl:with-param name="with" select="fi:styling/@link-to-action" />
+ </xsl:call-template>
+ </xsl:variable>
+ <!-- Create the link: -->
+ <a href="#" onclick="forms_submitForm(this, '{$actionFullId}'); return false;" id="{@id}">
+ <xsl:apply-templates select="." mode="styling"/>
+ <xsl:value-of select="fi:value" />
+ </a>
+ </xsl:template>
+
+ <!-- output fields: still apply styling -->
+ <xsl:template match="fi:field[@state = 'output']" priority="100">
+ <span id="{@id}">
+ <xsl:apply-templates select="." mode="styling" />
+ <xsl:value-of select="fi:value" />
+ </span>
+ </xsl:template>
+
+ <!-- widgets with invisible styling; useful for when we need a widget's XML
+ available for other stylings but don't want any client representation. -->
+ <xsl:template match="fi:*[fi:styling/@type = 'invisible']" priority="100">
+ <span id="{@id}"></span>
+ </xsl:template>
+
+ <!-- add class="marked" attribute if the specified booleanfield widget's value is true -->
+ <xsl:template match="fi:styling/@booleanfield-as-class" mode="styling">
+ <xsl:variable name="classValue">
+ <xsl:call-template name="booleanfieldToClass">
+ <xsl:with-param name="idString" select="." />
+ <xsl:with-param name="replaceInto" select="../../@id" />
+ </xsl:call-template>
+ </xsl:variable>
+ <xsl:if test="$classValue">
+ <xsl:attribute name="class"><xsl:value-of select="$classValue" /></xsl:attribute>
+ </xsl:if>
+ </xsl:template>
+
+ <xsl:key name="getBooleanfieldById" match="fi:booleanfield" use="@id" />
+
+ <xsl:template name="booleanfieldToClass">
+ <xsl:param name="idString" />
+ <xsl:param name="replaceInto" />
+
+ <xsl:variable name="booleanfieldId">
+ <xsl:choose>
+ <xsl:when test="contains($idString, ',')">
+ <xsl:value-of select="substring-before($idString, ',')" />
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="." />
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:variable>
+
+ <xsl:variable name="booleanfieldFullId">
+ <xsl:call-template name="replaceMostSpecific">
+ <xsl:with-param name="into" select="$replaceInto" />
+ <xsl:with-param name="with" select="$booleanfieldId" />
+ </xsl:call-template>
+ </xsl:variable>
+
+ <xsl:variable name="booleanfield" select="key('getBooleanfieldById', $booleanfieldFullId)" />
+
+ <!-- If the booleanfield is true, write out its id -->
+ <xsl:if test="$booleanfield/fi:value = 'true'">
+ <xsl:value-of select="concat($booleanfieldId, ' ')" />
+ </xsl:if>
+
+ <!-- if there are more in the list apply recursively: -->
+ <xsl:if test="contains($idString, ',')">
+ <xsl:call-template name="booleanfieldToClass">
+ <xsl:with-param name="idString" select="substring-after($idString, ',')" />
+ </xsl:call-template>
+ </xsl:if>
+ </xsl:template>
+
+ <!-- Utility template to get a full id using a local id and a sibling full id -->
+ <xsl:template name="replaceMostSpecific">
+ <xsl:param name="into" />
+ <xsl:param name="with" />
+ <xsl:choose>
+ <xsl:when test="contains($into, '.')">
+ <xsl:value-of select="concat(substring-before($into, '.'), '.')" />
+ <xsl:call-template name="replaceMostSpecific">
+ <xsl:with-param name="into" select="substring-after($into, '.')" />
+ <xsl:with-param name="with" select="$with" />
+ </xsl:call-template>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="$with" />
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:template>
+
+ <!-- field with styling type="email-content": replace CR/LF with <br /> -->
+ <xsl:template match="fi:field[fi:styling/@type = 'email-content']" priority="101">
+ <span id="{@id}">
+ <xsl:call-template name="splitLines">
+ <xsl:with-param name="text" select="fi:value/text()" />
+ </xsl:call-template>
+ </span>
+ </xsl:template>
+ <xsl:template name="splitLines">
+ <xsl:param name="text" />
+ <xsl:choose>
+ <xsl:when test="contains($text, '&#13;')">
+ <xsl:value-of select="substring-before($text, '&#13;')" />
+ <br />
+ <xsl:call-template name="splitLines">
+ <xsl:with-param name="text" select="substring-after($text, '&#13;&#10;')" />
+ </xsl:call-template>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="$text" />
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:template>
+
+ <!-- fi:messages: display as box with a button that clears the contents. -->
+ <xsl:template match="fi:messages">
+ <div id="{@id}">
+ <xsl:if test="fi:message">
+ <div class="alert">
+ <xsl:for-each select="fi:message">
+ <p><xsl:apply-templates/></p>
+ </xsl:for-each>
+ <input type="button" value="OK" onclick="
+ var cont = this.parentNode.parentNode;
+ while(cont.firstChild) cont.removeChild(cont.firstChild);
+ " />
+ </div>
+ </xsl:if>
+ </div>
+ </xsl:template>
+
+</xsl:stylesheet>
View
198 cforms-webmail/webmail.definition.xml
@@ -0,0 +1,198 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<fd:form xmlns:fd="http://apache.org/cocoon/forms/1.0#definition">
+ <fd:widgets>
+
+ <!--========== Messages: ===========-->
+ <fd:messages id="notifications" />
+
+ <!--========== Folder Tree: ===========-->
+ <fd:class id="folder-repeater">
+ <fd:widgets>
+ <fd:repeater id="folders">
+ <fd:widgets>
+ <!--
+ <fd:action id="collapser">
+ <fd:label>+</fd:label>
+ <fd:on-action>
+ <javascript>
+ var ws = Packages.org.apache.cocoon.forms.formmodel.WidgetState;
+ var repeater = event.source.lookupWidget("../folders");
+ repeater.state = (repeater.state === ws.ACTIVE) ? ws.INVISIBLE : ws.ACTIVE;
+ </javascript>
+ </fd:on-action>
+ </fd:action>
+ -->
+ <fd:action id="select" command="selectFolder">
+ <fd:label>Select</fd:label>
+ </fd:action>
+ <fd:field id="name" state="output">
+ <fd:datatype base="string" />
+ </fd:field>
+ <fd:field id="fullName" state="invisible">
+ <fd:datatype base="string" />
+ </fd:field>
+ <fd:booleanfield id="hasUnread" state="output">
+ <fd:on-value-changed>
+ <javascript>event.source.getForm().addWidgetUpdate(event.source.lookupWidget("../name"));</javascript>
+ </fd:on-value-changed>
+ </fd:booleanfield>
+ <fd:field id="unread" state="output">
+ <fd:datatype base="integer" />
+ </fd:field>
+ <fd:field id="total" state="output">
+ <fd:datatype base="integer" />
+ </fd:field>
+ <fd:new id="folder-repeater" />
+ </fd:widgets>
+ </fd:repeater>
+ </fd:widgets>
+ </fd:class>
+ <fd:new id="folder-repeater" />
+ <fd:action id="refreshFolderList" command="refreshFolderList">
+ <fd:label>Refresh</fd:label>
+ </fd:action>
+
+ <!--=========== Message List: ============-->
+ <fd:group id="messages">
+ <fd:widgets>
+ <fd:field id="folderName" state="output">
+ <fd:datatype base="string" />
+ </fd:field>
+ <fd:field id="begin" state="output">
+ <fd:datatype base="double" />
+ </fd:field>
+ <fd:field id="end" state="output">
+ <fd:datatype base="double" />
+ </fd:field>
+ <fd:field id="total" state="output">
+ <fd:datatype base="integer" />
+ </fd:field>
+ <fd:action id="delete" command="deleteMessages">
+ <fd:label>Delete</fd:label>
+ </fd:action>
+ <fd:action id="move" command="moveMessages">
+ <fd:label>Move To:</fd:label>
+ </fd:action>
+ <fd:field id="folderList">
+ <fd:datatype base="string" />
+ <!--<fd:selection-list type="flow-jxpath" list-path="folderSelectionList" value-path="value" label-path="label" />-->
+ </fd:field>
+ <fd:repeater id="list">
+ <fd:widgets>
+ <fd:action id="select" command="selectMessage">
+ <fd:label>Select</fd:label>
+ </fd:action>
+ <fd:booleanfield id="isUnread" state="output">
+ <fd:on-value-changed>
+ <javascript>
+ var widgets = event.source.getParent().getChildren();
+ while(widgets.hasNext()) {
+ event.source.getForm().addWidgetUpdate(widgets.next());
+ }
+ </javascript>
+ </fd:on-value-changed>
+ </fd:booleanfield>
+ <fd:booleanfield id="isSelected" />
+ <fd:field id="subject" state="output">
+ <fd:datatype base="string" />
+ <fd:on-value-changed>
+ <javascript>event.source.getForm().addWidgetUpdate(event.source.lookupWidget("../isUnread"));</javascript>
+ </fd:on-value-changed>
+ </fd:field>
+ <fd:field id="sender" state="output">
+ <fd:datatype base="string" />
+ <fd:on-value-changed>
+ <javascript>event.source.getForm().addWidgetUpdate(event.source.lookupWidget("../isUnread"));</javascript>
+ </fd:on-value-changed>
+ </fd:field>
+ <fd:field id="date" state="output">
+ <fd:datatype base="date">
+ <fd:convertor type="formatting">
+ <fd:patterns>
+ <fd:pattern>yyyy/MM/dd hh:mm</fd:pattern>
+ </fd:patterns>
+ </fd:convertor>
+ </fd:datatype>
+ <fd:on-value-changed>
+ <javascript>event.source.getForm().addWidgetUpdate(event.source.lookupWidget("../isUnread"));</javascript>
+ </fd:on-value-changed>
+ </fd:field>
+ <fd:field id="size" state="output">
+ <fd:datatype base="integer" />
+ <fd:on-value-changed>
+ <javascript>event.source.getForm().addWidgetUpdate(event.source.lookupWidget("../isUnread"));</javascript>
+ </fd:on-value-changed>
+ </fd:field>
+ </fd:widgets>
+ </fd:repeater>
+ <fd:action id="previous" command="messageListPrevious">
+ <fd:label>Previous</fd:label>
+ <fd:hint>Previous 10 Messages</fd:hint>
+ </fd:action>
+ <fd:action id="next" command="messageListNext">
+ <fd:label>Next</fd:label>
+ <fd:hint>Next 10 Messages</fd:hint>
+ </fd:action>
+ </fd:widgets>
+ </fd:group>
+
+ <!--=========== Message Details: ============-->
+ <fd:group id="message">
+ <fd:widgets>
+ <fd:action id="delete" command="deleteMessage" state="disabled">
+ <fd:label>Delete</fd:label>
+ </fd:action>
+
+ <fd:field id="subject" state="output">
+ <fd:datatype base="string" />
+ <fd:label>Subject</fd:label>
+ </fd:field>
+ <fd:field id="sender" state="output">
+ <fd:datatype base="string" />
+ <fd:label>From</fd:label>
+ </fd:field>
+
+ <fd:field id="to" state="output">
+ <fd:datatype base="string" />
+ <fd:label>To</fd:label>
+ </fd:field>
+ <fd:field id="cc" state="output">
+ <fd:datatype base="string" />
+ <fd:label>CC</fd:label>
+ </fd:field>
+
+ <fd:field id="date" state="output">
+ <fd:datatype base="date">
+ <fd:convertor type="formatting">
+ <fd:patterns>
+ <fd:pattern>yyyy/MM/dd hh:mm</fd:pattern>
+ </fd:patterns>
+ </fd:convertor>
+ </fd:datatype>
+ <fd:label>Date</fd:label>
+ </fd:field>
+
+ <fd:field id="content" state="output">
+ <fd:datatype base="string" />
+ </fd:field>
+
+ <!-- Attachment repeater; hidden by default, activated by the binding if contains data (FIXME) -->
+ <fd:repeater id="attachments">
+ <fd:label>Attachments</fd:label>
+ <fd:widgets>
+ <fd:field id="contentType" state="output">
+ <fd:datatype base="string" />
+ </fd:field>
+ <fd:field id="fileName" state="output">
+ <fd:datatype base="string" />
+ </fd:field>
+ <fd:submit id="display" command="displayAttachment" validate="false">
+ <fd:label>Display</fd:label>
+ </fd:submit>
+ </fd:widgets>
+ </fd:repeater>
+ </fd:widgets>
+ </fd:group>
+
+ </fd:widgets>
+</fd:form>
View
566 cforms-webmail/webmail.flow.js
@@ -0,0 +1,566 @@
+cocoon.load("resource://org/apache/cocoon/forms/flow/javascript/Form.js");
+
+// Create cocoon.exit() method (added in SVN, making sure it's available):
+cocoon.exit = new Continuation();
+
+// Some patchwork to add support for multiple bindings per form:
+Form.prototype._orig_createBinding = Form.prototype.createBinding;
+Form.prototype.createBinding = function(bindingURI) {
+ this._orig_createBinding(bindingURI);
+ return this.binding;
+};
+Form.prototype._orig_load = Form.prototype.load;
+Form.prototype.load = function(obj, binding) {
+ if(binding != null) this.binding = binding;
+ this._orig_load(obj);
+};
+Form.prototype._orig_save = Form.prototype.save;
+Form.prototype.save = function(obj, binding) {
+ if(binding != null) this.binding = binding;
+ this._orig_save(obj);
+};
+
+
+
+// Session globals:
+var store;
+
+function webmail() {
+ store = null;
+
+ // Create the Form object and the bindings:
+ var form = new Form("webmail.definition.xml");
+ var folderListBinding = form.createBinding("webmail.folderlist.binding.xml");
+ var messageListBinding = form.createBinding("webmail.messagelist.binding.xml");
+ var messageDetailBinding = form.createBinding("webmail.messagedetail.binding.xml");
+
+ // Method to display exceptions as a message:
+ form.handleException = function(e) {
+ form.lookupWidget("notifications").addMessage(e.message);
+ };
+
+ function StoreWrapper(store, session) {
+ this.store = store;
+ this.session = session;
+ };
+ StoreWrapper.prototype = {
+ host : null,
+ port : null,
+ username : null,
+ password : null,
+ connect : function() {
+ if(this.store.isConnected()) return;
+ var success = false;
+ var loginForm = null;
+ while(!success) {
+ if(this.host == null || this.port == null || this.username == null || this.password == null) {
+ if(loginForm == null) {
+ loginForm = new Form("login.definition.xml");
+ }
+ loginForm.showForm("login.display");
+ this.host = loginForm.lookupWidget("host").value;
+ this.port = loginForm.lookupWidget("port").value;
+ this.username = loginForm.lookupWidget("username").value;
+ this.password = loginForm.lookupWidget("password").value;
+ }
+ try {
+ this.store.connect(this.host, this.username, this.password);
+ success = true;
+ }
+ catch(e) {
+ loginForm.lookupWidget("messages").addMessage(e.message);
+ this.username = this.password = null;
+ }
+ }
+ },
+
+ folders : {}, //map of cached folder names to FolderWrapper objects
+ getFolder : function(name) {
+ var cached = this.folders[name];
+ if(cached != null) return cached;
+ this.connect();
+ return this.folders[name] = new FolderWrapper(this.store.getFolder(name), this);
+ },
+
+ defaultFolder : null,
+ getDefaultFolder : function() {
+ if(this.defaultFolder == null) {
+ this.connect();
+ this.defaultFolder = new FolderWrapper(this.store.getDefaultFolder(), this);
+ }
+ return this.defaultFolder;
+ },
+
+ trashFolder : null,
+ getTrashFolder : function() {
+ if(this.trashFolder == null) {
+ this.connect();
+ this.trashFolder = this.getFolder("INBOX.Trash");
+ if(this.trashFolder == null) { //try folder in root
+ this.trashFolder = this.getFolder("Trash");
+ }
+ }
+ return this.trashFolder;
+ },
+
+ selectedFolder : null,
+ selectFolder : function(name) {
+ this.selectedFolder = this.getFolder(name);
+ this.selectedFolder.chunkStart = 1; //reset paging
+ this.selectedFolder.displayMessages();
+
+ // refresh folder list
+ this.displayFolders();
+ },
+
+ displayFolders : function() {
+ this.connect();
+ var topFolder = this.getDefaultFolder();
+ topFolder.refreshSubFolders();
+ form.load(topFolder, folderListBinding);
+ },
+
+ folderSelectionList : [],
+ refreshFolderSelectionList : function() {
+ var thisRef = this;
+ function addSubFolders(parentFolder, prefix) {
+ for(var i=0, folder; (folder = parentFolder.subFolders[i]); i++) {
+ thisRef.folderSelectionList.push({
+ label : prefix + folder.folder.getName(),
+ value : folder.folder.getFullName()
+ });
+ addSubFolders(folder, prefix + "\u00A0\u00A0"); // nbsp
+ }
+ }
+ addSubFolders(this.getDefaultFolder(), "");
+ }
+ };
+
+ function FolderWrapper(folder, store) {
+ this.folder = folder;
+ this.store = store;
+ this.exists = (folder != null);
+ };
+ FolderWrapper.prototype = {
+ chunkStart : 1,
+ chunkEnd : 10,
+ chunkSize : 10,
+
+ messagesByUID : {},
+ getMessageByUID : function(uid) {
+ var cached = this.messagesByUID[uid];
+ if(cached == null) {
+ this.open();
+ cached = this.messagesByUID[uid] = new MessageWrapper(this.folder.getMessageByUID(uid), this);
+ this.close();
+ }
+ return cached;
+ },
+
+ chunkedMessages : [],
+ updateChunkedMessages : function() {
+ this.open();
+ this.chunkedMessages = []; //reset
+
+ var totalMsgs = this.folder.getMessageCount();
+ this.chunkEnd = this.chunkStart + this.chunkSize - 1;
+ if(this.chunkEnd > totalMsgs) this.chunkEnd = Number(totalMsgs);
+
+ // get the chunk from the end of the folder, in reverse order.
+ // (TODO: use IMAP SORT to do this instead.)
+ var msgArray = this.folder.getMessages(totalMsgs+1 - this.chunkEnd, totalMsgs+1 - this.chunkStart); //subtract from total to get chunk from end of folder
+ java.util.Collections.reverse(java.util.Arrays.asList(msgArray)); //reverse the order of items in that chunk so most recent is first
+
+ // fetch message UIDs:
+ var fetchProfile = new Packages.javax.mail.FetchProfile();
+ fetchProfile.add(Packages.javax.mail.UIDFolder.FetchProfileItem.UID);
+ this.folder.fetch(msgArray, fetchProfile);
+
+ // get MessageWrapper objects for each UID:
+ for(var i=0; i<msgArray.length; i++) {
+ if(i in msgArray) {
+ this.messages.push(this.getMessageByUID(this.folder.getUID(msgArray[i])));
+ } else {
+ this.messages.push(MessageWrapper.EMPTY_MESSAGE);
+ }
+ }
+ this.close();
+ },
+
+ messages : [],
+ displayMessages : function() {
+ this.open(); //make sure we are opened
+ this.messages = []; //reset
+
+ var totalMsgs = this.folder.messageCount;
+ this.chunkEnd = this.chunkStart + this.chunkSize - 1;
+ if(this.chunkEnd > totalMsgs) this.chunkEnd = Number(totalMsgs);
+
+ // get the chunk from the end of the folder, in reverse order.
+ // (TODO: use IMAP SORT to do this instead.)
+ var msgArray = this.folder.getMessages(totalMsgs+1 - this.chunkEnd, totalMsgs+1 - this.chunkStart); //subtract from total to get chunk from end of folder
+ java.util.Collections.reverse(java.util.Arrays.asList(msgArray)); //reverse the order of items in that chunk so most recent is first
+ this.folder.fetch(msgArray, FolderWrapper.messageListFetchProfile); //prefetch metadata to be displayed in list
+
+ for(var i=0; i<this.chunkSize; i++) {
+ if(i in msgArray) {
+ var newMsg = new MessageWrapper(msgArray[i], this);
+ this.messages.push(newMsg);
+ } else {
+ this.messages.push(MessageWrapper.EMPTY_MESSAGE);
+ }
+ }
+ form.load(this, messageListBinding);
+ this.close();
+
+ // update prev/next button state:
+ var ws = Packages.org.apache.cocoon.forms.formmodel.WidgetState;
+ var prev = form.lookupWidget("messages/previous");
+ var next = form.lookupWidget("messages/next");
+ var prevTgtState = (this.chunkStart > 1) ? ws.ACTIVE : ws.INVISIBLE;
+ var nextTgtState = (this.chunkStart < totalMsgs - this.chunkSize) ? ws.ACTIVE : ws.INVISIBLE
+ if(prev.state != prevTgtState) prev.state = prevTgtState;
+ if(next.state != nextTgtState) next.state = nextTgtState;
+ },
+ goPrevious : function() {
+ this.chunkStart -= this.chunkSize;
+ if(this.chunkStart < 1) this.chunkStart = 1;
+ this.displayMessages();
+ },
+ goNext : function() {
+ this.chunkStart += this.chunkSize;
+ var last = this.folder.messageCount - 1;
+ if(this.chunkStart > last) this.chunkStart = last;
+ this.displayMessages();
+ },
+
+ selectedMessage : null,
+ selectMessage : function(msgIdx) {
+ this.open();
+ this.selectedMessage = this.messages[msgIdx];
+ this.selectedMessage.displayDetails();
+ this.close();
+
+ // enable the buttons:
+ var ws = Packages.org.apache.cocoon.forms.formmodel.WidgetState;
+ form.lookupWidget("message/delete").setState(ws.ACTIVE);
+ },
+
+ deleteSelectedMessages : function() {
+ this.moveSelectedMessages(this.store.getTrashFolder());
+ },
+
+ moveSelectedMessages : function(toFolder) {
+ this.open();
+
+ var msgArrayList = new Packages.java.util.ArrayList();
+ var repeater = form.lookupWidget("messages/list");
+ for(var i=0; i<repeater.getSize(); i++) {
+ var checkbox = repeater.getRow(i).getChild("isSelected");
+ if(checkbox.getValue().booleanValue()) {
+ msgArrayList.add(this.messages[i].message);
+ checkbox.setValue(false); //uncheck
+ }
+ }
+ var msgArray = msgArrayList.toArray(
+ java.lang.reflect.Array.newInstance(Packages.javax.mail.Message, msgArrayList.size())
+ );
+
+ // copy to target folder:
+ this.folder.copyMessages(msgArray, toFolder.folder);
+
+ // set the DELETED flag
+ var Flags = Packages.javax.mail.Flags;
+ this.folder.setFlags(msgArray, new Flags(Flags.Flag.DELETED), true);
+ this.close();
+
+ // refresh message and folder list:
+ this.displayMessages();
+ this.store.displayFolders();
+ },
+
+ open : function() {
+ store.connect(); // make sure we are connected
+ if(!this.folder.isOpen()) { // open the folder if it's not already open
+ this.folder.open(Packages.javax.mail.Folder.READ_WRITE);
+ this.folder.expunge(); // auto-expunge
+ }
+ },
+ close : function() {
+ if(this.folder.isOpen()) {
+ this.folder.close(true); //close and expunge
+ }
+ },
+
+ sort : function(criterion, reverse) {
+ var command = new JavaAdapter(
+ Packages.com.sun.mail.imap.IMAPFolder.ProtocolCommand,
+ {
+ doCommand : function(protocol) {
+ // Create arguments:
+ var args = new Packages.com.sun.mail.imap.protocol.Arguments();
+ var criteria = new Packages.com.sun.mail.imap.protocol.Arguments();
+ if(reverse) {
+ criteria.writeString("REVERSE");
+ }
+ criteria.writeString(criterion);
+ args.writeArgument(criteria);
+ args.writeString("UTF-8");
+ args.writeString("ALL");
+
+ // Issue command and get response:
+ var responseLines = protocol.command("SORT", args);
+ var response = responseLines[responseLines.length - 1];
+
+ if(response.isOK()) {
+
+ }
+
+ /*
+ // Issue command
+ Argument args = new Argument();
+ Argument list = new Argument();
+ list.writeString("SUBJECT");
+ args.writeArgument(list);
+ args.writeString("UTF-8");
+ args.writeString("ALL");
+ Response[] r = p.command("SORT", args);
+ Response response = r[r.length-1];
+
+ // Grab response
+ if (response.isOK()) { // command succesful
+ for (int i = 0, len = r.length; i < len; i++) {
+ if (!(r[i] instanceof IMAPResponse))
+ continue;
+
+ IMAPResponse ir = (IMAPResponse)r[i];
+ if (ir.keyEquals("SORT")) {
+ String num;
+ while ((num = ir.readAtomString()) != null)
+ System.out.println(num);
+ r[i] = null;
+ }
+ }
+ }
+
+ // dispatch remaining untagged responses
+ p.notifyResponseHandlers(r);
+ p.handleResult(response);
+
+ return null;
+ */
+ }
+ }
+ );
+ },
+
+ subFolders : [],
+ refreshSubFolders : function() {
+ this.subFolders = [];
+ var subArray = this.folder.listSubscribed();
+ for(var i=0; i<subArray.length; i++) {
+ var sub = new FolderWrapper(subArray[i], this.store);
+ this.subFolders.push(sub);
+ sub.refreshSubFolders();
+ }
+ }
+ };
+ FolderWrapper.messageListFetchProfile = new Packages.javax.mail.FetchProfile();
+ FolderWrapper.messageListFetchProfile.add(Packages.javax.mail.FetchProfile.Item.ENVELOPE);
+ FolderWrapper.messageListFetchProfile.add(Packages.javax.mail.FetchProfile.Item.FLAGS);
+ FolderWrapper.messageListFetchProfile.add(Packages.javax.mail.UIDFolder.FetchProfileItem.UID);
+ FolderWrapper.messageListFetchProfile.add(Packages.com.sun.mail.imap.IMAPFolder.FetchProfileItem.SIZE);
+
+
+ function MessageWrapper(message, folder) {
+ this.message = message;
+ this.folder = folder;
+ this.parseEnvelope();
+ };
+ MessageWrapper.prototype = {
+ selected : false,
+
+ content : null,
+ attachments : null,
+ parseContent : function() {
+ if(this.content != null) return; //only fetch once
+ this.content = "";
+ this.attachments = [];
+
+ var thisRef = this;
+ function doParse(part) {
+ if(part.isMimeType("text/plain")) {
+ try {
+ thisRef.content += String(part.getContent()).replace(/ /g, " \u00A0");
+ } catch(e) {
+ thisRef.content += "[COULD NOT PARSE MESSAGE]: " + e.toString();
+ }
+ }
+ else if(part.isMimeType("multipart/*")) {
+ var multipart = part.getContent();
+ for(var i=0; i < multipart.getCount(); i++) {
+ doParse(multipart.getBodyPart(i));
+ }
+ }
+ else {
+ thisRef.attachments.push(part);
+ }
+ };
+ doParse(this.message);
+
+ if(this.content == "") this.content = "[ NO PLAINTEXT PARTS FOUND ]";
+ },
+
+ subject : null,
+ sender : null,
+ recipients : null, //{to : [], cc : []}
+ size : null,
+ parseEnvelope : function() {
+ if(this.subject == null) {
+ this.subject = this.message.subject || "(No Subject)";
+ }
+ if(this.sender == null) {
+ var from = (this.message.from)[0];
+ this.sender = from.personal || from.address;
+ }
+ if(this.recipients == null) {
+ var rt = Packages.javax.mail.Message.RecipientType;
+ var ia = Packages.javax.mail.internet.InternetAddress;
+ this.recipients = {
+ to : ia.toString(this.message.getRecipients(rt.TO)),
+ cc : ia.toString(this.message.getRecipients(rt.CC)),
+ };
+ }
+ if(this.size == null) {
+ this.size = this.message.getSize();
+ if(this.size == -1) this.size = null; //handle empty messages that return -1 for their size
+ }
+ },
+
+ displayDetails : function() {
+ this.folder.open();
+ this.parseContent();
+ form.load(this, messageDetailBinding);
+ this.markAsRead();
+ this.folder.close();
+ },
+
+ markAsRead : function() {
+ this.folder.open();
+ var seen = Packages.javax.mail.Flags.Flag.SEEN;
+ if(!this.message.isSet(seen)) {
+ this.message.setFlag(seen, true);
+ this.folder.displayMessages(); //refresh message list...
+ this.folder.store.displayFolders(); //...and folder list.
+ }
+ this.folder.close();
+ },
+
+ deleteMessage : function() {
+ this.folder.open();
+
+ // Copy to the Trash folder if we can find it:
+ var trash = this.folder.store.getTrashFolder();
+ if(trash.exists) {
+ var msgArray = java.lang.reflect.Array.newInstance(Packages.javax.mail.Message, 1);
+ msgArray[0] = this.message;
+ this.folder.folder.copyMessages(msgArray, trash.folder);
+ }
+
+ // Set the DELETED flag on the message:
+ this.message.setFlag(Packages.javax.mail.Flags.Flag.DELETED, true);
+ this.folder.close(); //commits the change
+ },
+
+ displayAttachment : function(num) {
+ this.folder.open();
+ var part = this.attachments[num];
+ var content = part.getContent();
+ cocoon.sendPage("attachment", {content : content});
+ this.folder.close();
+ cocoon.exit();
+ }
+ };
+ // fake MessageWrapper with empty properties - this is hackish, find a better way.
+ MessageWrapper.EMPTY_MESSAGE = {message:{subject:null, from:[{personal:null, address:null}], receivedDate:null, size:null}, content:null, attachments:[], subject:null, sender:null, recipients:{to:null, cc:null}, size:null};
+
+ // Log in to a mail Store if needed:
+ if(store == null) {
+ var props = new Packages.java.lang.System.getProperties();
+ var session = Packages.javax.mail.Session.getInstance(props, null);
+ store = new StoreWrapper(session.getStore("imap"), session);
+ }
+ store.connect();
+
+
+ // Set handlers for form action events:
+ var formHandler = new JavaAdapter(Packages.org.apache.cocoon.forms.event.AbstractFormHandler, {
+ handleActionEvent : function(actionEvent) {
+ try {
+ // Listen for action events:
+ switch(String(actionEvent.actionCommand)) {
+ case "messageListNext":
+ store.selectedFolder.goNext();
+ break;
+
+ case "messageListPrevious":
+ store.selectedFolder.goPrevious();
+ break;
+
+ case "selectMessage":
+ var msgIdx = actionEvent.source.parent.id; //index of the row
+ store.selectedFolder.selectMessage(msgIdx);
+ break;
+
+ case "selectFolder":
+ var folderName = actionEvent.source.lookupWidget("../fullName").value;
+ store.selectFolder(folderName);
+ break;
+
+ case "refreshFolderList":
+ store.displayFolders();
+ break;
+
+ case "deleteMessages":
+ store.selectedFolder.deleteSelectedMessages();
+ break;
+
+ case "moveMessages":
+ var targetFolder = store.getFolder(form.lookupWidget("messages/folderList").getValue());
+ store.selectedFolder.moveSelectedMessages(targetFolder);
+ break;
+
+ case "deleteMessage":
+ store.selectedFolder.selectedMessage.deleteMessage();
+ store.selectedFolder.displayMessages();
+ store.displayFolders();
+ var ws = Packages.org.apache.cocoon.forms.formmodel.WidgetState;
+ form.lookupWidget("message/delete").setState(ws.DISABLED);
+ break;
+
+ case "displayAttachment":
+ var attachmentNum = actionEvent.source.getParent().getId(); //row index
+ store.selectedFolder.selectedMessage.displayAttachment(attachmentNum);
+ break;
+ }
+ } catch(e) {
+ form.handleException(e);
+ }
+ },
+ handleValueChangedEvent : function(valueChangedEvent) {}
+ });
+ form.form.setFormHandler(formHandler);
+
+ // Perform initial data load:
+ store.displayFolders();
+ store.selectFolder("INBOX");
+
+ // Set the folder selection list:
+ store.refreshFolderSelectionList();
+ form.lookupWidget("messages/folderList").setSelectionList(store.folderSelectionList, "value", "label");
+
+ while(true) { //keep looping in case we accidentally exit
+ form.showForm("webmail.display", store);
+ }
+};
+
View
18 cforms-webmail/webmail.folderlist.binding.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<fb:context path="/" direction="load" xmlns:fb="http://apache.org/cocoon/forms/1.0#binding">
+
+ <fb:class id="folder-repeater">
+ <fb:simple-repeater id="folders" parent-path="." row-path="subFolders" clear-before-load="false">
+ <fb:context path="folder">
+ <fb:value id="name" path="name" />
+ <fb:value id="fullName" path="fullName" />
+ <fb:value id="hasUnread" path="unreadMessageCount &gt; 0" />
+ <fb:value id="unread" path="unreadMessageCount" />
+ <fb:value id="total" path="messageCount" />
+ </fb:context>
+ <fb:new id="folder-repeater" />
+ </fb:simple-repeater>
+ </fb:class>
+ <fb:new id="folder-repeater" />
+
+</fb:context>
View
32 cforms-webmail/webmail.messagedetail.binding.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<fb:context path="/" direction="load" xmlns:fb="http://apache.org/cocoon/forms/1.0#binding">
+
+ <fb:group id="message" path=".">
+ <fb:value id="subject" path="subject" />
+
+ <fb:value id="sender" path="string(message/from[1])" />
+
+ <fb:context path="recipients">
+ <fb:value id="to" path="to" />
+ <fb:value id="cc" path="cc" />
+ </fb:context>
+
+ <fb:value id="date" path="message/receivedDate" />
+
+ <fb:value id="content" path="content" />
+
+ <!-- FIXME changing state of the repeater widget is buggy
+ <fb:javascript id="attachments" path="attachments" direction="load">
+ <fb:load-form>
+ var ws = Packages.org.apache.cocoon.forms.formmodel.WidgetState;
+ widget.setState(jxpathPointer.getNode().length > 0 ? ws.ACTIVE : ws.INVISIBLE);
+ </fb:load-form>
+ </fb:javascript>
+ -->
+ <fb:simple-repeater id="attachments" parent-path="." row-path="attachments">
+ <fb:value id="contentType" path="contentType" />
+ <fb:value id="fileName" path="fileName" />
+ </fb:simple-repeater>
+ </fb:group>
+
+</fb:context>
View
38 cforms-webmail/webmail.messagelist.binding.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<fb:context path="/" direction="load" xmlns:fb="http://apache.org/cocoon/forms/1.0#binding">
+
+ <fb:group id="messages" path=".">
+ <fb:value id="folderName" path="folder/name" />
+ <fb:value id="begin" path="chunkStart" />
+ <fb:value id="end" path="chunkEnd" />
+ <fb:value id="total" path="folder/messageCount" />
+
+ <fb:simple-repeater id="list" parent-path="." row-path="messages" clear-before-load="false">
+ <fb:context path="message">
+ <fb:javascript id="isUnread" path="flags" direction="load">
+ <fb:load-form>
+ var flags = jxpathPointer.getNode();
+ if(flags != null) widget.value = !flags.contains(Packages.javax.mail.Flags.Flag.SEEN);
+ </fb:load-form>
+ </fb:javascript>
+ </fb:context>
+
+ <!-- hide the checkbox for empty rows: -->
+ <fb:javascript id="isSelected" path="." direction="load">
+ <fb:load-form>
+ var ws = Packages.org.apache.cocoon.forms.formmodel.WidgetState;
+ var curState = widget.getState();
+ var tgtState = (jxpathPointer.getNode().subject == null) ? // HACK!!! Depends on knowing that "real" messages will never have a null subject but our fake empty ones will.
+ ws.INVISIBLE : ws.ACTIVE;
+ if(curState != tgtState) widget.setState(tgtState);
+ </fb:load-form>
+ </fb:javascript>
+
+ <fb:value id="subject" path="subject" />
+ <fb:value id="sender" path="sender" />
+ <fb:value id="date" path="message/receivedDate" />
+ <fb:value id="size" path="size" />
+ </fb:simple-repeater>
+ </fb:group>
+
+</fb:context>
View
168 cforms-webmail/webmail.template.xml
@@ -0,0 +1,168 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<html xmlns:ft="http://apache.org/cocoon/forms/1.0#template"
+ xmlns:fi="http://apache.org/cocoon/forms/1.0#instance"
+ xmlns:jx="http://apache.org/cocoon/templates/jx/1.0">
+<head>
+ <title>Webmail</title>
+ <link rel="stylesheet" type="text/css" href="resources/webmail.css" />
+ <script type="text/javascript" src="resources/webmail.js"><!-- --></script>
+</head>
+<body>
+ <jx:import uri="resource://org/apache/cocoon/forms/generation/jx-macros.xml"/>
+
+ <ft:form-template action="" method="post" ajax="true">
+ <ft:continuation-id />
+
+ <!--=========== Messages: ===========-->
+ <ft:widget id="notifications" />
+
+ <!--=========== Folder List: ============-->
+ <div id="folderList">
+ <ft:class id="folder-repeater">
+ <ft:repeater id="folders">
+ <ul>
+ <ft:repeater-rows>
+ <li>
+ <!--<ft:widget id="collapser" />-->
+ <ft:widget id="name">
+ <fi:styling link-to-action="select" booleanfield-as-class="hasUnread" />
+ </ft:widget>
+ <ft:widget id="hasUnread">
+ <fi:styling type="invisible" />
+ </ft:widget>
+
+ (<ft:widget id="unread" />/<ft:widget id="total" />)
+ <ft:new id="folder-repeater" />
+ </li>
+ </ft:repeater-rows>
+ </ul>
+ </ft:repeater>
+ </ft:class>
+ <ft:new id="folder-repeater" />
+ <ft:widget id="refreshFolderList" />
+ </div>
+
+ <!--=========== Message List: ============-->
+ <div id="messageList">
+ <ft:group id="messages">
+ <div id="messages">
+ <p id="messageListCounts"><strong><ft:widget id="folderName" /></strong> - Viewing messages <ft:widget id="begin" /> to <ft:widget id="end" /> of <ft:widget id="total" /></p>
+
+ <p id="messageListActions">
+ <ft:widget id="delete" />
+ <ft:widget id="move" />
+ <ft:widget id="folderList" />
+ </p>
+
+ <ft:widget id="previous" />
+ <ft:repeater id="list">
+ <table>
+ <col id="selectedCol" />
+ <col id="subjectCol" />
+ <col id="senderCol" />
+ <col id="dateCol" />
+ <col id="sizeCol" />
+ <thead>
+ <tr>
+ <th>
+ <input type="checkbox" checked="checked" title="Select All"
+ onclick="toggleAllCheckboxes(document.getElementById('messageList').getElementsByTagName('tbody')[0]); this.checked = true;" />
+ </th>
+ <th>Subject</th>
+ <th>Sender</th>
+ <th>Date</th>
+ <th>Size</th>
+ </tr>
+ </thead>
+ <tbody>
+ <ft:repeater-rows>
+ <tr>
+ <td><ft:widget id="isSelected" /></td>
+ <td>
+ <ft:widget id="isUnread">
+ <fi:styling type="invisible" />
+ </ft:widget>
+ <ft:widget id="subject">
+ <fi:styling link-to-action="select" booleanfield-as-class="isUnread" />
+ </ft:widget>
+ </td>
+ <td>
+ <ft:widget id="sender">
+ <fi:styling booleanfield-as-class="isUnread" />
+ </ft:widget>
+ </td>
+ <td>
+ <ft:widget id="date">
+ <fi:styling booleanfield-as-class="isUnread" />
+ </ft:widget>
+ </td>
+ <td>
+ <ft:widget id="size">
+ <fi:styling booleanfield-as-class="isUnread" />
+ </ft:widget>
+ </td>
+ </tr>
+ </ft:repeater-rows>
+ </tbody>
+ </table>
+ </ft:repeater>
+ <ft:widget id="next" />
+ </div>
+ </ft:group>
+ </div>
+
+ <!--=========== Message Detail: ============-->
+ <div id="messageDetails">
+ <ft:group id="message">
+ <div id="message">
+ <p id="messageButtons">
+ <ft:widget id="delete" />
+ </p>
+
+ <table>
+ <tbody>
+ <tr>
+ <th><ft:widget-label id="subject" /></th>
+ <td><ft:widget id="subject" /></td>
+ </tr>
+ <tr>
+ <th><ft:widget-label id="sender" /></th>
+ <td><ft:widget id="sender" /></td>
+ </tr>
+ <tr>
+ <th><ft:widget-label id="to" /></th>
+ <td><ft:widget id="to" /></td>
+ </tr>
+ <tr>
+ <th><ft:widget-label id="cc" /></th>
+ <td><ft:widget id="cc" /></td>
+ </tr>
+ <tr>
+ <th><ft:widget-label id="date" /></th>
+ <td><ft:widget id="date" /></td>
+ </tr>
+ </tbody>
+ </table>
+
+ <ft:widget id="content">
+ <fi:styling type="email-content" />
+ </ft:widget>
+
+ <ft:repeater id="attachments">
+ <ul>
+ <ft:repeater-rows>
+ <li>
+ <ft:widget id="fileName"><fi:styling link-to-action="display" /></ft:widget>
+ (<ft:widget id="contentType"><fi:styling link-to-action="display" /></ft:widget>)
+ </li>
+ </ft:repeater-rows>
+ </ul>
+ </ft:repeater>
+ </div>
+ </ft:group>
+ </div>
+
+ </ft:form-template>
+
+</body>
+</html>

0 comments on commit 627dd68

Please sign in to comment.
Something went wrong with that request. Please try again.