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