View Javadoc

1   /**
2    * This file is part of the equanda project.
3    *
4    * The contents of this file are subject to the Mozilla Public License Version 1.1 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at http://www.mozilla.org/MPL/
7    *
8    * Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF
9    * ANY KIND, either express or implied. See the License for the specific language governing rights and
10   * limitations under the License.
11   *
12   * Alternatively, the contents of this file may be used under the terms of
13   * either the GNU General Public License Version 2 or later (the "GPL"), or
14   * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
15   * in which case the provisions of the GPL or the LGPL are applicable instead
16   * of those above. If you wish to allow use of your version of this file only
17   * under the terms of either the GPL or the LGPL, and not to allow others to
18   * use your version of this file under the terms of the MPL, indicate your
19   * decision by deleting the provisions above and replace them with the notice
20   * and other provisions required by the GPL or the LGPL. If you do not delete
21   * the provisions above, a recipient may use your version of this file under
22   * the terms of any one of the MPL, the GPL or the LGPL.
23   */
24  
25  package org.equanda.tapestry5.components;
26  
27  import org.apache.tapestry.commons.components.Hidden;
28  import org.apache.tapestry5.*;
29  import org.apache.tapestry5.annotations.*;
30  import org.apache.tapestry5.corelib.components.Delegate;
31  import org.apache.tapestry5.corelib.components.Loop;
32  import org.apache.tapestry5.ioc.annotations.Inject;
33  import org.apache.tapestry5.services.ComponentDefaultProvider;
34  import org.apache.tapestry5.services.Environment;
35  import org.apache.tapestry5.services.FormSupport;
36  import org.apache.tapestry5.services.Request;
37  import org.equanda.tapestry5.base.ObjectCreatorDelegate;
38  import org.slf4j.Logger;
39  
40  import java.util.ArrayList;
41  import java.util.Collection;
42  import java.util.List;
43  
44  /**
45   * Paged Loop component with Java Script Pager
46   *
47   * @author <a href="mailto:vladimir.tkachenko@gmail.com">Vladimir Tkachenko</a>
48   */
49  public class JSPagedLoop
50      implements ClientElement
51  {
52      @Environmental
53      private RenderSupport renderSupport;
54  
55      @Inject
56      private Environment environment;
57  
58      @Inject
59      private Logger logger;
60  
61      @Inject
62      private ComponentResources resources;
63  
64      @Environmental
65      private FormSupport formSupport;
66  
67      @Inject
68      private Request request;
69  
70      @Inject
71      private ComponentDefaultProvider componentDefaultProvider;
72  
73      @Parameter( value = "prop:componentResources.id", defaultPrefix = BindingConstants.LITERAL )
74      private String clientId;
75  
76      /**
77       * The element to render. If not null, then the loop will render the indicated element around its body (on each pass
78       * through the loop). The default is derived from the component template.
79       */
80      @Parameter( value = "prop:componentResources.elementName", defaultPrefix = BindingConstants.LITERAL )
81      private String elementName;
82  
83      /**
84       * Defines the collection of values for the loop to iterate over.
85       */
86      @Parameter( required = true, principal = true )
87      private Collection<Object> source;
88  
89      @Persist
90      private List<Object> addedRowsList;
91  
92      @Persist
93      private List<Object> pagedSource;
94  
95      /**
96       * The number of rows of data displayed on each page. If there are more rows than will fit, the Grid will divide up
97       * the rows into "pages" and (normally) provide a pager to allow the user to navigate within the overall result set.
98       */
99      @Parameter( "25" )
100     private int rowsPerPage;
101 
102     @Persist
103     private int currentPage = 1;
104 
105     // @Persist
106     private int addedRowCount;
107 
108     /**
109      * The current value, set before the component renders its body.
110      */
111     @SuppressWarnings( "unused" )
112     @Parameter
113     private Object value;
114 
115     /**
116      * If true and the Loop is enclosed by a Form, then the normal state saving logic is turned off. Defaults to false,
117      * enabling state saving logic within Forms.
118      */
119     @SuppressWarnings( "unused" )
120     @Parameter
121     private boolean _volatile;
122 
123     @Parameter( defaultPrefix = BindingConstants.PROP )
124     private ObjectCreatorDelegate objectCreatorDelegate;
125 
126     /**
127      * The index into the source items.
128      */
129     @Parameter
130     private int index;
131 
132     @Parameter
133     private int additionalRowCount;
134 
135     /**
136      * Optional primary key converter; if provided and inside a form and not volatile, then each iterated value is
137      * converted and stored into the form.
138      */
139     @SuppressWarnings( "unused" )
140     @Parameter
141     private PrimaryKeyEncoder<?, ?> encoder;
142 
143     @SuppressWarnings( "unused" )
144     @Component( parameters = { "source=pagedSource", "elementName=prop:elementName", "value=inherit:value",
145             "volatile=inherit:volatile", "encoder=inherit:encoder", "index=index",
146             "additionalRowCount=inherit:additionalRowCount" } )
147     private Loop loop;
148 
149     @Component( parameters = { "source=pagedSource", "rowsPerPage=rowsPerPage", "currentPage=currentPage" } )
150     private JSPager internalPager;
151 
152     @Component( id = "CurrentPageHidden", parameters = { "value=currentPage", "translate=translate:integer" } )
153     private Hidden currentPageHidden;
154 
155     @Component( id = "AddedRowsHidden", parameters = { "value=addedRowCount", "translate=translate:integer" } )
156     private Hidden addedRowHidden;
157 
158     @SuppressWarnings( "unused" )
159     @Component( parameters = "to=pagerBottom" )
160     private Delegate pagerBottom;
161 
162     /**
163      * A Block to render instead of the table (and pager, etc.) when the source is empty. The default is simply the text
164      * "There is no data to display". This parameter is used to customize that message, possibly including components to
165      * allow the user to create new objects.
166      */
167     @Parameter( value = "block:empty" )
168     private Block empty;
169 
170     @Parameter( value = "block:bottomBlock" )
171     private Block bottomBlock;
172 
173     @Parameter( value = "message:add.title.label", defaultPrefix = BindingConstants.LITERAL )
174     private String addRowLinkTitle;
175 
176     private String assignedClientId;
177 
178     private List<String> rowIds;
179 
180     private String addedRowCountHiddenName;
181 
182     static class FormSubmitAction
183         implements ComponentAction<JSPagedLoop>
184     {
185 
186         private static final long serialVersionUID = 456056528334001727L;
187 
188         public void execute( JSPagedLoop component )
189         {
190             component.processFormAction();
191         }
192     }
193 
194     static class SetupAction
195         implements ComponentAction<JSPagedLoop>
196     {
197 
198         private static final long serialVersionUID = 6727709013577574475L;
199         private final String _addedRowCountHiddenName;
200 
201         public SetupAction ( String controlName )
202         {
203             _addedRowCountHiddenName = controlName;
204         }
205 
206         public void execute( JSPagedLoop component )
207         {
208             component.setupAddedRowCountHiddenName( _addedRowCountHiddenName );
209         }
210     }
211 
212     private static final FormSubmitAction FORM_SUBMIT_ACTION = new FormSubmitAction();
213 
214     private void setupAddedRowCountHiddenName( String name )
215     {
216         addedRowCountHiddenName = name;
217     }
218 
219     Binding defaultSource()
220     {
221         return componentDefaultProvider.defaultBinding( "source", resources );
222     }
223 
224     void setSource( Collection<Object> source )
225     {
226         this.source = source;
227     }
228 
229     public String getAddRowLinkTitle()
230     {
231         return addRowLinkTitle;
232     }
233 
234     public int getIndex()
235     {
236         return index;
237     }
238 
239     public void setIndex( int index )
240     {
241         this.index = index;
242     }
243 
244     public String getElementName()
245     {
246         return elementName;
247     }
248 
249     public Block getBottomBlock()
250     {
251         return bottomBlock;
252     }
253 
254     public Object getPagerBottom()
255     {
256         return internalPager;
257     }
258 
259     public List<Object> getPagedSource()
260     {
261         return pagedSource;
262     }
263 
264     public int getRowsPerPage()
265     {
266         return rowsPerPage;
267     }
268 
269     public void setRowsPerPage( int rowsPerPage )
270     {
271         this.rowsPerPage = rowsPerPage;
272     }
273 
274     public int getCurrentPage()
275     {
276         return currentPage;
277     }
278 
279     public void setCurrentPage( int currentPage )
280     {
281         this.currentPage = currentPage;
282     }
283 
284     public int getAddedRowCount()
285     {
286         return addedRowCount;
287     }
288 
289     public void setAddedRowCount( int addedRowCount )
290     {
291         this.addedRowCount = addedRowCount;
292     }
293 
294     public int getAdditionalRowCount()
295     {
296         return additionalRowCount;
297     }
298 
299     public void setAdditionalRowCount( int additionalRowCount )
300     {
301         this.additionalRowCount = additionalRowCount;
302     }
303 
304     public String getCurrentRowId()
305     {
306         String newId = renderSupport.allocateClientId( assignedClientId );
307         String rowId = String.format( "%s_row_%s", assignedClientId, newId.toLowerCase() );
308         rowIds.add( rowId );
309         return rowId;
310     }
311 
312     public String getCurrentPageFieldName()
313     {
314         return currentPageHidden.getClientId();
315     }
316 
317     public String getAddedRowFieldName()
318     {
319         return addedRowHidden.getClientId();
320     }
321 
322     @SetupRender
323     Object setupRender()
324     {
325         rowIds = new ArrayList<String>();
326         assignedClientId = renderSupport.allocateClientId( clientId );
327 
328         addedRowsList = new ArrayList<Object>();
329         pagedSource = new ArrayList<Object>();
330 
331         if ( source != null )
332         {
333             for ( Object item : source )
334             {
335                 pagedSource.add( item );
336             }
337         }
338 
339         if ( objectCreatorDelegate != null )
340         {
341             for ( int i = 0; i < additionalRowCount; i++ )
342             {
343                 Object object = objectCreatorDelegate.createObject();
344                 pagedSource.add( object );
345                 addedRowsList.add( object );
346             }
347         }
348 
349         // If there's no rows, display the empty block placeholder.
350         if ( pagedSource.size() == 0 )
351         {
352             return empty;
353         }
354 
355         return null;
356     }
357 
358     @BeginRender
359     Object begin( MarkupWriter writer )
360     {
361         environment.push( JSPagedLoop.class, this );
362 
363         // Skip rendering of component (template, body, etc.) when there's nothing to display.
364         // The empty placeholder will already have rendered.
365         return pagedSource.size() != 0;
366 
367     }
368 
369     @AfterRender
370     void afterRender()
371     {
372         formSupport.storeAndExecute( this, new SetupAction( addedRowHidden.getControlName() ) );
373         formSupport.store( this, FORM_SUBMIT_ACTION );
374     }
375 
376     @CleanupRender
377     void cleanupRender()
378     {
379         environment.pop( JSPagedLoop.class );
380     }
381 
382     void onActionFromAdd()
383     {/*nothing*/}
384 
385     private void processFormAction()
386     {
387         int count = 0;
388         String sCount = null;
389         try
390         {
391             if ( addedRowCountHiddenName != null )
392             {
393                 sCount = request.getParameter( addedRowCountHiddenName );
394                 if ( sCount != null && sCount.trim().length() > 0 )
395                 {
396                     count = Integer.parseInt( sCount.trim() );
397                 }
398             }
399         }
400         catch ( Exception e )
401         {
402             logger.error( "Unable to parse AddedRowCount parameter: " + sCount );
403         }
404 
405         if ( objectCreatorDelegate != null )
406         {
407             for ( int i = 0; i < count && i < addedRowsList.size(); i++ )
408             {
409                 objectCreatorDelegate.addNewObject( addedRowsList.get( i ) );
410             }
411         }
412         addedRowsList.clear();
413         addedRowCount = count;
414     }
415 
416     /**
417      * Returns a unique id for the element. This value will be unique for any given rendering of a page. This value is
418      * intended for use as the id attribute of the client-side element, and will be used with any DHTML/Ajax related
419      * JavaScript.
420      */
421     public String getClientId()
422     {
423         return assignedClientId;
424     }
425 
426     public String getFixedClientId()
427     {
428         return assignedClientId != null ? assignedClientId.toLowerCase() : "";
429     }
430 
431 }