coliver 2003/02/23 11:20:25
Modified: src/java/org/apache/cocoon/components/flow/javascript system.js ScriptablePointer.java ScriptablePropertyPointer.java ScriptablePropertyHandler.java Log: 'Updated to support using the Cocoon flow layer with XMLForm' Revision Changes Path 1.7 +264 -0 xml-cocoon2/src/java/org/apache/cocoon/components/flow/javascript/system.js Index: system.js =================================================================== RCS file: /home/cvs/xml-cocoon2/src/java/org/apache/cocoon/components/flow/javascript/system.js,v retrieving revision 1.6 retrieving revision 1.7 diff -u -r1.6 -r1.7 --- system.js 9 Jan 2003 03:41:37 -0000 1.6 +++ system.js 23 Feb 2003 19:20:25 -0000 1.7 @@ -112,3 +112,267 @@ cocoon.outputModuleRollback(type); } } + +// +// XMLForm Support +// + +/** + * Creates a new JavaScript wrapper of a Form object + * see org.apache.cocoon.components.xmlform.Form + * @param id form id + * @param validatorNS Namespace of validator + * @param validatorDoc Validator document + */ + +function XForm(id, validatorNS, validatorDoc) { + cocoon.createSession(); + this.id = id; + this.lastContinuation = null; + XForm.forms[id] = this; + this.validatorNS = validatorNS; + this.validatorDoc = validatorDoc; + this.dead = false; +} + +/** + * Global variable that stores XForm instances by id + */ +XForm.forms = {}; + +/** + * Return the model object of this form + * @return a Java bean, JavaScript, DOM, or JDOM object + */ +XForm.prototype.getModel = function() { + return this.form.getModel(); +} + +/** + * Set the model object of this form + * @param model Any Java bean, JavaScript, DOM, or JDOM object + */ +XForm.prototype.setModel = function(model) { + this.form = + new Packages.org.apache.cocoon.components.xmlform.Form(this.id, + model); + this.form.setAutoValidate(false); + if (this.validatorNS != undefined && this.validatorDoc != undefined) { + this._setValidator(this.validatorNS, this.validatorDoc); + } +} + +/** + * Creates a new web continuation + * @param lastCont previous web continuation + * @param timeToLive expiration time for this continuation + * @return a new WebContinuation instance + */ +XForm.prototype.start = function(lastCont, timeToLive) { + var k = new Continuation(); + var kont = new WebContinuation(cocoon, k, + lastCont, timeToLive); + return kont; +} + +/** + * Adds a violation to this form + * @param xpath xpath expression of field that contains invalid data + * @param message error message + */ +XForm.prototype.addViolation = function(xpath, message) { + var violation = + new Packages.org.apache.cocoon.components.validation.Violation(); + violation.path = xpath; + violation.message = message; + var list = new java.util.LinkedList(); + list.add(violation); + try { + this.form.addViolations(list); + } catch (e) { + print(e); + if (e instanceof java.lang.Throwable) { + e.printStackTrace(); + } + } +} + +/** + * Computes the value of an xpath expression against the model of this form + * @param expr xpath expression + * @return result of computing <code>expr</code> + */ +XForm.prototype.xpath = function(expr) { + var ctx = new Packages.org.apache.commons.jxpath.JXPathContext.newContext(this.form.getModel()); + return ctx.getValue(expr); +} + +XForm.prototype._sendView = function(uri, lastCont, timeToLive) { + var k = new Continuation(); + var kont = new WebContinuation(cocoon, k, lastCont, timeToLive); + var bizData = this.form.getModel(); + if (bizData == undefined) { + bizData = null; + } + cocoon.forwardTo("cocoon://" + cocoon.environment.getURIPrefix() + uri, + bizData, kont); + this.lastContinuation = kont; + suicide(); +} + +/** + * Sends view to presentation pipeline and waits for subsequent submission. + * Automatically resends view if validation fails. + * Creates two continuations: one immediately before the page submission + * and one immediately after. These are used to implement automated support + * for back/forward navigation in the form. When you move forward in the + * form the second continuation is invoked. When you move back from the + * following page the first continuation is invoked. + * @param phase view to send (and phase to validate) + * @param uri presentation pipeline resource identifier + * @param validator optional function invoked to perform validation + */ +XForm.prototype.sendView = function(phase, uri, validator) { + var lastCont = this.lastContinuation; + this.form.clearViolations(); + var view = this.form.getFormView(cocoon.environment.objectModel); + while (true) { + var k = this.start(lastCont); + if (cocoon.request == null) { + // this continuation has been invalidated + this.dead = true; + handleInvalidContinuation(); + suicide(); + } + cocoon.request.setAttribute("view", view); + try { + this.form.save(cocoon.environment.objectModel, "request"); + } catch (e if (e instanceof java.lang.IllegalStateException)) { + if (cocoon.session.getAttribute(this.id) != null) { + // someone else has taken my session + this.dead = true; + cocoon.removeSession(); + handleInvalidContinuation(); + suicide(); + } + throw e; + } + this._sendView(uri, k); + if (this.dead || cocoon.request == null) { + // this continuation has been invalidated + handleInvalidContinuation(); + suicide(); + } + this.form.populate(cocoon.environment.objectModel); + if (validator != undefined) { + validator(this); + } + this.form.validate(phase); + if (this.form.violationsAsSortedSet == null || + this.form.violationsAsSortedSet.size() == 0) { + break; + } + } +} + +XForm.prototype._setValidator = function(schNS, schDoc) { + // if validator params are not specified, then + // there is no validation by default + if (schNS == null || schDoc == null ) return null; + var resolver = cocoon.environment; + var schemaSrc = resolver.resolveURI( schDoc ); + try { + var is = Packages.org.apache.cocoon.components.source.SourceUtil.getInputSource(schemaSrc); + var schf = Packages.org.apache.cocoon.components.validation.SchemaFactory.lookup ( schNS ); + var sch = schf.compileSchema ( is ); + this.form.setValidator(sch.newValidator()); + } finally { + resolver.release(schemaSrc); + } +} + +/** + * Sends view to presentation pipeline but doesn't wait for submission + * @param view view to send + * @param uri presentation pipeline uri + */ + +XForm.prototype.finish = function(view, uri) { + try { + this.form.save(cocoon.environment.objectModel, "request"); + } catch (e if (e instanceof java.lang.IllegalStateException)) { + if (cocoon.session.getAttribute(this.id) != null) { + // someone else has taken my session + this.dead = true; + cocoon.removeSession(); + handleInvalidContinuation(); + suicide(); + } + throw e; + } + cocoon.forwardTo("cocoon://" + cocoon.environment.getURIPrefix() + uri, + this.form.getModel(), null); + delete XForm.forms[this.id]; // delete myself + this.dead = true; + cocoon.removeSession(); + if (this.lastContinuation != null) { + this.lastContinuation.invalidate(); + this.lastContinuation = null; + } + +} + +/** + * Entry point to a flow-based XMLForm application. Replaces the functionality + * of XMLForm actions. + * @param application Name of a JavaScript function that represents the page flow for a form + * @param id form id + * @param validator_ns XML namespace of validator + * @param validator_doc validator document + */ + +function xmlForm(application, id, validator_ns, validator_doc) { + function getCommand() { + if (cocoon.request == null) { + return undefined; + } + var enum_ = cocoon.request.parameterNames; + var command = undefined; + while (enum_.hasMoreElements()) { + var paramName = enum_.nextElement(); + // search for the command + if (paramName.startsWith(Packages.org.apache.cocoon.Constants.ACTION_PARAM_PREFIX)) { + command = + paramName.substring(Packages.org.apache.cocoon.Constants.ACTION_PARAM_PREFIX.length(), paramName.length()); + break; + } + } + // command encodes the continuation id for "back" or "next" actions + return command; + } + var command = getCommand(); + if (command != undefined) { + var xform = XForm.forms[id]; + if (xform != undefined) { + // invoke a continuation + var continuationsMgr = + cocoon.componentManager.lookup(Packages.org.apache.cocoon.components.flow.ContinuationsManager.ROLE); + var wk = continuationsMgr.lookupWebContinuation(command); + cocoon.componentManager.release(continuationsMgr); + if (wk != null) { + var jswk = wk.userObject; + xform.form.clearViolations(); + jswk.continuation(jswk); + } else { + handleInvalidContinuation(command); + } + } + } + // Just start a new instance of the application + cocoon.session.removeAttribute(id); + this[application](new XForm(id, validator_ns, validator_doc)); +} + + + + 1.2 +15 -0 xml-cocoon2/src/java/org/apache/cocoon/components/flow/javascript/ScriptablePointer.java Index: ScriptablePointer.java =================================================================== RCS file: /home/cvs/xml-cocoon2/src/java/org/apache/cocoon/components/flow/javascript/ScriptablePointer.java,v retrieving revision 1.1 retrieving revision 1.2 diff -u -r1.1 -r1.2 --- ScriptablePointer.java 20 Feb 2003 18:22:43 -0000 1.1 +++ ScriptablePointer.java 23 Feb 2003 19:20:25 -0000 1.2 @@ -47,6 +47,7 @@ import org.mozilla.javascript.*; import org.apache.commons.jxpath.ri.QName; import org.apache.commons.jxpath.*; +import org.apache.commons.jxpath.ri.model.dynamic.*; import org.apache.commons.jxpath.ri.model.beans.*; import org.apache.commons.jxpath.ri.model.*; import java.util.Locale; @@ -65,6 +66,13 @@ node = object; } + public boolean isCollection() { + if (node instanceof NativeArray) { + return true; + } + return false; + } + public ScriptablePointer(QName name, Scriptable object, Locale locale) { @@ -84,6 +92,13 @@ public PropertyPointer getPropertyPointer(){ return new ScriptablePropertyPointer(this, handler); + } + + public void setValue(Object value){ + System.out.println("attempt to set " + getName() + " to " + value); + System.out.println("node="+node + "->" +java.util.Arrays.asList(node.getIds())); + getParent().setValue(value); + //handler.setProperty(node, getName().toString(), value); } } 1.2 +110 -15 xml-cocoon2/src/java/org/apache/cocoon/components/flow/javascript/ScriptablePropertyPointer.java Index: ScriptablePropertyPointer.java =================================================================== RCS file: /home/cvs/xml-cocoon2/src/java/org/apache/cocoon/components/flow/javascript/ScriptablePropertyPointer.java,v retrieving revision 1.1 retrieving revision 1.2 diff -u -r1.1 -r1.2 --- ScriptablePropertyPointer.java 20 Feb 2003 18:22:43 -0000 1.1 +++ ScriptablePropertyPointer.java 23 Feb 2003 19:20:25 -0000 1.2 @@ -47,10 +47,11 @@ import org.mozilla.javascript.*; import org.apache.commons.jxpath.ri.QName; import org.apache.commons.jxpath.*; -import org.apache.commons.jxpath.ri.model.beans.*; +import org.apache.commons.jxpath.ri.model.dynamic.*; import org.apache.commons.jxpath.ri.model.*; -import org.apache.commons.jxpath.util.*; import java.util.Locale; +import java.util.LinkedList; +import java.util.List; public class ScriptablePropertyPointer extends DynamicPropertyPointer { @@ -66,6 +67,9 @@ Object obj = getBaseValue(); if (obj instanceof Scriptable) { Scriptable node = (Scriptable)obj; + if (node instanceof NativeArray) { + return (int)((NativeArray)node).jsGet_length(); + } if (ScriptableObject.hasProperty(node, "length")) { Object val = ScriptableObject.getProperty(node, "length"); if (val instanceof Number) { @@ -76,32 +80,123 @@ return super.getLength(); } - public Object getNodeValue() { - Object value; + public Object getValue() { + Object val = getNode(); + if (val instanceof NativeArray) { + NativeArray arr = (NativeArray)val; + List list = new LinkedList(); + int len = (int)arr.jsGet_length(); + for (int i = 0; i < len; i++) { + Object obj = arr.get(i, arr); + if (obj == Undefined.instance) { + obj = null; + } + list.add(obj); + } + return list; + } + return super.getValue(); + } + + public void setValue(Object value){ if (index == WHOLE_COLLECTION){ - value = handler.getProperty(getBean(), getPropertyName()); + handler.setProperty(getBean(), getPropertyName(), value); } else { - value = handler.getProperty(getBean(), getPropertyName()); - if (value instanceof Scriptable && - ScriptableObject.hasProperty((Scriptable)value, index)) { - value = ScriptableObject.getProperty((Scriptable)value, index); + Object val = handler.getProperty(getBean(), getPropertyName()); + if (val instanceof Scriptable) { + ScriptableObject.putProperty((Scriptable)val, index, value); } else { - return super.getNodeValue(); + super.setValue(value); } } - return value; } - public void setValue(Object value){ + public void remove(){ if (index == WHOLE_COLLECTION){ - handler.setProperty(getBean(), getPropertyName(), value); + handler.setProperty(getBean(), "length", new Integer(0)); } else { Object val = handler.getProperty(getBean(), getPropertyName()); if (val instanceof Scriptable) { - ScriptableObject.putProperty((Scriptable)val, index, value); + try { + ScriptableObject.deleteProperty((Scriptable)val, index); + } catch (Exception e) { + e.printStackTrace(); + } } else { - super.setValue(value); + super.remove(); + } + } + } + + public boolean isCollection() { + Object obj = getBaseValue(); + if (obj instanceof NativeArray) { + return true; + } + return super.isCollection(); + } + + public String asPath(){ + Object obj = getBaseValue(); + if (!(obj instanceof Scriptable)) { + return super.asPath(); + } + StringBuffer buffer = new StringBuffer(); + buffer.append(getParent().asPath()); + if (buffer.length() == 0){ + buffer.append("/."); + } + else if (buffer.charAt(buffer.length() - 1) == '/'){ + buffer.append('.'); + } + buffer.append("[EMAIL PROTECTED] = '"); + buffer.append(escape(getPropertyName())); + buffer.append("']"); + if (index != WHOLE_COLLECTION && (obj instanceof NativeArray)) { + buffer.append('[').append(index+1).append(']'); + } + return buffer.toString(); + } + + private String escape(String string){ + int index = string.indexOf('\''); + while (index != -1){ + string = string.substring(0, index) + "'" + string.substring(index + 1); + index = string.indexOf('\''); + } + index = string.indexOf('\"'); + while (index != -1){ + string = string.substring(0, index) + """ + string.substring(index + 1); + index = string.indexOf('\"'); + } + return string; + } + + public Object getImmediateNode() { + System.out.println("get immediate node: " + getBean() + ": " + + getPropertyName() + ": " + + index); + Object value; + if (index == WHOLE_COLLECTION) { + value = handler.getProperty(getBean(), getPropertyName()); + } + else { + value = handler.getProperty(getBean(), getPropertyName()); + if (value instanceof Scriptable) { + System.out.println("getting indexed property: " + + getPropertyName() + ": " + + index); + value = ScriptableObject.getProperty((Scriptable)value, index); + if (value == ScriptableObject.NOT_FOUND) { + value = null; + } else if (value instanceof Wrapper) { + value = ((Wrapper)value).unwrap(); + } + } else { + return super.getImmediateNode(); } } + return value; } + } 1.2 +33 -7 xml-cocoon2/src/java/org/apache/cocoon/components/flow/javascript/ScriptablePropertyHandler.java Index: ScriptablePropertyHandler.java =================================================================== RCS file: /home/cvs/xml-cocoon2/src/java/org/apache/cocoon/components/flow/javascript/ScriptablePropertyHandler.java,v retrieving revision 1.1 retrieving revision 1.2 diff -u -r1.1 -r1.2 --- ScriptablePropertyHandler.java 20 Feb 2003 18:22:43 -0000 1.1 +++ ScriptablePropertyHandler.java 23 Feb 2003 19:20:25 -0000 1.2 @@ -46,25 +46,44 @@ package org.apache.cocoon.components.flow.javascript; import org.apache.commons.jxpath.DynamicPropertyHandler; import org.mozilla.javascript.*; +import java.util.List; +import java.util.LinkedList; public class ScriptablePropertyHandler implements DynamicPropertyHandler { public Object getProperty(Object obj, String propertyName) { Context cx = null; + System.out.println("getProperty: " + propertyName); try { cx = Context.enter(); Scriptable s = (Scriptable)obj; + System.out.println("s="+s); Object result = ScriptableObject.getProperty(s, propertyName); if (result == ScriptableObject.NOT_FOUND) { - result = null; + result = ScriptableObject.getProperty(s, "get" + propertyName.substring(0, 1).toUpperCase() + (propertyName.length() > 1 ? propertyName.substring(1) : "")); + if (result != ScriptableObject.NOT_FOUND && + result instanceof Function) { + try { + result = ((Function)result).call(cx, + ScriptableObject.getTopLevelScope(s), s, new Object[] {}); + } catch (JavaScriptException exc) { + exc.printStackTrace(); + result = Undefined.instance; + } + } + if (result == Undefined.instance || + result == Scriptable.NOT_FOUND) { + result = null; + } } else if (result instanceof Wrapper) { result = ((Wrapper)result).unwrap(); } else if (result == Undefined.instance) { result = null; } + System.out.println("getProperty: " + propertyName + " = " + result); return result; } finally { - cx.exit(); + Context.exit(); } } @@ -84,7 +103,7 @@ } return result; } finally { - cx.exit(); + Context.exit(); } } @@ -93,12 +112,19 @@ Context cx = null; try { cx = Context.enter(); + System.out.println("setProperty: " + propertyName + + " = " + Context.toObject(value, (Scriptable)obj)); + if (!(value == null + || value instanceof String + || value instanceof Number + || value instanceof Boolean)) { + value = Context.toObject(value, + (Scriptable)obj); + } ScriptableObject.putProperty((Scriptable)obj, - propertyName, - Context.toObject(value, - (Scriptable)obj)); + propertyName, value); } finally { - cx.exit(); + Context.exit(); } } }