001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.cocoon.forms.binding;
018:
019: import java.util.Collections;
020: import java.util.HashMap;
021: import java.util.Iterator;
022: import java.util.Map;
023:
024: import org.apache.avalon.framework.context.Context;
025: import org.apache.avalon.framework.context.ContextException;
026: import org.apache.avalon.framework.context.Contextualizable;
027:
028: import org.apache.cocoon.forms.binding.JXPathBindingManager.Assistant;
029: import org.apache.cocoon.forms.util.DomHelper;
030: import org.apache.cocoon.forms.util.JavaScriptHelper;
031:
032: import org.mozilla.javascript.Function;
033: import org.w3c.dom.Element;
034:
035: /**
036: * Builds a {@link Binding} based on two JavaScript snippets, respectively for loading and saving the form.
037: * This binding also optionally accepts named child bindings, which are useful when the bound widget is a container.
038: * <p>
039: * The syntax for this binding is as follows:
040: * <pre>
041: * <fb:javascript id="foo" path="@foo">
042: * <fb:load-form>
043: * var appValue = jxpathPointer.getValue();
044: * var formValue = doLoadConversion(appValue);
045: * widget.setValue(formValue);
046: * childBindings["foo"].loadFormFromModel(widget, jxpathContext);
047: * </fb:load-form>
048: * <fb:save-form>
049: * var formValue = widget.getValue();
050: * var appValue = doSaveConversion(formValue);
051: * jxpathPointer.setValue(appValue);
052: * childBindings["foo"].saveFormToModel(widget, jxpathContext);
053: * </fb:save-form>
054: * <fb:child-binding name="foo">
055: * <fb:value id="bar" path="baz"/>
056: * </fb:child-binding>
057: * </fb:javascript>
058: * </pre>
059: * This example is rather trivial and could be replaced by a simple <fb:value>, but
060: * it shows the available variables in the script:
061: * <ul>
062: * <li><code>widget</code>: the widget identified by the "id" attribute,
063: * <li><code>jxpathPointer</code>: the JXPath pointer corresponding to the "path" attribute,
064: * <li><code>jxpathContext</code> (not shown): the JXPath context corresponding to the "path" attribute
065: * </ul>
066: * <b>Notes:</b><ul>
067: * <li>The <fb:save-form> snippet should be ommitted if the "direction" attribute is set to "load".</li>
068: * <li>The <fb:load-form> snippet should be ommitted if the "direction" attribute is set to "save".</li>
069: * </ul>
070: *
071: * @version $Id: JavaScriptJXPathBindingBuilder.java 517733 2007-03-13 15:37:22Z vgritsenko $
072: */
073: public class JavaScriptJXPathBindingBuilder extends
074: JXPathBindingBuilderBase implements Contextualizable {
075:
076: private Context avalonContext;
077:
078: public void contextualize(Context context) throws ContextException {
079: this .avalonContext = context;
080: }
081:
082: public JXPathBindingBase buildBinding(Element element,
083: Assistant assistant) throws BindingException {
084: try {
085: CommonAttributes commonAtts = JXPathBindingBuilderBase
086: .getCommonAttributes(element);
087:
088: String id = DomHelper.getAttribute(element, "id", null);
089: String path = DomHelper.getAttribute(element, "path", null);
090:
091: JavaScriptJXPathBinding otherBinding = (JavaScriptJXPathBinding) assistant
092: .getContext().getSuperBinding();
093:
094: if (otherBinding != null) {
095: commonAtts = JXPathBindingBuilderBase
096: .mergeCommonAttributes(otherBinding
097: .getCommonAtts(), commonAtts);
098:
099: if (id == null) {
100: id = otherBinding.getId();
101: }
102: if (path == null) {
103: path = otherBinding.getPath();
104: }
105: }
106:
107: // Build load script
108: Function loadScript = null;
109: if (commonAtts.loadEnabled) {
110: if (otherBinding != null) {
111: loadScript = otherBinding.getLoadScript();
112: }
113:
114: Element loadElem = DomHelper.getChildElement(element,
115: BindingManager.NAMESPACE, "load-form");
116: if (loadElem != null) {
117: loadScript = JavaScriptHelper.buildFunction(
118: loadElem, "loadForm",
119: JavaScriptJXPathBinding.LOAD_PARAMS);
120: }
121: }
122:
123: // Build save script
124: Function saveScript = null;
125: if (commonAtts.saveEnabled) {
126: if (otherBinding != null) {
127: saveScript = otherBinding.getSaveScript();
128: }
129:
130: Element saveElem = DomHelper.getChildElement(element,
131: BindingManager.NAMESPACE, "save-form");
132: if (saveElem != null) {
133: saveScript = JavaScriptHelper.buildFunction(
134: saveElem, "saveForm",
135: JavaScriptJXPathBinding.SAVE_PARAMS);
136: }
137: }
138:
139: // Build child bindings
140: Map childBindings = new HashMap();
141:
142: if (otherBinding != null) {
143: Map otherChildren = otherBinding.getChildBindingsMap();
144: Iterator it = otherChildren.entrySet().iterator();
145: while (it.hasNext()) {
146: Map.Entry entry = (Map.Entry) it.next();
147: childBindings.put(entry.getKey(), entry.getValue());
148: }
149: }
150:
151: Element[] children = DomHelper.getChildElements(element,
152: BindingManager.NAMESPACE, "child-binding");
153: if (children.length != 0) {
154: for (int i = 0; i < children.length; i++) {
155: Element child = children[i];
156:
157: // Get the binding name and check its uniqueness
158: String name = DomHelper.getAttribute(child, "name");
159:
160: JXPathBindingBase[] otherBindings = null;
161: if (childBindings.containsKey(name)) {
162: //throw new BindingException("Duplicate name '" + name + "' at " + DomHelper.getLocation(child));
163: otherBindings = ((ComposedJXPathBindingBase) childBindings
164: .get(name)).getChildBindings();
165: }
166:
167: // Build the child binding
168: JXPathBindingBase[] bindings = assistant
169: .makeChildBindings(child, otherBindings);
170: if (bindings == null) {
171: bindings = new JXPathBindingBase[0];
172: }
173:
174: ComposedJXPathBindingBase composedBinding = new ComposedJXPathBindingBase(
175: commonAtts, bindings);
176: composedBinding.enableLogging(getLogger());
177: childBindings.put(name, composedBinding);
178: }
179: }
180:
181: JXPathBindingBase result = new JavaScriptJXPathBinding(
182: this .avalonContext, commonAtts, id, path,
183: loadScript, saveScript, Collections
184: .unmodifiableMap(childBindings));
185: result.enableLogging(getLogger());
186: return result;
187:
188: } catch (BindingException e) {
189: throw e;
190: } catch (Exception e) {
191: throw new BindingException("Cannot build binding", e,
192: DomHelper.getLocationObject(element));
193: }
194: }
195: }
|