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:
018: package org.apache.cocoon.woody.flow.javascript.v2;
019:
020: import org.apache.cocoon.woody.formmodel.Action;
021: import org.apache.cocoon.woody.formmodel.AggregateField;
022: import org.apache.cocoon.woody.formmodel.BooleanField;
023: import org.apache.cocoon.woody.formmodel.Field;
024: import org.apache.cocoon.woody.formmodel.Form;
025: import org.apache.cocoon.woody.formmodel.ContainerWidget;
026: import org.apache.cocoon.woody.formmodel.MultiValueField;
027: import org.apache.cocoon.woody.formmodel.Output;
028: import org.apache.cocoon.woody.formmodel.Repeater;
029: import org.apache.cocoon.woody.formmodel.Submit;
030: import org.apache.cocoon.woody.formmodel.Upload;
031: import org.apache.cocoon.woody.formmodel.Widget;
032: import org.apache.cocoon.woody.formmodel.DataWidget;
033: import org.apache.cocoon.woody.formmodel.SelectableWidget;
034: import org.apache.cocoon.woody.datatype.Datatype;
035: import org.apache.cocoon.woody.validation.ValidationError;
036: import org.apache.cocoon.woody.validation.ValidationErrorAware;
037: import org.apache.cocoon.woody.datatype.SelectionList;
038: import org.apache.cocoon.woody.event.FormHandler;
039: import org.apache.cocoon.woody.event.ActionEvent;
040: import org.apache.cocoon.woody.event.ValueChangedEvent;
041: import org.apache.cocoon.woody.event.WidgetEvent;
042: import org.mozilla.javascript.Context;
043: import org.mozilla.javascript.JavaScriptException;
044: import org.mozilla.javascript.NativeArray;
045: import org.mozilla.javascript.Function;
046: import org.mozilla.javascript.Scriptable;
047: import org.mozilla.javascript.ScriptableObject;
048: import org.mozilla.javascript.Undefined;
049: import org.mozilla.javascript.Wrapper;
050: import java.math.BigDecimal;
051: import java.util.List;
052: import java.util.LinkedList;
053: import java.util.Iterator;
054: import java.util.Map;
055: import java.util.HashMap;
056:
057: /**
058: * @version $Id: ScriptableWidget.java 487681 2006-12-15 21:55:33Z joerg $
059: *
060: */
061: public class ScriptableWidget extends ScriptableObject {
062:
063: final static String WIDGETS_PROPERTY = "__widgets__";
064:
065: Widget delegate;
066: ScriptableWidget formWidget;
067:
068: class ScriptableFormHandler implements FormHandler {
069: public void handleEvent(WidgetEvent widgetEvent) {
070: Widget src = widgetEvent.getSourceWidget();
071: ScriptableWidget w = wrap(src);
072: w.handleEvent(widgetEvent);
073: }
074: }
075:
076: public String getClassName() {
077: return "Widget";
078: }
079:
080: public ScriptableWidget() {
081: }
082:
083: public ScriptableWidget(Object widget) {
084: this .delegate = (Widget) unwrap(widget);
085: if (delegate instanceof Form) {
086: Form form = (Form) delegate;
087: form.setFormHandler(new ScriptableFormHandler());
088: formWidget = this ;
089: Map widgetMap = new HashMap();
090: widgetMap.put(delegate, this );
091: defineProperty(WIDGETS_PROPERTY, widgetMap, DONTENUM
092: | PERMANENT);
093: }
094: }
095:
096: static private Object unwrap(Object obj) {
097: if (obj == Undefined.instance) {
098: return null;
099: }
100: if (obj instanceof Wrapper) {
101: return ((Wrapper) obj).unwrap();
102: }
103: return obj;
104: }
105:
106: private void deleteWrapper(Widget w) {
107: if (delegate instanceof Form) {
108: Map widgetMap = (Map) super .get(WIDGETS_PROPERTY, this );
109: widgetMap.remove(w);
110: }
111: }
112:
113: private ScriptableWidget wrap(Widget w) {
114: if (w == null)
115: return null;
116: if (delegate instanceof Form) {
117: Map widgetMap = (Map) super .get(WIDGETS_PROPERTY, this );
118: ScriptableWidget result = null;
119: result = (ScriptableWidget) widgetMap.get(w);
120: if (result == null) {
121: result = new ScriptableWidget(w);
122: result.formWidget = this ;
123: result.setPrototype(getClassPrototype(this ,
124: getClassName()));
125: result.setParentScope(getParentScope());
126: widgetMap.put(w, result);
127: }
128: return result;
129: } else {
130: return formWidget.wrap(w);
131: }
132: }
133:
134: public boolean has(String id, Scriptable start) {
135: if (delegate != null) {
136: if (!(delegate instanceof Repeater)) {
137: Widget sub = delegate.getWidget(id);
138: if (sub != null) {
139: return true;
140: }
141: }
142: }
143: return super .has(id, start);
144: }
145:
146: public boolean has(int index, Scriptable start) {
147: if (super .has(index, start)) {
148: return true;
149: }
150: if (delegate instanceof Repeater) {
151: Repeater repeater = (Repeater) delegate;
152: return index >= 0 && index < repeater.getSize();
153: }
154: if (delegate instanceof MultiValueField) {
155: Object[] values = (Object[]) delegate.getValue();
156: return index >= 0 && index < values.length;
157: }
158: return false;
159: }
160:
161: public Object get(String id, Scriptable start) {
162: Object result = super .get(id, start);
163: if (result != NOT_FOUND) {
164: return result;
165: }
166: if (delegate != null && !(delegate instanceof Repeater)) {
167: Widget sub = delegate.getWidget(id);
168: if (sub != null) {
169: return wrap(sub);
170: }
171: }
172: return NOT_FOUND;
173: }
174:
175: public Object get(int index, Scriptable start) {
176: Object result = super .get(index, start);
177: if (result != NOT_FOUND) {
178: return result;
179: }
180: if (delegate instanceof Repeater) {
181: Repeater repeater = (Repeater) delegate;
182: if (index >= 0) {
183: int count = index + 1 - repeater.getSize();
184: if (count > 0) {
185: ScriptableWidget[] rows = new ScriptableWidget[count];
186: for (int i = 0; i < count; i++) {
187: rows[i] = wrap(repeater.addRow());
188: }
189: for (int i = 0; i < count; i++) {
190: rows[i].notifyAddRow();
191: }
192: }
193: return wrap(repeater.getRow(index));
194: }
195: } else if (delegate instanceof MultiValueField) {
196: Object[] values = (Object[]) delegate.getValue();
197: if (index >= 0 && index < values.length) {
198: return values[index];
199: }
200: }
201: return NOT_FOUND;
202: }
203:
204: public Object[] getAllIds() {
205: Object[] result = super .getAllIds();
206: return addWidgetIds(result);
207: }
208:
209: public Object[] getIds() {
210: Object[] result = super .getIds();
211: return addWidgetIds(result);
212: }
213:
214: private Object[] addWidgetIds(Object[] result) {
215: if (delegate instanceof ContainerWidget) {
216: Iterator iter = ((ContainerWidget) delegate).getChildren();
217: List list = new LinkedList();
218: for (int i = 0; i < result.length; i++) {
219: list.add(result[i]);
220: }
221: while (iter.hasNext()) {
222: Widget widget = (Widget) iter.next();
223: list.add(widget.getId());
224: }
225: result = list.toArray();
226: }
227: return result;
228: }
229:
230: private void deleteRow(Repeater repeater, int index) {
231: Widget row = repeater.getRow(index);
232: ScriptableWidget s = wrap(row);
233: s.notifyRemoveRow();
234: formWidget.deleteWrapper(row);
235: repeater.removeRow(index);
236: }
237:
238: private void notifyAddRow() {
239: ScriptableWidget repeater = wrap(delegate.getParent());
240: Object prop = getProperty(repeater, "onAddRow");
241: if (prop instanceof Function) {
242: try {
243: Function fun = (Function) prop;
244: Object[] args = new Object[1];
245: Scriptable scope = getTopLevelScope(this );
246: Scriptable this Obj = scope;
247: Context cx = Context.getCurrentContext();
248: args[0] = this ;
249: fun.call(cx, scope, this Obj, args);
250: } catch (Exception exc) {
251: throw Context.reportRuntimeError(exc.getMessage());
252: }
253: }
254: }
255:
256: private void notifyRemoveRow() {
257: ScriptableWidget repeater = wrap(delegate.getParent());
258: Object prop = getProperty(repeater, "onRemoveRow");
259: if (prop instanceof Function) {
260: try {
261: Function fun = (Function) prop;
262: Object[] args = new Object[1];
263: Scriptable scope = getTopLevelScope(this );
264: Scriptable this Obj = scope;
265: Context cx = Context.getCurrentContext();
266: args[0] = this ;
267: fun.call(cx, scope, this Obj, args);
268: } catch (Exception exc) {
269: throw Context.reportRuntimeError(exc.getMessage());
270: }
271: }
272: }
273:
274: public void delete(int index) {
275: if (delegate instanceof Repeater) {
276: Repeater repeater = (Repeater) delegate;
277: if (index >= 0 && index < repeater.getSize()) {
278: deleteRow(repeater, index);
279: return;
280: }
281: } else if (delegate instanceof MultiValueField) {
282: MultiValueField field = (MultiValueField) delegate;
283: Object[] values = (Object[]) field.getValue();
284: if (values != null && values.length > index) {
285: Object[] newValues = new Object[values.length - 1];
286: int i;
287: for (i = 0; i < index; i++) {
288: newValues[i] = values[i];
289: }
290: i++;
291: for (; i < values.length; i++) {
292: newValues[i - 1] = values[i];
293: }
294: field.setValues(newValues);
295: }
296: return;
297: }
298: super .delete(index);
299: }
300:
301: public Object jsGet_value() {
302: return delegate.getValue();
303: }
304:
305: public Object jsFunction_getValue() {
306: return jsGet_value();
307: }
308:
309: public void jsFunction_setValue(Object value)
310: throws JavaScriptException {
311: jsSet_value(value);
312: }
313:
314: public void jsSet_length(int len) {
315: if (delegate instanceof Repeater) {
316: Repeater repeater = (Repeater) delegate;
317: int size = repeater.getSize();
318: if (size > len) {
319: while (repeater.getSize() > len) {
320: deleteRow(repeater, repeater.getSize() - 1);
321: }
322: } else {
323: for (int i = size; i < len; ++i) {
324: wrap(repeater.addRow()).notifyAddRow();
325: }
326: }
327: }
328: }
329:
330: public Object jsGet_length() {
331: if (delegate instanceof Repeater) {
332: Repeater repeater = (Repeater) delegate;
333: return new Integer(repeater.getSize());
334: }
335: return Undefined.instance;
336: }
337:
338: public void jsSet_value(Object value) throws JavaScriptException {
339: if (delegate instanceof DataWidget) {
340: value = unwrap(value);
341: if (value != null) {
342: Datatype datatype = ((DataWidget) delegate)
343: .getDatatype();
344: Class typeClass = datatype.getTypeClass();
345: if (typeClass == String.class) {
346: value = Context.toString(value);
347: } else if (typeClass == boolean.class
348: || typeClass == Boolean.class) {
349: value = Context.toBoolean(value) ? Boolean.TRUE
350: : Boolean.FALSE;
351: } else {
352: if (value instanceof Double) {
353: // make woody accept a JS Number
354: if (typeClass == long.class
355: || typeClass == Long.class) {
356: value = new Long(((Number) value)
357: .longValue());
358: } else if (typeClass == int.class
359: || typeClass == Integer.class) {
360: value = new Integer(((Number) value)
361: .intValue());
362: } else if (typeClass == float.class
363: || typeClass == Float.class) {
364: value = new Float(((Number) value)
365: .floatValue());
366: } else if (typeClass == short.class
367: || typeClass == Short.class) {
368: value = new Short(((Number) value)
369: .shortValue());
370: } else if (typeClass == BigDecimal.class) {
371: value = new BigDecimal(((Number) value)
372: .doubleValue());
373: }
374: }
375: }
376: }
377: delegate.setValue(value);
378: } else if (delegate instanceof BooleanField) {
379: BooleanField field = (BooleanField) delegate;
380: field.setValue(new Boolean(Context.toBoolean(value)));
381: } else if (delegate instanceof Repeater) {
382: Repeater repeater = (Repeater) delegate;
383: if (value instanceof NativeArray) {
384: NativeArray arr = (NativeArray) value;
385: Object length = getProperty(arr, "length");
386: int len = ((Number) length).intValue();
387: for (int i = repeater.getSize(); i >= len; --i) {
388: deleteRow(repeater, i);
389: }
390: for (int i = 0; i < len; i++) {
391: Object elemValue = getProperty(arr, i);
392: ScriptableWidget wid = wrap(repeater.getRow(i));
393: wid.jsSet_value(elemValue);
394: }
395: }
396: } else if (delegate instanceof AggregateField) {
397: AggregateField aggregateField = (AggregateField) delegate;
398: if (value instanceof Scriptable) {
399: Scriptable obj = (Scriptable) value;
400: Object[] ids = obj.getIds();
401: for (int i = 0; i < ids.length; i++) {
402: String id = String.valueOf(ids[i]);
403: Object val = getProperty(obj, id);
404: ScriptableWidget wid = wrap(aggregateField
405: .getWidget(id));
406: if (wid == null) {
407: throw new JavaScriptException("No field \""
408: + id + "\" in widget \""
409: + aggregateField.getId() + "\"");
410: }
411: if (wid.delegate instanceof Field
412: || wid.delegate instanceof BooleanField
413: || wid.delegate instanceof Output) {
414: if (val instanceof Scriptable) {
415: Scriptable s = (Scriptable) val;
416: if (s.has("value", s)) {
417: wid.jsSet_value(s.get("value", s));
418: }
419: }
420: } else {
421: wid.jsSet_value(val);
422: }
423: }
424: }
425: } else if (delegate instanceof Repeater.RepeaterRow) {
426: Repeater.RepeaterRow row = (Repeater.RepeaterRow) delegate;
427: if (value instanceof Scriptable) {
428: Scriptable obj = (Scriptable) value;
429: Object[] ids = obj.getIds();
430: for (int i = 0; i < ids.length; i++) {
431: String id = String.valueOf(ids[i]);
432: Object val = getProperty(obj, id);
433: ScriptableWidget wid = wrap(row.getWidget(id));
434: if (wid == null) {
435: throw new JavaScriptException("No field \""
436: + id + "\" in row " + i
437: + " of repeater \""
438: + row.getParent().getId() + "\"");
439: }
440: if (wid.delegate instanceof Field
441: || wid.delegate instanceof BooleanField
442: || wid.delegate instanceof Output) {
443: if (val instanceof Scriptable) {
444: Scriptable s = (Scriptable) val;
445: if (s.has("value", s)) {
446: wid.jsSet_value(s.get("value", s));
447: }
448: }
449: } else {
450: wid.jsSet_value(val);
451: }
452: }
453: } else {
454: throw new JavaScriptException(
455: "Expected an object instead of: "
456: + Context.toString(value));
457: }
458: } else if (delegate instanceof MultiValueField) {
459: MultiValueField field = (MultiValueField) delegate;
460: Object[] values = null;
461: if (value instanceof NativeArray) {
462: NativeArray arr = (NativeArray) value;
463: Object length = getProperty(arr, "length");
464: int len = ((Number) length).intValue();
465: values = new Object[len];
466: for (int i = 0; i < len; i++) {
467: Object elemValue = getProperty(arr, i);
468: values[i] = unwrap(elemValue);
469: }
470: } else if (value instanceof Object[]) {
471: values = (Object[]) value;
472: }
473: field.setValues(values);
474: } else {
475: delegate.setValue(value);
476: }
477: }
478:
479: public String jsFunction_getId() {
480: return delegate.getId();
481: }
482:
483: public ScriptableWidget jsFunction_getSubmitWidget() {
484: return wrap(delegate.getForm().getSubmitWidget());
485: }
486:
487: public String jsFunction_getFullyQualifiedId() {
488: return delegate.getFullyQualifiedId();
489: }
490:
491: public String jsFunction_getNamespace() {
492: return delegate.getNamespace();
493: }
494:
495: public Object jsFunction_getParent() {
496: if (delegate != null) {
497: return wrap(delegate.getParent());
498: }
499: return Undefined.instance;
500: }
501:
502: public boolean jsFunction_isRequired() {
503: return delegate.isRequired();
504: }
505:
506: public ScriptableWidget jsFunction_getForm() {
507: return formWidget;
508: }
509:
510: public boolean jsFunction_equals(Object other) {
511: if (other instanceof ScriptableWidget) {
512: ScriptableWidget otherWidget = (ScriptableWidget) other;
513: return delegate.equals(otherWidget.delegate);
514: }
515: return false;
516: }
517:
518: public ScriptableWidget jsFunction_getWidget(String id) {
519: Widget sub = delegate.getWidget(id);
520: return wrap(sub);
521: }
522:
523: public void jsFunction_setValidationError(String message,
524: Object parameters) {
525: if (delegate instanceof ValidationErrorAware) {
526: String[] parms = null;
527: if (parameters != null && parameters != Undefined.instance) {
528: Scriptable obj = Context.toObject(parameters, this );
529: int len = (int) Context.toNumber(getProperty(obj,
530: "length"));
531: parms = new String[len];
532: for (int i = 0; i < len; i++) {
533: parms[i] = Context.toString(getProperty(obj, i));
534: }
535: }
536: ValidationError validationError = null;
537: if (message != null) {
538: if (parms != null && parms.length > 0) {
539: validationError = new ValidationError(message,
540: parms);
541: } else {
542: validationError = new ValidationError(message,
543: parms != null);
544: }
545: }
546: ((ValidationErrorAware) delegate)
547: .setValidationError(validationError);
548: formWidget.notifyValidationErrorListener(this ,
549: validationError);
550: }
551: }
552:
553: private void notifyValidationErrorListener(ScriptableWidget widget,
554: ValidationError error) {
555: Object fun = getProperty(this , "validationErrorListener");
556: if (fun instanceof Function) {
557: try {
558: Scriptable scope = getTopLevelScope(this );
559: Scriptable this Obj = scope;
560: Context cx = Context.getCurrentContext();
561: Object[] args = new Object[2];
562: args[0] = widget;
563: args[1] = error;
564: ((Function) fun).call(cx, scope, this Obj, args);
565: } catch (Exception exc) {
566: throw Context.reportRuntimeError(exc.getMessage());
567: }
568: }
569: }
570:
571: public Widget jsFunction_unwrap() {
572: return delegate;
573: }
574:
575: public ScriptableWidget jsFunction_addRow() {
576: ScriptableWidget result = null;
577: if (delegate instanceof Repeater) {
578: result = wrap(((Repeater) delegate).addRow());
579: result.notifyAddRow();
580: }
581: return result;
582: }
583:
584: public ScriptableObject jsFunction_getRow(int index) {
585: if (delegate instanceof Repeater) {
586: return wrap(((Repeater) delegate).getRow(index));
587: }
588: return null;
589: }
590:
591: public void jsFunction_removeRow(Object obj)
592: throws JavaScriptException {
593: if (delegate instanceof Repeater) {
594: Repeater repeater = (Repeater) delegate;
595: if (obj instanceof Function) {
596: Function fun = (Function) obj;
597: int len = repeater.getSize();
598: boolean[] index = new boolean[len];
599: Object[] args = new Object[1];
600: Scriptable scope = getTopLevelScope(this );
601: Scriptable this Obj = scope;
602: Context cx = Context.getCurrentContext();
603: for (int i = 0; i < len; i++) {
604: ScriptableWidget row = wrap(repeater.getRow(i));
605: args[0] = row;
606: Object result = fun.call(cx, scope, this Obj, args);
607: index[i] = Context.toBoolean(result);
608: }
609: for (int i = len - 1; i >= 0; --i) {
610: if (index[i]) {
611: deleteRow(repeater, i);
612: }
613: }
614: } else if (obj instanceof Number) {
615: int index = (int) Context.toNumber(obj);
616: if (index > 0 && index < repeater.getSize()) {
617: deleteRow(repeater, index);
618: }
619: } else {
620: //...
621: }
622: }
623: }
624:
625: private void handleEvent(WidgetEvent e) {
626: if (e instanceof ActionEvent) {
627: Object obj = super .get("onClick", this );
628: if (obj instanceof Function) {
629: try {
630: Function fun = (Function) obj;
631: Object[] args = new Object[1];
632: Scriptable scope = getTopLevelScope(this );
633: Scriptable this Obj = scope;
634: Context cx = Context.getCurrentContext();
635: args[0] = ((ActionEvent) e).getActionCommand();
636: fun.call(cx, scope, this Obj, args);
637: } catch (Exception exc) {
638: throw Context.reportRuntimeError(exc.getMessage());
639: }
640: }
641: } else if (e instanceof ValueChangedEvent) {
642: ValueChangedEvent vce = (ValueChangedEvent) e;
643: Object obj = super .get("onChange", this );
644: if (obj instanceof Function) {
645: try {
646: Function fun = (Function) obj;
647: Object[] args = new Object[2];
648: Scriptable scope = getTopLevelScope(this );
649: Scriptable this Obj = scope;
650: Context cx = Context.getCurrentContext();
651: args[0] = vce.getOldValue();
652: args[1] = vce.getNewValue();
653: fun.call(cx, scope, this Obj, args);
654: } catch (Exception exc) {
655: throw Context.reportRuntimeError(exc.getMessage());
656: }
657: }
658: }
659: }
660:
661: public void jsFunction_setSelectionList(Object arg,
662: Object valuePathArg, Object labelPathArg) throws Exception {
663: if (delegate instanceof SelectableWidget) {
664: arg = unwrap(arg);
665: if (valuePathArg != Undefined.instance
666: && labelPathArg != Undefined.instance) {
667: String valuePath = Context.toString(valuePathArg);
668: String labelPath = Context.toString(labelPathArg);
669: ((SelectableWidget) delegate).setSelectionList(arg,
670: valuePath, labelPath);
671: } else {
672: if (arg instanceof SelectionList) {
673: SelectionList selectionList = (SelectionList) arg;
674: ((SelectableWidget) delegate)
675: .setSelectionList(selectionList);
676: } else {
677: String str = Context.toString(arg);
678: ((SelectableWidget) delegate).setSelectionList(str);
679: }
680: }
681: }
682: }
683:
684: static final Object[] WIDGET_CLASS_MAP = { Form.class, "Form",
685: Field.class, "Field", Action.class, "Action",
686: Repeater.class, "Repeater", Repeater.RepeaterRow.class,
687: "RepeaterRow", AggregateField.class, "AggregateField",
688: BooleanField.class, "BooleanField", MultiValueField.class,
689: "MultiValueField", Output.class, "Output", Submit.class,
690: "Submit", Upload.class, "Upload" };
691:
692: public String jsFunction_getWidgetClass() {
693: for (int i = 0; i < WIDGET_CLASS_MAP.length; i += 2) {
694: Class c = (Class) WIDGET_CLASS_MAP[i];
695: if (c.isAssignableFrom(delegate.getClass())) {
696: return (String) WIDGET_CLASS_MAP[i + 1];
697: }
698: }
699: return "<unknown>";
700: }
701:
702: public String jsFunction_toString() {
703: return "[object Widget (" + jsFunction_getWidgetClass() + ")]";
704: }
705:
706: }
|