001    package org.tynamo.components.internal;
002    
003    import org.apache.tapestry5.*;
004    import org.apache.tapestry5.annotations.*;
005    import org.apache.tapestry5.beaneditor.BeanModel;
006    import org.apache.tapestry5.corelib.components.Form;
007    import org.apache.tapestry5.corelib.components.Zone;
008    import org.apache.tapestry5.dom.Element;
009    import org.apache.tapestry5.internal.TapestryInternalUtils;
010    import org.apache.tapestry5.ioc.Messages;
011    import org.apache.tapestry5.ioc.annotations.Inject;
012    import org.apache.tapestry5.ioc.services.PropertyAccess;
013    import org.apache.tapestry5.services.*;
014    import org.tynamo.builder.BuilderDirector;
015    import org.tynamo.descriptor.CollectionDescriptor;
016    import org.tynamo.services.DescriptorService;
017    import org.tynamo.services.PersistenceService;
018    import org.tynamo.util.DisplayNameUtils;
019    
020    import java.util.Collection;
021    import java.util.List;
022    
023    /**
024     * Copy of org.tynamo.components.Composition designed to be used from Block pages (i.e: PropertyDisplayBlocks) This
025     * class is for internal use only.
026     */
027    public class Composition
028    {
029    
030            @Inject
031            private DescriptorService descriptorService;
032    
033            @Inject
034            private PersistenceService persitenceService;
035    
036            @Inject
037            private PropertyAccess propertyAccess;
038    
039            @Inject
040            private BeanModelSource beanModelSource;
041    
042            @Inject
043            private Messages messages;
044    
045            @Inject
046            private BuilderDirector builderDirector;
047    
048            @Inject
049            private ComponentResources resources;
050    
051            @Inject
052            private ValueEncoderSource valueEncoderSource;
053    
054            @Inject
055            private Environment environment;
056    
057            @Inject
058            private ContextValueEncoder contextValueEncoder;
059    
060            @Inject
061            private Request request;
062    
063            @Environmental
064            private Heartbeat heartbeat;
065    
066            /**
067             * The id used to generate a page-unique client-side identifier for the component. If a component renders multiple
068             * times, a suffix will be appended to the to id to ensure uniqueness. The uniqued value may be accessed via the
069             * clientId property.
070             */
071            @Parameter(value = "prop:resources.id", defaultPrefix = BindingConstants.LITERAL)
072            @Property(write = false)
073            private String clientId;
074    
075            /**
076             * A Block to render when the source is empty. The default is simply the text "There is no data to display". This
077             * parameter is used to customize that message.
078             */
079            @Parameter(value = "block:empty", defaultPrefix = BindingConstants.LITERAL)
080            @Property(write = false)
081            private Block empty;
082    
083            @Parameter(required = false)
084            private List instances;
085    
086            @Parameter(required = true)
087            @Property(write = false)
088            private Collection collection;
089    
090            /**
091             * The object which owns the collection being edited
092             */
093            @Parameter(name = "owner", required = true)
094            private Object ownerParameter;
095    
096            private Object owner;
097    
098            /**
099             * Ognl expression to invoke on the model to create a new child instance
100             */
101            @Parameter(required = false)
102            private String createExpression;
103    
104            /**
105             * @return The CollectionDescriptor for the collection being edited
106             */
107            @Property(write = false)
108            private CollectionDescriptor collectionDescriptor;
109    
110            @Property
111            private Object collectionIterator;
112    
113            @Property
114            private Object formBean;
115    
116            @Parameter(value = "true")
117            private boolean allowCreate;
118    
119            @InjectComponent
120            private Zone compositionZone;
121    
122            @InjectComponent
123            @Property(write = false)
124            private Form form;
125    
126            @Parameter(value = "prop:collectionDescriptor.allowRemove")
127            private boolean allowRemove;
128    
129            @Property
130            private int index;
131    
132            @Parameter(value = "asset:../move_up.gif")
133            @Property(write = false)
134            private Asset upImage;
135    
136            @Parameter(value = "asset:../move_down.gif")
137            @Property(write = false)
138            private Asset downImage;
139    
140            @Parameter(required = true)
141            private String property;
142    
143            /**
144             * The image to use for the delete icon
145             */
146            @Parameter(value = "asset:../delete.png")
147            @Property(write = false)
148            private Asset deleteIcon;
149    
150            @Property(write = false)
151            private BeanModel beanModel;
152    
153            private Element addLink;
154    
155            public boolean isAllowCreate()
156            {
157                    return propertyAccess.get(owner, descriptorService.getClassDescriptor(owner.getClass()).getIdentifierDescriptor().getName()) != null;
158            }
159    
160            private void activate(Object owner, String property)
161            {
162                    this.owner = owner;
163                    collectionDescriptor = (CollectionDescriptor) descriptorService.getClassDescriptor(owner.getClass()).getPropertyDescriptor(property);
164                    beanModel = beanModelSource.createEditModel(collectionDescriptor.getElementType(), messages);
165            }
166    
167            void onPrepareFromForm()
168            {
169                    resources.triggerEvent(EventConstants.PREPARE, null, null);
170    
171                    if (beanModel == null)
172                    {
173                            beanModel = beanModelSource.createEditModel(collectionDescriptor.getElementType(), messages);
174                    }
175            }
176    
177            void setupRender()
178            {
179                    activate(ownerParameter, property);
180                    formBean = builderDirector.createNewInstance(collectionDescriptor.getElementType());
181                    pushPropertyOutputContextIntoEnvironment(property);
182            }
183    
184            boolean beginRender(MarkupWriter writer)
185            {
186    
187                    writer.element("div", "class", "t-add-child");
188                    addLink = writer.element("a", "id", "add-" + property + "-link", "href", "#", "class", "t-add-child-link");
189                    writer.write(messages.get("org.tynamo.i18n.add-child"));
190    
191                    Runnable command = new Runnable()
192                    {
193                            public void run()
194                            {
195                                    String fieldId = form.getClientId();
196                                    addLink.forceAttributes("onclick", "Element.toggle('" + form.getClientId() + "'); this.blur(); return false;");
197                            }
198                    };
199                    heartbeat.defer(command);
200    
201                    writer.end();
202                    writer.end();
203                    
204                    return true;
205            }
206    
207            public Object[] getShowPageContext()
208            {
209                    return new Object[]{collectionIterator.getClass(), collectionIterator};
210            }
211    
212            public String getLegendMessage()
213            {
214                    return messages.format("org.tynamo.i18n.add",
215                                    DisplayNameUtils.getDisplayName(collectionDescriptor.getElementType(), messages));
216            }
217    
218            public Object[] getFormContext()
219            {
220                    return new Object[]{owner.getClass(), owner, collectionDescriptor.getName()};
221            }
222    
223    
224            void onPrepareForSubmitFromForm(final Class clazz, String id, final String property)
225            {
226                    activate(contextValueEncoder.toValue(clazz, id), property);
227                    pushPropertyOutputContextIntoEnvironment(property);
228            }
229    
230            private void pushPropertyOutputContextIntoEnvironment(final String property)
231            {
232                    PropertyOutputContext context = new PropertyOutputContext()
233                    {
234                            public Messages getMessages()
235                            {
236                                    return messages;
237                            }
238    
239                            public Object getPropertyValue()
240                            {
241                                    return propertyAccess.get(owner, property);
242                            }
243    
244                            public String getPropertyId()
245                            {
246                                    return TapestryInternalUtils.extractIdFromPropertyExpression(property);
247                            }
248    
249                            public String getPropertyName()
250                            {
251                                    return property;
252                            }
253                    };
254    
255                    environment.push(PropertyOutputContext.class, context);
256            }
257    
258            public Object onSuccess()
259            {
260                    persitenceService.addToCollection(collectionDescriptor, formBean, owner);
261    
262                    if (request.isXHR()) return compositionZone.getBody();
263                    else return null;
264            }
265    
266            public Object[] getDeleteContext()
267            {
268                    return new Object[]{owner.getClass(), owner, collectionDescriptor.getName(), collectionIterator};
269            }
270    
271            public Object onActionFromDeleteChild(final Class clazz, String id, final String property, final String elementid)
272            {
273                    activate(contextValueEncoder.toValue(clazz, id), property);
274    
275                    ValueEncoder valueEncoder = valueEncoderSource.getValueEncoder(collectionDescriptor.getElementType());
276                    Object element = valueEncoder.toValue(elementid);
277    
278                    persitenceService.removeFromCollection(collectionDescriptor, element, owner);
279    
280                    pushPropertyOutputContextIntoEnvironment(property);
281    
282                    if (request.isXHR()) return compositionZone.getBody();
283                    else return null;
284            }
285    
286            @Log
287            void cleanupRender()
288            {
289                    environment.pop(PropertyOutputContext.class);
290            }
291    
292            public String getCompositionZoneClientId()
293            {
294                    return compositionZone.getClientId();
295            }
296    
297    }