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 }