/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.cocoon.woody.flow.javascript.v2; import org.apache.cocoon.woody.formmodel.Action; import org.apache.cocoon.woody.formmodel.AggregateField; import org.apache.cocoon.woody.formmodel.BooleanField; import org.apache.cocoon.woody.formmodel.Field; import org.apache.cocoon.woody.formmodel.Form; import org.apache.cocoon.woody.formmodel.ContainerWidget; import org.apache.cocoon.woody.formmodel.MultiValueField; import org.apache.cocoon.woody.formmodel.Output; import org.apache.cocoon.woody.formmodel.Repeater; import org.apache.cocoon.woody.formmodel.Submit; import org.apache.cocoon.woody.formmodel.Upload; import org.apache.cocoon.woody.formmodel.Widget; import org.apache.cocoon.woody.formmodel.DataWidget; import org.apache.cocoon.woody.formmodel.SelectableWidget; import org.apache.cocoon.woody.datatype.Datatype; import org.apache.cocoon.woody.validation.ValidationError; import org.apache.cocoon.woody.validation.ValidationErrorAware; import org.apache.cocoon.woody.datatype.SelectionList; import org.apache.cocoon.woody.event.FormHandler; import org.apache.cocoon.woody.event.ActionEvent; import org.apache.cocoon.woody.event.ValueChangedEvent; import org.apache.cocoon.woody.event.WidgetEvent; import org.mozilla.javascript.Context; import org.mozilla.javascript.JavaScriptException; import org.mozilla.javascript.NativeArray; import org.mozilla.javascript.Function; import org.mozilla.javascript.Scriptable; import org.mozilla.javascript.ScriptableObject; import org.mozilla.javascript.Undefined; import org.mozilla.javascript.Wrapper; import java.math.BigDecimal; import java.util.List; import java.util.LinkedList; import java.util.Iterator; import java.util.Map; import java.util.HashMap; /** * @version $Id$ * */ public class ScriptableWidget extends ScriptableObject { final static String WIDGETS_PROPERTY = "__widgets__"; Widget delegate; ScriptableWidget formWidget; class ScriptableFormHandler implements FormHandler { public void handleEvent(WidgetEvent widgetEvent) { Widget src = widgetEvent.getSourceWidget(); ScriptableWidget w = wrap(src); w.handleEvent(widgetEvent); } } public String getClassName() { return "Widget"; } public ScriptableWidget() { } public ScriptableWidget(Object widget) { this.delegate = (Widget)unwrap(widget); if (delegate instanceof Form) { Form form = (Form)delegate; form.setFormHandler(new ScriptableFormHandler()); formWidget = this; Map widgetMap = new HashMap(); widgetMap.put(delegate, this); defineProperty(WIDGETS_PROPERTY, widgetMap, DONTENUM|PERMANENT); } } static private Object unwrap(Object obj) { if (obj == Undefined.instance) { return null; } if (obj instanceof Wrapper) { return ((Wrapper)obj).unwrap(); } return obj; } private void deleteWrapper(Widget w) { if (delegate instanceof Form) { Map widgetMap = (Map)super.get(WIDGETS_PROPERTY, this); widgetMap.remove(w); } } private ScriptableWidget wrap(Widget w) { if (w == null) return null; if (delegate instanceof Form) { Map widgetMap = (Map)super.get(WIDGETS_PROPERTY, this); ScriptableWidget result = null; result = (ScriptableWidget)widgetMap.get(w); if (result == null) { result = new ScriptableWidget(w); result.formWidget = this; result.setPrototype(getClassPrototype(this, getClassName())); result.setParentScope(getParentScope()); widgetMap.put(w, result); } return result; } else { return formWidget.wrap(w); } } public boolean has(String id, Scriptable start) { if (delegate != null) { if (!(delegate instanceof Repeater)) { Widget sub = delegate.getWidget(id); if (sub != null) { return true; } } } return super.has(id, start); } public boolean has(int index, Scriptable start) { if (super.has(index, start)) { return true; } if (delegate instanceof Repeater) { Repeater repeater = (Repeater)delegate; return index >= 0 && index < repeater.getSize(); } if (delegate instanceof MultiValueField) { Object[] values = (Object[])delegate.getValue(); return index >= 0 && index < values.length; } return false; } public Object get(String id, Scriptable start) { Object result = super.get(id, start); if (result != NOT_FOUND) { return result; } if (delegate != null && !(delegate instanceof Repeater)) { Widget sub = delegate.getWidget(id); if (sub != null) { return wrap(sub); } } return NOT_FOUND; } public Object get(int index, Scriptable start) { Object result = super.get(index, start); if (result != NOT_FOUND) { return result; } if (delegate instanceof Repeater) { Repeater repeater = (Repeater)delegate; if (index >= 0) { int count = index + 1 - repeater.getSize(); if (count > 0) { ScriptableWidget[] rows = new ScriptableWidget[count]; for (int i = 0; i < count; i++) { rows[i] = wrap(repeater.addRow()); } for (int i = 0; i < count; i++) { rows[i].notifyAddRow(); } } return wrap(repeater.getRow(index)); } } else if (delegate instanceof MultiValueField) { Object[] values = (Object[])delegate.getValue(); if (index >= 0 && index < values.length) { return values[index]; } } return NOT_FOUND; } public Object[] getAllIds() { Object[] result = super.getAllIds(); return addWidgetIds(result); } public Object[] getIds() { Object[] result = super.getIds(); return addWidgetIds(result); } private Object[] addWidgetIds(Object[] result) { if (delegate instanceof ContainerWidget) { Iterator iter = ((ContainerWidget)delegate).getChildren(); List list = new LinkedList(); for (int i = 0; i < result.length; i++) { list.add(result[i]); } while (iter.hasNext()) { Widget widget = (Widget)iter.next(); list.add(widget.getId()); } result = list.toArray(); } return result; } private void deleteRow(Repeater repeater, int index) { Widget row = repeater.getRow(index); ScriptableWidget s = wrap(row); s.notifyRemoveRow(); formWidget.deleteWrapper(row); repeater.removeRow(index); } private void notifyAddRow() { ScriptableWidget repeater = wrap(delegate.getParent()); Object prop = getProperty(repeater, "onAddRow"); if (prop instanceof Function) { try { Function fun = (Function)prop; Object[] args = new Object[1]; Scriptable scope = getTopLevelScope(this); Scriptable thisObj = scope; Context cx = Context.getCurrentContext(); args[0] = this; fun.call(cx, scope, thisObj, args); } catch (Exception exc) { throw Context.reportRuntimeError(exc.getMessage()); } } } private void notifyRemoveRow() { ScriptableWidget repeater = wrap(delegate.getParent()); Object prop = getProperty(repeater, "onRemoveRow"); if (prop instanceof Function) { try { Function fun = (Function)prop; Object[] args = new Object[1]; Scriptable scope = getTopLevelScope(this); Scriptable thisObj = scope; Context cx = Context.getCurrentContext(); args[0] = this; fun.call(cx, scope, thisObj, args); } catch (Exception exc) { throw Context.reportRuntimeError(exc.getMessage()); } } } public void delete(int index) { if (delegate instanceof Repeater) { Repeater repeater = (Repeater)delegate; if (index >= 0 && index < repeater.getSize()) { deleteRow(repeater, index); return; } } else if (delegate instanceof MultiValueField) { MultiValueField field = (MultiValueField)delegate; Object[] values = (Object[])field.getValue(); if (values != null && values.length > index) { Object[] newValues = new Object[values.length-1]; int i; for (i = 0; i < index; i++) { newValues[i] = values[i]; } i++; for (;i < values.length; i++) { newValues[i-1] = values[i]; } field.setValues(newValues); } return; } super.delete(index); } public Object jsGet_value() { return delegate.getValue(); } public Object jsFunction_getValue() { return jsGet_value(); } public void jsFunction_setValue(Object value) throws JavaScriptException { jsSet_value(value); } public void jsSet_length(int len) { if (delegate instanceof Repeater) { Repeater repeater = (Repeater)delegate; int size = repeater.getSize(); if (size > len) { while (repeater.getSize() > len) { deleteRow(repeater, repeater.getSize() - 1); } } else { for (int i = size; i < len; ++i) { wrap(repeater.addRow()).notifyAddRow(); } } } } public Object jsGet_length() { if (delegate instanceof Repeater) { Repeater repeater = (Repeater)delegate; return new Integer(repeater.getSize()); } return Undefined.instance; } public void jsSet_value(Object value) throws JavaScriptException { if (delegate instanceof DataWidget) { value = unwrap(value); if (value != null) { Datatype datatype = ((DataWidget)delegate).getDatatype(); Class typeClass = datatype.getTypeClass(); if (typeClass == String.class) { value = Context.toString(value); } else if (typeClass == boolean.class || typeClass == Boolean.class) { value = Context.toBoolean(value) ? Boolean.TRUE : Boolean.FALSE; } else { if (value instanceof Double) { // make woody accept a JS Number if (typeClass == long.class || typeClass == Long.class) { value = new Long(((Number)value).longValue()); } else if (typeClass == int.class || typeClass == Integer.class) { value = new Integer(((Number)value).intValue()); } else if (typeClass == float.class || typeClass == Float.class) { value = new Float(((Number)value).floatValue()); } else if (typeClass == short.class || typeClass == Short.class) { value = new Short(((Number)value).shortValue()); } else if (typeClass == BigDecimal.class) { value = new BigDecimal(((Number)value).doubleValue()); } } } } delegate.setValue(value); } else if (delegate instanceof BooleanField) { BooleanField field = (BooleanField)delegate; field.setValue(new Boolean(Context.toBoolean(value))); } else if (delegate instanceof Repeater) { Repeater repeater = (Repeater)delegate; if (value instanceof NativeArray) { NativeArray arr = (NativeArray)value; Object length = getProperty(arr, "length"); int len = ((Number)length).intValue(); for (int i = repeater.getSize(); i >= len; --i) { deleteRow(repeater, i); } for (int i = 0; i < len; i++) { Object elemValue = getProperty(arr, i); ScriptableWidget wid = wrap(repeater.getRow(i)); wid.jsSet_value(elemValue); } } } else if (delegate instanceof AggregateField) { AggregateField aggregateField = (AggregateField)delegate; if (value instanceof Scriptable) { Scriptable obj = (Scriptable)value; Object[] ids = obj.getIds(); for (int i = 0; i < ids.length; i++) { String id = String.valueOf(ids[i]); Object val = getProperty(obj, id); ScriptableWidget wid = wrap(aggregateField.getWidget(id)); if (wid == null) { throw new JavaScriptException("No field \"" + id + "\" in widget \"" + aggregateField.getId() + "\""); } if (wid.delegate instanceof Field || wid.delegate instanceof BooleanField || wid.delegate instanceof Output) { if (val instanceof Scriptable) { Scriptable s = (Scriptable)val; if (s.has("value", s)) { wid.jsSet_value(s.get("value", s)); } } } else { wid.jsSet_value(val); } } } } else if (delegate instanceof Repeater.RepeaterRow) { Repeater.RepeaterRow row = (Repeater.RepeaterRow)delegate; if (value instanceof Scriptable) { Scriptable obj = (Scriptable)value; Object[] ids = obj.getIds(); for (int i = 0; i < ids.length; i++) { String id = String.valueOf(ids[i]); Object val = getProperty(obj, id); ScriptableWidget wid = wrap(row.getWidget(id)); if (wid == null) { throw new JavaScriptException("No field \"" + id + "\" in row " + i + " of repeater \"" + row.getParent().getId() + "\""); } if (wid.delegate instanceof Field || wid.delegate instanceof BooleanField || wid.delegate instanceof Output) { if (val instanceof Scriptable) { Scriptable s = (Scriptable)val; if (s.has("value", s)) { wid.jsSet_value(s.get("value", s)); } } } else { wid.jsSet_value(val); } } } else { throw new JavaScriptException("Expected an object instead of: " + Context.toString(value)); } } else if (delegate instanceof MultiValueField) { MultiValueField field = (MultiValueField)delegate; Object[] values = null; if (value instanceof NativeArray) { NativeArray arr = (NativeArray)value; Object length = getProperty(arr, "length"); int len = ((Number)length).intValue(); values = new Object[len]; for (int i = 0; i < len; i++) { Object elemValue = getProperty(arr, i); values[i] = unwrap(elemValue); } } else if (value instanceof Object[]) { values = (Object[])value; } field.setValues(values); } else { delegate.setValue(value); } } public String jsFunction_getId() { return delegate.getId(); } public ScriptableWidget jsFunction_getSubmitWidget() { return wrap(delegate.getForm().getSubmitWidget()); } public String jsFunction_getFullyQualifiedId() { return delegate.getFullyQualifiedId(); } public String jsFunction_getNamespace() { return delegate.getNamespace(); } public Object jsFunction_getParent() { if (delegate != null) { return wrap(delegate.getParent()); } return Undefined.instance; } public boolean jsFunction_isRequired() { return delegate.isRequired(); } public ScriptableWidget jsFunction_getForm() { return formWidget; } public boolean jsFunction_equals(Object other) { if (other instanceof ScriptableWidget) { ScriptableWidget otherWidget = (ScriptableWidget)other; return delegate.equals(otherWidget.delegate); } return false; } public ScriptableWidget jsFunction_getWidget(String id) { Widget sub = delegate.getWidget(id); return wrap(sub); } public void jsFunction_setValidationError(String message, Object parameters) { if (delegate instanceof ValidationErrorAware) { String[] parms = null; if (parameters != null && parameters != Undefined.instance) { Scriptable obj = Context.toObject(parameters, this); int len = (int) Context.toNumber(getProperty(obj, "length")); parms = new String[len]; for (int i = 0; i < len; i++) { parms[i] = Context.toString(getProperty(obj, i)); } } ValidationError validationError = null; if (message != null) { if (parms != null && parms.length > 0) { validationError = new ValidationError(message, parms); } else { validationError = new ValidationError(message, parms != null); } } ((ValidationErrorAware)delegate).setValidationError(validationError); formWidget.notifyValidationErrorListener(this, validationError); } } private void notifyValidationErrorListener(ScriptableWidget widget, ValidationError error) { Object fun = getProperty(this, "validationErrorListener"); if (fun instanceof Function) { try { Scriptable scope = getTopLevelScope(this); Scriptable thisObj = scope; Context cx = Context.getCurrentContext(); Object[] args = new Object[2]; args[0] = widget; args[1] = error; ((Function)fun).call(cx, scope, thisObj, args); } catch (Exception exc) { throw Context.reportRuntimeError(exc.getMessage()); } } } public Widget jsFunction_unwrap() { return delegate; } public ScriptableWidget jsFunction_addRow() { ScriptableWidget result = null; if (delegate instanceof Repeater) { result = wrap(((Repeater)delegate).addRow()); result.notifyAddRow(); } return result; } public ScriptableObject jsFunction_getRow(int index) { if (delegate instanceof Repeater) { return wrap(((Repeater)delegate).getRow(index)); } return null; } public void jsFunction_removeRow(Object obj) throws JavaScriptException { if (delegate instanceof Repeater) { Repeater repeater = (Repeater)delegate; if (obj instanceof Function) { Function fun = (Function)obj; int len = repeater.getSize(); boolean[] index = new boolean[len]; Object[] args = new Object[1]; Scriptable scope = getTopLevelScope(this); Scriptable thisObj = scope; Context cx = Context.getCurrentContext(); for (int i = 0; i < len; i++) { ScriptableWidget row = wrap(repeater.getRow(i)); args[0] = row; Object result = fun.call(cx, scope, thisObj, args); index[i] = Context.toBoolean(result); } for (int i = len-1; i >= 0; --i) { if (index[i]) { deleteRow(repeater, i); } } } else if (obj instanceof Number) { int index = (int)Context.toNumber(obj); if (index > 0 && index < repeater.getSize()) { deleteRow(repeater, index); } } else { //... } } } private void handleEvent(WidgetEvent e) { if (e instanceof ActionEvent) { Object obj = super.get("onClick", this); if (obj instanceof Function) { try { Function fun = (Function)obj; Object[] args = new Object[1]; Scriptable scope = getTopLevelScope(this); Scriptable thisObj = scope; Context cx = Context.getCurrentContext(); args[0] = ((ActionEvent)e).getActionCommand(); fun.call(cx, scope, thisObj, args); } catch (Exception exc) { throw Context.reportRuntimeError(exc.getMessage()); } } } else if (e instanceof ValueChangedEvent) { ValueChangedEvent vce = (ValueChangedEvent)e; Object obj = super.get("onChange", this); if (obj instanceof Function) { try { Function fun = (Function)obj; Object[] args = new Object[2]; Scriptable scope = getTopLevelScope(this); Scriptable thisObj = scope; Context cx = Context.getCurrentContext(); args[0] = vce.getOldValue(); args[1] = vce.getNewValue(); fun.call(cx, scope, thisObj, args); } catch (Exception exc) { throw Context.reportRuntimeError(exc.getMessage()); } } } } public void jsFunction_setSelectionList(Object arg, Object valuePathArg, Object labelPathArg) throws Exception { if (delegate instanceof SelectableWidget) { arg = unwrap(arg); if (valuePathArg != Undefined.instance && labelPathArg != Undefined.instance) { String valuePath = Context.toString(valuePathArg); String labelPath = Context.toString(labelPathArg); ((SelectableWidget)delegate).setSelectionList(arg, valuePath, labelPath); } else { if (arg instanceof SelectionList) { SelectionList selectionList = (SelectionList)arg; ((SelectableWidget)delegate).setSelectionList(selectionList); } else { String str = Context.toString(arg); ((SelectableWidget)delegate).setSelectionList(str); } } } } static final Object[] WIDGET_CLASS_MAP = { Form.class, "Form", Field.class, "Field", Action.class, "Action", Repeater.class, "Repeater", Repeater.RepeaterRow.class, "RepeaterRow", AggregateField.class, "AggregateField", BooleanField.class, "BooleanField", MultiValueField.class, "MultiValueField", Output.class, "Output", Submit.class, "Submit", Upload.class, "Upload" }; public String jsFunction_getWidgetClass() { for (int i = 0; i < WIDGET_CLASS_MAP.length; i += 2) { Class c = (Class)WIDGET_CLASS_MAP[i]; if (c.isAssignableFrom(delegate.getClass())) { return (String)WIDGET_CLASS_MAP[i + 1]; } } return "<unknown>"; } public String jsFunction_toString() { return "[object Widget (" + jsFunction_getWidgetClass() + ")]"; } }