Author: sylvain Date: Fri May 6 15:01:55 2005 New Revision: 168669 URL: http://svn.apache.org/viewcvs?rev=168669&view=rev Log: Ajax: fix a bug with boolean inputs, add some visual sugar by highlighting updated areas
Modified: cocoon/blocks/core/forms/trunk/java/org/apache/cocoon/forms/flow/javascript/Form.js cocoon/blocks/core/forms/trunk/java/org/apache/cocoon/forms/resources/js/cforms.js cocoon/blocks/core/forms/trunk/samples/forms/carselector_template.xml cocoon/blocks/core/forms/trunk/samples/welcome.xml Modified: cocoon/blocks/core/forms/trunk/java/org/apache/cocoon/forms/flow/javascript/Form.js URL: http://svn.apache.org/viewcvs/cocoon/blocks/core/forms/trunk/java/org/apache/cocoon/forms/flow/javascript/Form.js?rev=168669&r1=168668&r2=168669&view=diff ============================================================================== --- cocoon/blocks/core/forms/trunk/java/org/apache/cocoon/forms/flow/javascript/Form.js (original) +++ cocoon/blocks/core/forms/trunk/java/org/apache/cocoon/forms/flow/javascript/Form.js Fri May 6 15:01:55 2005 @@ -114,11 +114,7 @@ this.locale = java.util.Locale.getDefault(); viewdata["locale"] = this.locale; - // Keep the first continuation that will be created as the result of this function - var result = null; - var finished = false; - this.isValid = false; var comingBack = false; var bookmark = cocoon.createWebContinuation(ttl); @@ -126,10 +122,11 @@ if (comingBack) { // We come back to the bookmark: process the form - if (cocoon.request.getParameter("cocoon-ajax-continue") != null) { - //TODO(SW): when exiting AJAX rountrips, better send a new continuation - //to ensure we really come back from a "100-continue" response (see below) - //and this isn't a forged request + if (finished && cocoon.request.getParameter("cocoon-ajax-continue") != null) { + // A request with this parameter is sent by the client upon receiving the indication + // that Ajax interaction on the form is finished (see below). + // We also check "finished" to ensure we won't exit showForm() because of some + // faulty or hacked request. It's set to false, this will simply redisplay the form. return bookmark; } @@ -155,7 +152,7 @@ // Ask the client to load the page cocoon.response.setHeader("X-Cocoon-Ajax", "continue"); cocoon.response.setHeader("Content-Length", "0"); - cocoon.sendStatus(200); // Continue + cocoon.sendStatus(200); FOM_Cocoon.suicide(); } Modified: cocoon/blocks/core/forms/trunk/java/org/apache/cocoon/forms/resources/js/cforms.js URL: http://svn.apache.org/viewcvs/cocoon/blocks/core/forms/trunk/java/org/apache/cocoon/forms/resources/js/cforms.js?rev=168669&r1=168668&r2=168669&view=diff ============================================================================== --- cocoon/blocks/core/forms/trunk/java/org/apache/cocoon/forms/resources/js/cforms.js (original) +++ cocoon/blocks/core/forms/trunk/java/org/apache/cocoon/forms/resources/js/cforms.js Fri May 6 15:01:55 2005 @@ -94,10 +94,15 @@ // Iterate on all form controls for (var i = 0; i < form.elements.length; i++) { input = form.elements[i]; - if (input.type != "submit" && input.type != "image") { + if (input.type == "submit" || input.type == "image") { // Skip buttons - result += "&" + encodeURIComponent(input.name) + "=" + encodeURIComponent(input.value); + continue; } + if ((input.type == "checkbox" || input.type == "radio") && !input.checked) { + // Skip unchecked checkboxes and radio buttons + continue; + } + result += "&" + encodeURIComponent(input.name) + "=" + encodeURIComponent(input.value); } return result; } @@ -131,10 +136,20 @@ return; } - BrowserUpdate.processResponse(doc); - } + BrowserUpdate.processResponse(doc); + } } else { - alert("request failed : " + request.status); +/* var str = ""; + for(prop in request) { + str += prop + str += " = " + str += request[prop]; + str += '\n'; + } + alert(str); + alert(request.getAllResponseHeaders()); +*/ + alert("request failed - status: " + request.status); } } @@ -146,7 +161,7 @@ ATTRIBUTE_NODE : 2, TEXT_NODE : 3, CDATA_SECTION_NODE : 4, - ENTITY_REFERENCE_NODE : 5, + ENTITY_REFERENCE_NODE : 5, ENTITY_NODE : 6, PROCESSING_INSTRUCTION_NODE : 7, COMMENT_NODE : 8, @@ -269,6 +284,140 @@ oldElement.parentNode.replaceChild(newElement, oldElement); // Ensure the new node has the correct id newElement.setAttribute("id", id); + + if (BrowserUpdate.highlight) { + BrowserUpdate.highlight(newElement); + } } +} + +//------------------------------------------------------------------------------------------------- +// Fader used to highlight page areas that have been updated +//------------------------------------------------------------------------------------------------- + +/** + * Create a fader that will progressively change an element's background color from + * a given color back to its original color. + * + * @param elt the element to fade + * @param color the starting color (default yellow) + * @param duration the fade duration in msecs (default 1000) + * @param fps the animation frames per seconds (default 25) + */ +function Fader(elt, color, duration, fps) { + // Set default values + if (!color) color = "#FFFF80"; // yellow + if (!duration) duration = 1000; // 1 sec + if (!fps) fps = 25; // 25 frames/sec + + this.element = elt; + this.fromColor = Fader.colorToRgb(color); + this.toColor = Fader.colorToRgb(Fader.getBgColor(this.element)); + + this.maxFrames = Math.round(fps * duration / 1000.0); + this.delay = duration / this.maxFrames; +} + +/** + * Creates a default fader for a given element. This function can be used to set BrowserUpdate.highlight + */ +Fader.fade = function(elt) { + new Fader(elt).start(); +} + +Fader.prototype.start = function() { + this.frame = 0; + this._changeColor(); +} + +Fader.prototype._changeColor = function() { + if (this.frame < this.maxFrames) { + // Schedule the next iteration right now to keep a more accurate timing + var fader = this; + setTimeout(function() {fader._changeColor();}, this.delay); + } + var newColor = new Array(3); + for (var channel = 0; channel < 3; channel++) { + newColor[channel] = Math.floor( + this.fromColor[channel] * ((this.maxFrames - this.frame) / this.maxFrames) + + this.toColor[channel] * (this.frame/this.maxFrames) + ); + } + + this.frame++; + var color = Fader.rgbToColor(newColor[0], newColor[1], newColor[2]); + this.element.style.backgroundColor = color; +} + +/** Converts a "#RRGGBB" color as an array of 3 ints */ +Fader.colorToRgb = function(hex) { + return [ + parseInt(hex.substr(1,2),16), + parseInt(hex.substr(3,2),16), + parseInt(hex.substr(5,2),16) ]; +} + +/** Converts rgb values to a "#RRGGBB" color */ +Fader.rgbToColor = function(r, g, b) { + r = r.toString(16); if (r.length == 1) r = '0' + r; + g = g.toString(16); if (g.length == 1) g = '0' + g; + b = b.toString(16); if (b.length == 1) b = '0' + b; + return "#" + r + g + b; +} + +/** Get the background color of an element */ +Fader.getBgColor = function(elt) { + while(elt) { + var c; + if (window.getComputedStyle) c = window.getComputedStyle(elt,null).getPropertyValue("background-color"); + if (elt.currentStyle) c = elt.currentStyle.backgroundColor; + if ((c != "" && c != "transparent") || elt.tagName == "BODY") { break; } + elt = elt.parentNode; + } + if (c == undefined || c == "" || c == "transparent" || c == "white") c = "#FFFFFF"; + + var rgb = c.match(/rgb\s*\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)/); + if (rgb) return this.rgbToColor(parseInt(rgb[1]),parseInt(rgb[2]),parseInt(rgb[3])); + return c; +} + +BrowserUpdate.highlight = Fader.fade; + +//------------------------------------------------------------------------------------------------- +// Blinker used to highlight page areas that have been updated +//------------------------------------------------------------------------------------------------- + +function Blinker(elt, color, hltDelay, normalDelay, blinks) { + this.element = elt; + if (!color) color = "#FFFF80"; // yellow + if (!hltDelay) hltDelay = 100; + if (!normalDelay) normalDelay = 100; + if (!blinks) blinks = 2; + + this.hltColor = color; + this.hltDelay = hltDelay; + this.normalDelay = normalDelay; + this.normalColor = Fader.getBgColor(elt); + this.maxBlinks = blinks * 2; + this.blink = 0; +} + +Blinker.prototype.start = function() { + this.blink = 0; + this._doBlink(); +} + +Blinker.blink = function(elt) { + new Blinker(elt).start(); +} + +Blinker.prototype._doBlink = function() { + var hlt = (this.blink % 2 == 0); + this.element.style.backgroundColor = hlt ? this.hltColor : this.normalColor;; + if (this.blink <= this.maxBlinks) { + var blinker = this; + setTimeout(function() {blinker._doBlink();}, hlt ? this.hltDelay : this.normalDelay); + } + this.blink++; } Modified: cocoon/blocks/core/forms/trunk/samples/forms/carselector_template.xml URL: http://svn.apache.org/viewcvs/cocoon/blocks/core/forms/trunk/samples/forms/carselector_template.xml?rev=168669&r1=168668&r2=168669&view=diff ============================================================================== --- cocoon/blocks/core/forms/trunk/samples/forms/carselector_template.xml (original) +++ cocoon/blocks/core/forms/trunk/samples/forms/carselector_template.xml Fri May 6 15:01:55 2005 @@ -18,11 +18,11 @@ <!-- Import the macros that define CForms template elements --> <jx:import uri="resource://org/apache/cocoon/forms/generation/jx-macros.xml"/> <title>Car selector</title> - <para>This example illustrates how you can programmatically update the - content of a selection list.</para> - <para> - This sample illustrates event-handling in Cocoon Forms and how selection lists can be changed - programmatically. + <para>This example illustrates: + <ul> + <li>how you can programmatically update the content of a selection list,</li> + <li>the AJAX features that allow partial page reloads and the associated visual effects.</li> + </ul> </para> <para> Event-handlers are defined in the form definition to update the selection lists and set @@ -34,6 +34,30 @@ See "carselector_form.xml" and "carselector_template.xml" to see how this is done. </para> <content> + <script language="JavaScript"> + var currentEffect = "fade"; + function setEffect(effect) { + if (effect == "none") { + BrowserUpdate.highlight = null; + } else if (effect == "fade") { + BrowserUpdate.highlight = Fader.fade; + } else if (effect == "blink") { + BrowserUpdate.highlight = Blinker.blink; + } + + document.getElementById(currentEffect).style.fontWeight = ""; + document.getElementById(effect).style.fontWeight = "bold"; + currentEffect = effect; + return false; + } + </script> + <para> + Page update effect: + <a id="none" href="#" onclick="setEffect('none')">None</a> - + <a style="font-weight: bold" id="fade" href="#" onclick="setEffect('fade')">Fade</a> - + <a id="blink" href="#" onclick="setEffect('blink')">Blink</a> + </para> + <ft:form-template action="carselector" method="POST" ajax="true"> <ft:continuation-id/> <fi:group> Modified: cocoon/blocks/core/forms/trunk/samples/welcome.xml URL: http://svn.apache.org/viewcvs/cocoon/blocks/core/forms/trunk/samples/welcome.xml?rev=168669&r1=168668&r2=168669&view=diff ============================================================================== --- cocoon/blocks/core/forms/trunk/samples/welcome.xml (original) +++ cocoon/blocks/core/forms/trunk/samples/welcome.xml Fri May 6 15:01:55 2005 @@ -30,14 +30,18 @@ <sample name="Cocoon Forms Documentation" href="http://cocoon.apache.org/2.1/userdocs/forms/"> Documentation is available on the Cocoon site. </sample> + <note> + Samples with the (Ajax) mark use the new transparent Ajax (Asynchronous Javascript And XML) framework + to reduce client/server roundtrips and perform partial page updates. + </note> </group> <group name="Basic Samples"> <sample name="Various (Actions)" href="form1">This sample shows validation, event handling and various Cocoon Forms features.</sample> <sample name="Various (Flowscript)" href="form1.flow">The same sample as above using Flowscript.</sample> <sample name="Registration" href="registration">A simple registration form.</sample> - <sample name="Car selector" href="carselector">Illustrates programmatically changing selectionlists.</sample> - <sample name="XHR Car selector" href="xhr_carselector">Same sample, using XmlHttpRequest to reduce client/server roundtrips.</sample> + <sample name="Car selector" href="carselector">(Ajax) Illustrates programmatically changing selectionlists.</sample> + <sample name="XHR Car selector" href="xhr_carselector">Same sample, with the historical first use of XmlHttpRequest to reduce client/server roundtrips.</sample> <sample name="Country selector" href="countryselector">Illustrates programmatically changing flow-jxpath selectionlists.</sample> <sample name="Upload" href="upload">Shows an upload widget used with Flowscript</sample> <sample name="Form Model GUI" href="form_model_gui.flow">Illustrates the use of Class, New, Struct, and Union.</sample> @@ -88,14 +92,14 @@ widgets. </note> <sample name="Dynamic repeater template" href="do-dynaRepeater.flow"> - Shows a simple repeater, which isn't displayed at all if empty, and whose row action depend on + (Ajax) Shows a simple repeater, which isn't displayed at all if empty, and whose row action depend on the row number. </sample> <sample name="Datasource chooser" href="do-datasourceChooser.flow"> - A datasource chooser, illustrating the fd:union widget. + (Ajax) A datasource chooser, illustrating the fd:union widget. </sample> <sample name="Task tree" href="do-taskTree.flow"> - A project work breakdown into a hierarchy of tasks, showing the use of the fd:class and fd:new + (Ajax) A project work breakdown into a hierarchy of tasks, showing the use of the fd:class and fd:new widgets. </sample> </group>