KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > opensubsystems > patterns > listdata > www > ListTag


1 /*
2  * Copyright (c) 2003 - 2007 OpenSubsystems s.r.o. Slovak Republic. All rights reserved.
3  *
4  * Project: OpenSubsystems
5  *
6  * $Id: ListTag.java,v 1.70 2007/02/01 07:25:20 bastafidli Exp $
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; version 2 of the License.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20  */

21  
22 package org.opensubsystems.patterns.listdata.www;
23
24 import java.util.Iterator JavaDoc;
25 import java.util.List JavaDoc;
26 import java.util.Map JavaDoc;
27 import java.util.Properties JavaDoc;
28 import java.util.logging.Level JavaDoc;
29 import java.util.logging.Logger JavaDoc;
30
31 import javax.servlet.jsp.JspException JavaDoc;
32
33 import org.opensubsystems.core.data.DataObject;
34 import org.opensubsystems.core.data.GenericData;
35 import org.opensubsystems.core.util.ArrayUtils;
36 import org.opensubsystems.core.util.Config;
37 import org.opensubsystems.core.util.GlobalConstants;
38 import org.opensubsystems.core.util.Log;
39 import org.opensubsystems.core.util.StringUtils;
40 import org.opensubsystems.core.www.MessageListTag;
41 import org.opensubsystems.core.www.PageElementCacheTag;
42 import org.opensubsystems.core.www.TagUtils;
43 import org.opensubsystems.patterns.listdata.data.DataCondition;
44 import org.opensubsystems.patterns.listdata.data.ListDefinition;
45 import org.opensubsystems.patterns.listdata.data.ListOptions;
46 import org.opensubsystems.patterns.scrollabletable.www.ScrollableTableTag;
47
48 /**
49  * Custom tag to construct all required elements of scrollable and browsable
50  * table of data objects. It is mainly used by datalist.jsp layout.
51  *
52  * @version $Id: ListTag.java,v 1.70 2007/02/01 07:25:20 bastafidli Exp $
53  * @author Julo Legeny
54  * @code.reviewer Miro Halas
55  * @code.reviewed 1.62 2006/04/14 00:42:30
56  */

57 public abstract class ListTag extends PageElementCacheTag
58 {
59    // Constants ////////////////////////////////////////////////////////////////
60

61    /**
62     * Constant defining length in percentage of the row width that will be used
63     * for checkbox column width if the flag specifying to display checkboxes in
64     * each row is set.
65     */

66    protected static final int CHECKBOX_COLUMN_WIDTH = 2;
67    
68    /**
69     * Function parameter for getting headings.
70     */

71    protected static final String JavaDoc FUNCTION_HEADINGS = "header";
72
73    /**
74     * Function parameter for getting columns.
75     */

76    protected static final String JavaDoc FUNCTION_COLS = "settings";
77    
78    /**
79     * Function parameter for getting init data for sorting
80     */

81    protected static final String JavaDoc FUNCTION_SORT = "sort";
82
83    /**
84     * Function parameter for getting init data for filter
85     */

86    protected static final String JavaDoc FUNCTION_FILTER = "filter";
87
88    /**
89     * Function parameter for getting init data for messages
90     */

91    protected static final String JavaDoc FUNCTION_MESSAGES = "messages";
92
93    /**
94     * Function parameter for getting body.
95     */

96    protected static final String JavaDoc FUNCTION_BODY = "body";
97
98    /**
99     * This definition will be used whenever code specified to use unknown column
100     * so that we do not present error on the screen.
101     */

102    protected static final ColumnDefinition UNKNOWN_COLUMN;
103    
104    // Attributes ///////////////////////////////////////////////////////////////
105

106    /**
107     * Name of the attribute which identifies ListOptions object. Required.
108     */

109    protected String JavaDoc m_strOptions;
110    
111    /**
112     * Name of the attribute which identifies the list of data objects. Required.
113     */

114    protected String JavaDoc m_strData;
115
116    /**
117     * Name of the function (element), which should be created by this tag.
118     * This is one of the FUNCTION_XXX constants defined in this class. Required.
119     */

120    protected String JavaDoc m_strFunction;
121    
122    /**
123     * Flag if the list should contain a checkboxes included in each row. These
124     * checkboxes will be used for selecting particular item (row) in the list.
125     * This attribute should say true or 1. Not required.
126     */

127    protected String JavaDoc m_strCheck;
128
129    /**
130     * Default value for m_strCheck.
131     */

132    protected String JavaDoc m_strCheckDefault;
133
134    /**
135     * Flag if the list should be sorted.
136     * This attribute should say true or 1. Not required.
137     */

138    protected String JavaDoc m_strSortable;
139
140    /**
141     * Default value for m_strSortable.
142     */

143    protected String JavaDoc m_strSortableDefault;
144    
145    /**
146     * Id of the table.
147     * Not required.
148     */

149    protected String JavaDoc m_strTableId;
150
151    /**
152     * Flag if the list should contain a link. Not required.
153     * TODO: Improve: How is this really used?
154     */

155    protected boolean m_bLink;
156
157    /**
158     * Number specified limit for assigned items within the table.
159     * limit = 0 ... no limit
160     * limit = X (X > 0) ... max. X items can be assigned in the list
161     */

162    protected String JavaDoc m_strLimit;
163
164    /**
165     * Identificator of type of data shown in table. This should be constant
166     * that can be safely used to generate JavaScript identificators and should
167     * be also convertible to data type code using the methods in DataConstant
168     * class.
169     */

170    protected String JavaDoc m_strDataTypeName;
171    
172    /**
173     * Array of columns specifying what columns to display in a list by default
174     * and in what order.
175     */

176    protected int[] m_arrDefaultListColumns;
177    
178    /**
179     * Array of columns specifying what columns should be used to sort by default
180     * on gui.
181     */

182    protected int[] m_arrDefaultOrderColumns;
183    
184    /**
185     * Array of ListDefinition.ORDER_XXX constants specifying the default
186     * directions for the default list of columns used for sorting. The array
187     * has to have the same length as the one specified in
188     * m_arrDefaultOrderColumns where the order for column specified at index i
189     * will be specified at the same index.
190     */

191    protected String JavaDoc[] m_arrDefaultOrderDirections;
192    
193    /**
194     * Map defining what columns are available for this list. Key is Integer
195     * column code and value is ColumnDefinition describing that column.
196     */

197    protected Map JavaDoc m_mapColumnDefinitions;
198    
199    // Cached values ////////////////////////////////////////////////////////////
200

201    /**
202     * Logger for this class
203     */

204    private static Logger JavaDoc s_logger = Log.getInstance(ListTag.class);
205
206    // Constructors /////////////////////////////////////////////////////////////
207

208    /**
209     * Static initializer
210     */

211    static
212    {
213       UNKNOWN_COLUMN = new ColumnDefinition("Unknown", "Unknown column",
214                                             DataCondition.VALUE_TYPE_UNKNOWN,
215                                             1);
216    }
217    
218    /**
219     * Constructor for custom tag.
220     *
221     * @param strDataTypeName - id of type of data shown in table. This should be
222     * constant that can be safely used to generate
223     * JavaScript identificators and should be also
224     * convertible to data type code using the methods
225     * in DataConstant class.
226     * @param arrDefaultListColumns - array of columns specifying what columns to
227     * display in a list by default and in what
228     * order.
229     * @param arrDefaultOrderColumns - array of columns specifying what columns
230     * should be used to sort by default on gui.
231     * @param arrDefaultOrderDirections - array of ListDefinition.ORDER_XXX
232     * constants specifying the default
233     * directions for the default list of
234     * columns used for sorting. The array has
235     * to have the same length as the one
236     * specified in arrDefaultOrderColumns
237     * where the order for column specified at
238     * index i will be specified at the same
239     * index.
240     * @param mapColumnDefinitions - map of column definition where key is the
241     * identification of the column (Integer constant)
242     * and value is ColumnDefinition object.
243     */

244    public ListTag(
245       String JavaDoc strDataTypeName,
246       int[] arrDefaultListColumns,
247       int[] arrDefaultOrderColumns,
248       String JavaDoc[] arrDefaultOrderDirections,
249       Map JavaDoc mapColumnDefinitions
250    )
251    {
252       super();
253
254       m_strDataTypeName = strDataTypeName;
255       m_arrDefaultListColumns = arrDefaultListColumns;
256       m_arrDefaultOrderColumns = arrDefaultOrderColumns;
257       m_arrDefaultOrderDirections = arrDefaultOrderDirections;
258       m_mapColumnDefinitions = mapColumnDefinitions;
259       
260       m_strOptions = "";
261       m_strFunction = "";
262       m_bLink = true;
263       m_strTableId = ScrollableTableTag.SCROLLABLE_TABLE_PREFIX;
264
265       // Load setting which specify if we can use checkboxes for each line.
266
// This definition has to be in constructor
267
// and not in static definition because of tests.
268
Properties JavaDoc prpSettings;
269       
270       prpSettings = Config.getInstance().getPropertiesSafely();
271       
272       prpSettings = Config.getInstance().getPropertiesSafely();
273       m_strCheckDefault = Config.getBooleanPropertyAsString(
274                              prpSettings,
275                              ScrollableTableTag.WEBUI_LIST_CHECK,
276                              ScrollableTableTag.WEBUI_LIST_CHECK_DEFAULT,
277                              "Flag allowing for scrollable tables to have checkbox"
278                              + " in each line");
279       
280       m_strSortableDefault = Config.getBooleanPropertyAsString(
281                              prpSettings,
282                              ScrollableTableTag.WEBUI_LIST_SORTABLE,
283                              ScrollableTableTag.WEBUI_LIST_SORTABLE_DEFAULT,
284                              "Flag allowing for scrollable tables to be sortable"
285                              + " using column headings");
286    }
287    
288    // Business logic ///////////////////////////////////////////////////////////
289

290    /**
291     * Perform the test required for this particular tag, and either evaluate
292     * or skip the body of this tag. This method really determines what
293     *
294     * @return int - predefined constant
295     * @exception JspException if a JSP exception occurs
296     */

297    public int doStartTag(
298    ) throws JspException JavaDoc
299    {
300       // Initialize flags from cache. This value is cached in the ScrollableTableTag.
301
m_strTableId = getCachedContent(ScrollableTableTag.ACTIVE_SCROLLABLE_TABLE_ID,
302                                       false).trim();
303       m_strCheck = getCachedContent(ScrollableTableTag.SCROLLABLE_TABLE_CHECK,
304                                     false).trim();
305       m_strSortable = getCachedContent(ScrollableTableTag.SCROLLABLE_TABLE_SORTABLE,
306                                     false).trim();
307       m_strLimit = getCachedContent(ScrollableTableTag.SCROLLABLE_TABLE_ROW_LIMIT,
308                                      false).trim();
309       if (m_strTableId.length() == 0)
310       {
311          m_strTableId = ScrollableTableTag.SCROLLABLE_TABLE_PREFIX;
312       }
313       if (m_strCheck.length() == 0)
314       {
315          m_strCheck = m_strCheckDefault;
316       }
317       if (m_strSortable.length() == 0)
318       {
319          m_strSortable = m_strSortableDefault;
320       }
321
322       processFunction(getFunction());
323       
324       // We just produced the content of the tag and the body should be empty
325
return (SKIP_BODY);
326    }
327
328    /**
329     * Evaluate the remainder of the current page normally.
330     *
331     * @return int - predefined constant
332     * @exception JspException if a JSP exception occurs
333     */

334    public int doEndTag(
335    ) throws JspException JavaDoc
336    {
337       return (EVAL_PAGE);
338    }
339
340    /**
341     * Release all allocated resources.
342     */

343    public void release(
344    )
345    {
346       super.release();
347    }
348    
349    /**
350     * Get the name of the attribute which identifies ListOptions object. Required.
351     *
352     * @return String - name of the attribute which identifies ListOptions object
353     */

354    public String JavaDoc getOptions()
355    {
356       return m_strOptions;
357    }
358
359    /**
360     * Sets the name of the attribute which identifies ListOptions object. Required.
361     *
362     * @param strName - name of the attribute which identifies ListOptions object
363     */

364    public void setOptions(
365       String JavaDoc strName
366    )
367    {
368       m_strOptions = strName;
369    }
370
371    /**
372     * Get the name of the attribute which identifies the list of data objects. Required.
373     *
374     * @return name of the list
375     */

376    public String JavaDoc getData()
377    {
378       return m_strData;
379    }
380
381    /**
382     * Set the name of the attribute which identifies the list of data objects. Required.
383     *
384     * @param strData - name of the attribute which identifies the list of data objects
385     */

386    public void setData(
387       String JavaDoc strData
388    )
389    {
390       m_strData = strData;
391    }
392
393    /**
394     * Get the name of the function (element), which should be created by this tag.
395     * This is one of the FUNCTION_XXX constants defined in this class. Required.
396     *
397     * @return String - name of the function
398     */

399    public String JavaDoc getFunction()
400    {
401       return m_strFunction;
402    }
403
404    /**
405     * Sets the name of the function (element), which should be created by this tag.
406     * This is one of the FUNCTION_XXX constants defined in this class. Required.
407     *
408     * @param strFunction - function that will be set
409     */

410    public void setFunction(
411       String JavaDoc strFunction
412    )
413    {
414       m_strFunction = strFunction;
415    }
416
417    /**
418     * Get the flag if the list should contain a link. Not required.
419     *
420     * @return boolean - flag if the list should contain a link. Not required.
421     */

422    public boolean getLink()
423    {
424       return m_bLink;
425    }
426
427    /**
428     * Set the flag if the list should contain a link. Not required.
429     *
430     * @param bLink - Flag if the list should contain a link
431     */

432    public void setLink(
433       boolean bLink
434    )
435    {
436       m_bLink = bLink;
437    }
438
439    /**
440     * Get the flag if the list should contain a checkboxes included in each row.
441     * Not required.
442     *
443     * @return String - flag value
444     */

445    public String JavaDoc getCheck()
446    {
447       return m_strCheck;
448    }
449
450    /**
451     * Set flag if the list should contain a checkboxes included in each row. These
452     * checkboxes will be used for selecting particular item (row) in the list.
453     * Not required.
454     *
455     * @param strCheck - name of the attribute which identifies the flag
456     */

457    public void setCheck(
458       String JavaDoc strCheck
459    )
460    {
461       m_strCheck = strCheck;
462    }
463
464    /**
465     * Generate the HTML for the column definition
466     *
467     * @param iColumnId - column code of the specific table
468     * @param iCounter - counter for the position of the column width in the float array
469     * @param strFunction - the type of the function (headings, cols, body)
470     * @param outDefinition - definition of the column is output parameter which
471     * will be filled out so that we optimize the performance
472     */

473    protected void getColumnDefinition(
474       int iColumnId,
475       int iCounter,
476       String JavaDoc strFunction,
477       ColumnDefinition outDefinition
478    )
479    {
480       Integer JavaDoc intColumnId = new Integer JavaDoc(iColumnId);
481       ColumnDefinition column;
482       
483       column = (ColumnDefinition)m_mapColumnDefinitions.get(intColumnId);
484       if (column == null)
485       {
486          column = UNKNOWN_COLUMN;
487       }
488       
489       // TODO: For Miro: Propagate values from column to outDefinition
490
}
491
492    /**
493     * Generate the HTML for the column definition
494     *
495     * @param iColumnId - column code of the specific table
496     * @param iCounter - counter for the position of the column width in the float array
497     * @param strFunction - the type of the function (headings, cols, body)
498     * @return ColumnDefinition - definition for the requested column
499     */

500    protected ColumnDefinition getColumnDefinition(
501       int iColumnId,
502       int iCounter,
503       String JavaDoc strFunction
504    )
505    {
506       Integer JavaDoc intColumnId = new Integer JavaDoc(iColumnId);
507       ColumnDefinition column;
508       
509       column = (ColumnDefinition)m_mapColumnDefinitions.get(intColumnId);
510       if (column == null)
511       {
512          column = UNKNOWN_COLUMN;
513       }
514       
515       return column;
516    }
517
518    /**
519     * Generate the HTML for the body definition
520     *
521     * @param iColumnId - column code of the specific table
522     * @param iCounter - counter for the position of the column width in the float array
523     * @param iItemCounter - counter of actual processed item
524     * @param objListItem - particular object from the list
525     * @param sbHtml - out HTML string buffer
526     */

527    protected abstract void getBodyDefinition(
528       int iColumnId,
529       int iCounter,
530       int iItemCounter,
531       Object JavaDoc objListItem,
532       StringBuffer JavaDoc sbHtml
533    );
534
535    /**
536     * Get row id String for data object row. By default it is the
537     * id of the data object.
538     *
539     * @param objListItem - particular object from the list
540     * @return String - String representing row id
541     */

542    protected String JavaDoc getRowId(
543       Object JavaDoc objListItem
544    )
545    {
546       return Integer.toString(((DataObject)objListItem).getId());
547    }
548
549    /**
550     * Get array of columns specifying what columns to display in a list by
551     * default and in what order.
552     *
553     * @return int[] - list column codes
554     */

555    protected int[] getDefaultListColumns(
556    )
557    {
558       return m_arrDefaultListColumns;
559    }
560
561    /**
562     * Get array of columns specifying what columns should be used to sort by
563     * default on gui.
564     *
565     * @return int[] - list column codes
566     */

567    protected int[] getDefaultOrderColumns(
568    )
569    {
570       return m_arrDefaultOrderColumns;
571    }
572
573    /**
574     * Get default directions for the default list of columns used for sorting.
575     * The returned array has to have the same length as the one returned from
576     * getDefaultOrderColumns where the order for column specified at index i
577     * will be specified at the same index.
578     *
579     * @return String[] - ListDefinition.ORDER_XXX constants
580     */

581    protected String JavaDoc[] getDefaultOrderDirections(
582    )
583    {
584       return m_arrDefaultOrderDirections;
585    }
586
587    /**
588     * Get identificator of type of data shown in table. This should be constant
589     * that can be safely used to generate JavaScript identificators and should
590     * be also convertible to data type code using the methods in DataConstant
591     * class.
592     *
593     * @return String - type of data showed in table
594     */

595    protected String JavaDoc getDataTypeName(
596    )
597    {
598       return m_strDataTypeName;
599    }
600    
601    /**
602     * Generate the HTML for the column headings to the table
603     *
604     * @throws JspException - jsp error occured during getting headings to the table
605     */

606    private void getHeadingsToTable(
607    ) throws JspException JavaDoc
608    {
609       // part I : getting of the table headings
610
int[] arListColumns;
611       Object JavaDoc options = null;
612       
613       if ((m_strOptions != null) && (m_strOptions.length() > 0))
614       {
615          options = pageContext.getRequest().getAttribute(m_strOptions);
616       }
617       if (options == null)
618       {
619          // set showColumnCodes if there was not sent listoptions parameter from the JSP
620
arListColumns = getDefaultListColumns();
621       }
622       else
623       {
624          arListColumns = ((ListOptions)options).getShowColumnCodes();
625       }
626
627       StringBuffer JavaDoc sbHtml = new StringBuffer JavaDoc();
628       ColumnDefinition definition;
629       
630       // If there is enabled flag for allowing checkboxes in each row, add
631
// header for this field.
632
if (isCheckActive())
633       {
634           /*
635             <div class="clsColumnHeader">
636                <input type="checkbox" name="" id=""
637                       onclick="toggleAllRows(this, '<%=m_strTableId%>'>
638             </div>
639           */

640
641           sbHtml.append("<div class=\"clsColumnHeader\"><input type=\"checkbox\" name=\"");
642           sbHtml.append(m_strTableId);
643           sbHtml.append("_headerselectall\" id=\"");
644           sbHtml.append(m_strTableId);
645           sbHtml.append("_headerselectall\" onclick=\"toggleAllRows(this, '");
646           sbHtml.append(m_strTableId);
647           sbHtml.append("')\"></div>\n");
648       }
649
650       for (int iCounter = 0; iCounter < arListColumns.length; iCounter++)
651       {
652          definition = getColumnDefinition(arListColumns[iCounter], iCounter,
653                                           getFunction());
654          
655           /*
656             <div class="clsColumnHeader">
657                <a class="clsColumnHeaderText"
658                   href="#" title="<%=columnHeaderTooltip%>"
659                   columnDataType="<%=columnDataType%>"><%=columnHeaderName%></a>
660             </div>
661           */

662
663           sbHtml.append("<div class=\"clsColumnHeader\">\n");
664           sbHtml.append("<a class=\"clsColumnHeaderText");
665           if (!isSortableActive())
666           {
667              // if there is no sortable columns
668
sbHtml.append("NoSort");
669           }
670           sbHtml.append("\" HREF=\"#\" title=\"");
671           sbHtml.append(definition.getColumnTooltip());
672           sbHtml.append("\">");
673           sbHtml.append(definition.getColumnName());
674           sbHtml.append("</a></div>\n");
675        }
676
677        TagUtils.write(pageContext, sbHtml.toString());
678    }
679
680    /**
681     * Generate the HTML for the columns with specific width to the table
682     *
683     * @throws JspException - jsp error occured during getting columns to the table
684     */

685    private void getColsToTable(
686    ) throws JspException JavaDoc
687    {
688       int[] arListColumns;
689       Object JavaDoc options = null;
690       
691       if ((m_strOptions != null) && (m_strOptions.length() > 0))
692       {
693          options = pageContext.getRequest().getAttribute(m_strOptions);
694       }
695       if (options == null)
696       {
697          // set showColumnCodes if there was not sent listoptions parameter from the JSP
698
arListColumns = getDefaultListColumns();
699       }
700       else
701       {
702          arListColumns = ((ListOptions)options).getShowColumnCodes();
703       }
704
705       StringBuffer JavaDoc sbHtml = new StringBuffer JavaDoc();
706       int iTotalActualWidth = 0;
707       int iActualWidth = 0;
708       double dSumColumnScale = 0;
709       int iBase = 100;
710       
711       ColumnDefinition definition;
712       double[] widths = new double[arListColumns.length];
713       
714       for (int iCounter = 0; iCounter < arListColumns.length; iCounter++)
715       {
716          definition = getColumnDefinition(arListColumns[iCounter], iCounter,
717                                           getFunction());
718          widths[iCounter] = definition.getColumnWidth();
719          // Get sum of column widths so that we can scale them to fit into 100%
720
dSumColumnScale += widths[iCounter];
721       }
722
723       // If there is enabled flag for allowing checkboxes in each row, add
724
// column for this field.
725
if (isCheckActive())
726       {
727          // First change base for full width. There must be subtracted
728
// width of the column with checkbox from the full width.
729
iBase = iBase - CHECKBOX_COLUMN_WIDTH;
730
731          sbHtml.append("<col width=\"");
732          sbHtml.append(CHECKBOX_COLUMN_WIDTH);
733          sbHtml.append("%\">\n");
734       }
735
736       for (int iCounter = 0; iCounter < widths.length; iCounter++)
737       {
738          if (iCounter == (arListColumns.length - 1))
739          {
740             // this is the last column so get due width
741
iActualWidth = iBase - iTotalActualWidth;
742          }
743          else
744          {
745             // count width for each column as a percentage of the desired width
746
// compared to desired width of all columns
747
iActualWidth = (int)Math.round((iBase / dSumColumnScale)
748                                            * widths[iCounter]);
749             iTotalActualWidth += iActualWidth;
750           }
751           sbHtml.append("<col width=\"");
752           sbHtml.append(iActualWidth);
753           sbHtml.append("%\">\n");
754        }
755
756        TagUtils.write(pageContext, sbHtml.toString());
757    }
758
759    /**
760     * Generate the HTML for the body to the table
761     *
762     * @throws JspException - jsp error occured during getting body to the table
763     */

764    private void getBodyToTable(
765    ) throws JspException JavaDoc
766    {
767       List JavaDoc ldListData = (List JavaDoc)pageContext.getRequest().getAttribute(m_strData);
768       int[] arListColumns;
769       Object JavaDoc options = null;
770       String JavaDoc strSelectedItemIDs;
771       String JavaDoc strMarkedItemIDs;
772       int iEmptyLineHeigth = 0;
773       
774       if ((m_strOptions != null) && (m_strOptions.length() > 0))
775       {
776          options = pageContext.getRequest().getAttribute(m_strOptions);
777       }
778       if (options == null)
779       {
780          // set showColumnCodes if there was not sent listoptions parameter from the JSP
781
arListColumns = getDefaultListColumns();
782          strSelectedItemIDs = "";
783          strMarkedItemIDs = "";
784       }
785       else
786       {
787          ListOptions listOptions = (ListOptions)options;
788          arListColumns = listOptions.getShowColumnCodes();
789          strSelectedItemIDs = listOptions.getSelectedItemIDs();
790          strMarkedItemIDs = listOptions.getMarkedItemIDs();
791       }
792
793       StringBuffer JavaDoc sbHtml = new StringBuffer JavaDoc();
794       int iTmpCounter;
795       ColumnDefinition definition;
796
797       if ((ldListData != null) && (!ldListData.isEmpty()))
798       {
799          Object JavaDoc listItem = null;
800          int iItemCounter = 0;
801          StringBuffer JavaDoc selected = new StringBuffer JavaDoc();
802          StringBuffer JavaDoc marked = new StringBuffer JavaDoc();
803          boolean bMarked = false;
804          int iCounter;
805          
806          selected.append(",");
807          selected.append(strSelectedItemIDs);
808          selected.append(",");
809          marked.append(",");
810          marked.append(strMarkedItemIDs);
811          marked.append(",");
812          
813          // add item ID to the <tr> tag
814
for (Iterator JavaDoc items = ldListData.iterator(); items.hasNext(); iItemCounter++)
815          {
816             listItem = items.next();
817             bMarked = marked.indexOf("," + getRowId(listItem) + ",") > -1;
818
819             sbHtml.append("<tr id=\"");
820             sbHtml.append(m_strTableId);
821             sbHtml.append("_");
822             sbHtml.append(getRowId(listItem));
823             if (selected.indexOf("," + getRowId(listItem) + ",") > -1)
824             {
825                sbHtml.append("\" originalClassName=\"");
826                if (iItemCounter % 2 == 0)
827                {
828                   sbHtml.append(bMarked ? "clsMarked" : "clsEven");
829                }
830                else
831                {
832                   sbHtml.append(bMarked ? "clsMarked" : "clsOdd");
833                }
834                sbHtml.append("\" markedClassName=\"");
835                if (iItemCounter % 2 == 0)
836                {
837                   sbHtml.append("clsEven");
838                }
839                else
840                {
841                   sbHtml.append("clsOdd");
842                }
843                sbHtml.append("\" class=\"clsSelected\">");
844             }
845             else
846             {
847                sbHtml.append("\" class=\"");
848                if (iItemCounter % 2 == 0)
849                {
850                   sbHtml.append(bMarked ? "clsMarked" : "clsEven");
851                }
852                else
853                {
854                   sbHtml.append(bMarked ? "clsMarked" : "clsOdd");
855                }
856                sbHtml.append("\">");
857             }
858
859             // If there is enabled flag for allowing checkboxes in each row, add
860
// <td> tag for this field.
861
if (isCheckActive())
862             {
863                /*
864                 * <input type="checkbox" name="checkboxName" id="checkboxId" value="0">
865                 */

866                sbHtml.append("<td>\n");
867                sbHtml.append("<input type=\"checkbox\" name=\"");
868                sbHtml.append(m_strTableId);
869                sbHtml.append("_selectcheck_");
870                sbHtml.append(getRowId(listItem));
871                sbHtml.append("\" id=\"");
872                sbHtml.append(m_strTableId);
873                sbHtml.append("_selectcheck_");
874                sbHtml.append(getRowId(listItem));
875                sbHtml.append("\"></td>\n");
876             }
877
878             // add item value to the particular column
879
for (iCounter = 0; iCounter < arListColumns.length; iCounter++)
880             {
881                getBodyDefinition(arListColumns[iCounter], iCounter, (iItemCounter + 1),
882                                  listItem, sbHtml);
883             }
884             sbHtml.append("</tr>\n");
885          }
886          
887          // Check if number of assigned items within the scrollableatble doesn't exceed
888
// limit value specified for this table. If it exceeds, log this case.
889
if ((m_strLimit != null) && (m_strLimit.length() > 0))
890          {
891             int iLimitValue = 0;
892             try
893             {
894                iLimitValue = Integer.parseInt(m_strLimit);
895             }
896             catch (NumberFormatException JavaDoc nfeExc)
897             {
898                s_logger.log(Level.WARNING, "Limit value " + m_strLimit +
899                   " is incorrectly specified. It has to be numeric value.");
900             }
901             if ((iLimitValue != 0) && (iLimitValue < ldListData.size()))
902             {
903                s_logger.log(Level.WARNING, "Number of assigned items exceeds " +
904                                            "specified limit value.");
905             }
906          }
907       }
908       else
909       {
910          // There is problem in Mozilla for table that contains no data. Last empty line
911
// 'invisiblerow' has special style="line-height: 0px;". And there is incorrectly
912
// shown scrollbar in the empty table. In this case we can increase line height
913
// to 10px and srollbar doesn't show anymore.
914
iEmptyLineHeigth = 10;
915       }
916       
917       // Make it last column because otherwise it triggers bug
918
// in Mozilla 1.6 which always displays scrollbar
919
// We have to have this line here even if the table is empty otherwise
920
// the columns would collaps, therefore if the table is empty, the scrollbar
921
// will be still there unless we disable the overwlow as hidden.
922
sbHtml.append("<tr id=\"");
923       sbHtml.append(m_strTableId);
924       sbHtml.append("_invisiblerow\">\n");
925
926       // If there is enabled flag for allowing checkboxes in each row, add
927
// <td> tag for this field.
928
if (isCheckActive())
929       {
930          sbHtml.append("<td style=\"line-height: ");
931          sbHtml.append(iEmptyLineHeigth);
932          sbHtml.append("px;\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</td>\n");
933       }
934
935       for (int iCounter = 0; iCounter < arListColumns.length; iCounter++)
936       {
937          definition = getColumnDefinition(arListColumns[iCounter], iCounter,
938                                           getFunction());
939          
940          // add empty line to enable adjusting of columns to correct width based
941
// on the column heading when column contains less text then the column heading
942
// Miro: I have played with the line-height and other properties
943
// but as Julo correctly found out, there is no way
944
// how to hide the line and this is the only way how to minimize it
945
sbHtml.append("<td style=\"line-height: ");
946          sbHtml.append(iEmptyLineHeigth);
947          sbHtml.append("px;\">");
948          
949          for (iTmpCounter = 0;
950               iTmpCounter < (definition.getColumnName().length() < 6
951                              ? 6 : definition.getColumnName().length()) * 3;
952               iTmpCounter++)
953          {
954             sbHtml.append("&nbsp;");
955          }
956          sbHtml.append("</td>\n");
957       }
958       sbHtml.append("</tr>\n");
959
960       TagUtils.write(pageContext, sbHtml.toString());
961    }
962
963    /**
964     * Generate the HTML for the table cell (&lt;td&gt; tag body)
965     *
966     * @param sbBuffer - StringBuffer to add table cell code to
967     * @param strText - full length text to add
968     * @param iLimit - limit of length to show (0 - no limit)
969     * @param strLink - href for &lt;a&gt; tag (null - no link)
970     * @param strClass - class of &lt;td&gt; tag (null - no class)
971     */

972    protected void generateTableCell(
973       StringBuffer JavaDoc sbBuffer,
974       String JavaDoc strText,
975       int iLimit,
976       String JavaDoc strLink,
977       String JavaDoc strClass
978    )
979    {
980       String JavaDoc strHelp;
981
982       if (strText == null)
983       {
984          strText = "";
985       }
986       
987       // start tag
988
sbBuffer.append("<td");
989       // Limit the length of the text
990
strHelp = StringUtils.limitStringLength(iLimit, strText);
991       // add title if necessary
992
if (strHelp.length() < strText.length())
993       {
994          sbBuffer.append(" title=\"");
995          sbBuffer.append(strText);
996          sbBuffer.append("\"");
997       }
998       // add class
999
if (strClass != null)
1000      {
1001         sbBuffer.append(" class=\"");
1002         sbBuffer.append(strClass);
1003         sbBuffer.append("\"");
1004      }
1005      sbBuffer.append(">");
1006      // TODO: For Miro: Shouldn't we take into account the link attribute if to
1007
// generate link or not?
1008
// add start tag for link
1009
if (strLink != null)
1010      {
1011         sbBuffer.append("<a HREF=\"");
1012         sbBuffer.append(strLink);
1013         sbBuffer.append("\">");
1014      }
1015      // add text to display or if empty then add &nbsp;
1016
sbBuffer.append((strHelp.trim().length() == 0) ? "&nbsp;" : strHelp);
1017      // add end tag for link
1018
if (strLink != null)
1019      {
1020         sbBuffer.append("</a>");
1021      }
1022      // add end tag
1023
sbBuffer.append("</td>\n");
1024   }
1025   
1026   /**
1027    * Method process particular function
1028    *
1029    * @param strFunction - function that has to be processed
1030    * @throws JspException - error occured
1031    */

1032   protected void processFunction(
1033      String JavaDoc strFunction
1034   ) throws JspException JavaDoc
1035   {
1036      if (strFunction.equals(FUNCTION_HEADINGS))
1037      {
1038         // Get the table headings
1039
getHeadingsToTable();
1040      }
1041      else if (strFunction.equals(FUNCTION_COLS))
1042      {
1043         // Get columns with the specific width
1044
getColsToTable();
1045      }
1046      else if (strFunction.equals(FUNCTION_BODY))
1047      {
1048         // Get the table body
1049
setLink(true);
1050         getBodyToTable();
1051      }
1052      else if (strFunction.equals(FUNCTION_SORT))
1053      {
1054         // Get javascript initializing sort
1055
getSortInitScript();
1056      }
1057      else if (strFunction.equals(FUNCTION_FILTER))
1058      {
1059         // Get javascript initializing filter
1060
getFilterInitScript();
1061      }
1062      else if (strFunction.equals(FUNCTION_MESSAGES))
1063      {
1064         // Get javascript initializing message area
1065
getMessagesInitScript();
1066      }
1067      else
1068      {
1069         StringBuffer JavaDoc sbHtml = new StringBuffer JavaDoc();
1070
1071         sbHtml.append("<!-- Unknown list tag function ");
1072         sbHtml.append(m_strFunction);
1073         sbHtml.append(" -->");
1074 
1075         TagUtils.write(pageContext, sbHtml.toString());
1076      }
1077   }
1078
1079   /**
1080    * Generate initialization values for JavaScript to dynamically create
1081    * options in dropdown list which is used for server side sorting.
1082    *
1083    * @throws JspException - an error ha occured
1084    */

1085   private void getSortInitScript(
1086   ) throws JspException JavaDoc
1087   {
1088      Object JavaDoc options = null;
1089      
1090      StringBuffer JavaDoc sbHtml = new StringBuffer JavaDoc();
1091      int[] arrListColumnCodes;
1092      int[] arrOrderColumnCodes;
1093      String JavaDoc[] arrOrderDirections;
1094      int[] arrSortableColumnCodes = null;
1095      int iCount;
1096      int iIndex;
1097
1098      if ((m_strOptions != null) && (m_strOptions.length() > 0))
1099      {
1100         options = pageContext.getRequest().getAttribute(m_strOptions);
1101      }
1102      if (options == null)
1103      {
1104         arrListColumnCodes = getDefaultListColumns();
1105         arrOrderColumnCodes = getDefaultOrderColumns();
1106         arrOrderDirections = getDefaultOrderDirections();
1107      }
1108      else
1109      {
1110         ListOptions listOptions = (ListOptions) options;
1111         arrListColumnCodes = listOptions.getShowColumnCodes();
1112         arrOrderColumnCodes = listOptions.getOrderColumnCodes();
1113         arrOrderDirections = listOptions.getOrderDirections();
1114         arrSortableColumnCodes = listOptions.getSortableColumnCodes();
1115      }
1116      
1117      if (arrSortableColumnCodes == null)
1118      {
1119         // It seems like the list options didn't pass through the persistance
1120
// layer and therefore we do not know what columns are sortable so lets
1121
// assume that all which we need to show are sortable
1122
arrSortableColumnCodes = arrListColumnCodes;
1123      }
1124      
1125      // checking if parent has sorting select
1126
sbHtml.append("if (parent.document.getElementById(\"");
1127      sbHtml.append(getDataTypeName());
1128      sbHtml.append("topright");
1129      sbHtml.append("\") != null)\n");
1130      sbHtml.append("{\n");
1131      // Type of Data
1132
sbHtml.append(" var strType = \"");
1133      sbHtml.append(getDataTypeName());
1134      sbHtml.append("\";\n");
1135      // Column Codes and Names
1136
sbHtml.append(" var arrColumnCodes = new Array();\n");
1137      sbHtml.append(" var arrColumnNames = new Array();\n");
1138      ColumnDefinition definition;
1139      for (iCount = 0, iIndex = 0; iCount < arrListColumnCodes.length;
1140           iCount++, iIndex++)
1141      {
1142         definition = getColumnDefinition(arrListColumnCodes[iCount], iIndex,
1143                                          FUNCTION_HEADINGS);
1144         if (ArrayUtils.contains(arrSortableColumnCodes, arrListColumnCodes[iCount])
1145             != -1)
1146         {
1147            sbHtml.append(" arrColumnCodes[");
1148            sbHtml.append(iIndex);
1149            sbHtml.append("] = \"");
1150            sbHtml.append(arrListColumnCodes[iCount]);
1151            sbHtml.append("\";\n arrColumnNames[");
1152            sbHtml.append(iIndex);
1153            sbHtml.append("] = \"");
1154            sbHtml.append(definition.getColumnName());
1155            sbHtml.append("\";\n");
1156         }
1157         else
1158         {
1159            // Decrease index since we haven't done anything for this element
1160
// of the array
1161
iIndex--;
1162         }
1163      }
1164
1165      // Selected column ID
1166
sbHtml.append(" iSelectedColumnCode = ");
1167      sbHtml.append(arrOrderColumnCodes == null ? "null" : "" + arrOrderColumnCodes[0]);
1168      sbHtml.append(";\n");
1169      // Selected sort Type
1170
sbHtml.append(" bSortTypeIsAscending = ");
1171      sbHtml.append(arrOrderDirections == null ? "null" : "" +
1172                    arrOrderDirections[0].equals(ListDefinition.ORDER_ASCENDING));
1173      sbHtml.append(";\n");
1174      // Call parent function
1175
sbHtml.append(" parent.fillSortSelect(strType, arrColumnCodes, arrColumnNames," +
1176                        " iSelectedColumnCode, bSortTypeIsAscending);\n");
1177      sbHtml.append("}\n");
1178
1179      TagUtils.write(pageContext, sbHtml.toString());
1180   }
1181
1182   /**
1183    * Generate initialization values for JavaScript to dynamically create
1184    * variables used to construct options in dropdown list which is used
1185    * for defining filter criteria.
1186    *
1187    * @throws JspException - an error ha occured
1188    */

1189   private void getFilterInitScript(
1190   ) throws JspException JavaDoc
1191   {
1192      StringBuffer JavaDoc sbHtml = new StringBuffer JavaDoc();
1193
1194      int iCount;
1195      int iIndex;
1196      int[] arrListColumnCodes = null;
1197      int[] arrCriteriaColumnCodes = null;
1198      int[] arrSortableColumnCodes = null;
1199      String JavaDoc strTypeOfData;
1200      String JavaDoc strSelFilterID = "-1";
1201      Object JavaDoc options = null;
1202      
1203      // Set up column codes that can be used for criterias in the filter
1204

1205      if ((m_strOptions != null) && (m_strOptions.length() > 0))
1206      {
1207         options = pageContext.getRequest().getAttribute(m_strOptions);
1208      }
1209      if (options == null)
1210      {
1211         arrListColumnCodes = getDefaultListColumns();
1212      }
1213      else
1214      {
1215         ListOptions listOptions = (ListOptions) options;
1216         arrListColumnCodes = listOptions.getShowColumnCodes();
1217         strSelFilterID = listOptions.getDefinitionId();
1218         arrCriteriaColumnCodes = listOptions.getFilterableColumnCodes();
1219         arrSortableColumnCodes = listOptions.getSortableColumnCodes();
1220      }
1221      if (arrCriteriaColumnCodes == null)
1222      {
1223         // It seems like the list options didn't pass through the persistance
1224
// layer and therefore we do not know what columns are filterable so lets
1225
// assume that all which we need to show can be filtered by
1226
arrCriteriaColumnCodes = arrListColumnCodes;
1227      }
1228      if (arrSortableColumnCodes == null)
1229      {
1230         // It seems like the list options didn't pass through the persistance
1231
// layer and therefore we do not know what columns are sortable so lets
1232
// assume that all which we need to show can be sorted by
1233
arrSortableColumnCodes = arrListColumnCodes;
1234      }
1235
1236      strTypeOfData = getDataTypeName();
1237
1238      // Define Column Codes and Names for criteria
1239
ColumnDefinition definition;
1240      
1241      for (iCount = 0, iIndex = 0; iCount < arrCriteriaColumnCodes.length;
1242           iCount++, iIndex++)
1243      {
1244         definition = getColumnDefinition(arrCriteriaColumnCodes[iCount], iIndex,
1245                                          FUNCTION_HEADINGS);
1246         
1247         if (GlobalConstants.ERROR_CHECKING)
1248         {
1249            // THE PRECONDITION IS THAT THE COLUMN NAMES CAN'T CONTAIN ','
1250
// WITHIN THE STRING
1251
assert (definition.getColumnName().indexOf(",") == -1)
1252                   : "Column name should not contain comma character.";
1253         }
1254         
1255         sbHtml.append(" arrCriteriaColumnCodes[");
1256         sbHtml.append(iIndex);
1257         sbHtml.append("] = \"");
1258         sbHtml.append(arrCriteriaColumnCodes[iCount]);
1259         sbHtml.append("\";\n arrCriteriaColumnNames[");
1260         sbHtml.append(iCount);
1261         sbHtml.append("] = \"");
1262         sbHtml.append(definition.getColumnName());
1263         sbHtml.append("\";\n arrCriteriaColumnDataTypes[");
1264         sbHtml.append(iCount);
1265         sbHtml.append("] = \"");
1266         sbHtml.append(definition.getColumnDataType());
1267         sbHtml.append("\";\n arrCriteriaColumnBoolValues[");
1268         sbHtml.append(iCount);
1269         sbHtml.append("] = \"");
1270         if (definition.getColumnDataType() == DataCondition.VALUE_TYPE_BOOLEAN)
1271         {
1272            sbHtml.append(definition.getColumnBooleanValueNames(":"));
1273         }
1274         else
1275         {
1276            sbHtml.append(":");
1277         }
1278         sbHtml.append("\";\n arrCriteriaColumnMandatoryRetrieved[");
1279         sbHtml.append(iCount);
1280         sbHtml.append("] = \"");
1281         sbHtml.append(definition.isColumnMandatory() ? 1 : 0);
1282         sbHtml.append("\";\n arrCriteriaColumnSortable[");
1283         sbHtml.append(iCount);
1284         if (ArrayUtils.contains(arrSortableColumnCodes, arrCriteriaColumnCodes[iCount])
1285            == -1)
1286         {
1287            // Column is not sortable
1288
sbHtml.append("] = \"0\";\n");
1289         }
1290         else
1291         {
1292            // Column is sortable
1293
sbHtml.append("] = \"1\";\n");
1294         }
1295      }
1296
1297      // Call parent function to set up hidden parameters storing
1298
// data necessary for filter criteria
1299
sbHtml.append(" fillFilterVariables('");
1300      sbHtml.append(strTypeOfData);
1301      sbHtml.append("', arrCriteriaColumnCodes, " +
1302                    "arrCriteriaColumnNames, arrCriteriaColumnDataTypes, " +
1303                    "arrCriteriaColumnBoolValues, arrCriteriaColumnMandatoryRetrieved, " +
1304                    "arrCriteriaColumnSortable);\n");
1305
1306      // get list of generic data from request and define array of filters that
1307
// will be added to the top filter combo box
1308
List JavaDoc ldListData = (List JavaDoc)pageContext.getRequest().getAttribute(
1309                               ListBrowserServlet.PARAMETER_GENERIC_DATA_LIST_NAME);
1310      // if list data is null it means there are no filters stored
1311
if ((ldListData != null) && (!ldListData.isEmpty()))
1312      {
1313         Object JavaDoc listItem = null;
1314         int iItemCounter = 0;
1315   
1316         for (Iterator JavaDoc items = ldListData.iterator(); items.hasNext();)
1317         {
1318            listItem = items.next();
1319      
1320            sbHtml.append(" arrFilterIDs[");
1321            sbHtml.append(iItemCounter);
1322            sbHtml.append("] = \"");
1323            sbHtml.append(((GenericData)listItem).getId());
1324            sbHtml.append("\";\n arrFilterNames[");
1325            sbHtml.append(iItemCounter);
1326            sbHtml.append("] = \"");
1327            sbHtml.append(((GenericData)listItem).getName());
1328            sbHtml.append("\";\n ");
1329
1330            iItemCounter++;
1331         }
1332      }
1333
1334      // generate code that will find out actual listprefix
1335
// Originally listprefix variable is set up to the actual used datatype
1336
// and for filter items (filterlist) will be used FILTER datatype. It is ok,
1337
// but when we want to fill filter select, we are not used listprefix for filter
1338
// but listprefix for dataobject. Therefore at this place we should check if
1339
// there is used additional parameter with name 'listprefix' and if yes, we will
1340
// set up strListPrefix variable and his variable we will use as parameter to the
1341
// function parent.fillFilterSelect() - called bellow.
1342
sbHtml.append(" var iIndex;\nvar strListPrefix = '");
1343      sbHtml.append(strTypeOfData);
1344      sbHtml.append("';\nfor (iIndex = 0; iIndex < arrAdditionParams.length; iIndex++) {\n" +
1345                    " if (arrAdditionParams[iIndex] == 'listprefix') {\n" +
1346                    " strListPrefix = document.getElementById('listprefix').value;" +
1347                    " \n}\n}");
1348
1349      // Call parent function to fill filter select by last used items
1350
sbHtml.append(" fillFilterSelect(strListPrefix, arrFilterIDs, arrFilterNames, '");
1351      sbHtml.append(strSelFilterID);
1352      sbHtml.append("', '");
1353      sbHtml.append(isFilter());
1354      sbHtml.append("');\n");
1355
1356      // Call function that will set up flag signaling if there is currently
1357
// shown filter dataobjects within the list or not filter dataobjects
1358
sbHtml.append(" setIsFilter(strListPrefix, '");
1359      sbHtml.append(strTypeOfData);
1360      sbHtml.append("');\n");
1361      
1362      TagUtils.write(pageContext, sbHtml.toString());
1363   }
1364
1365   /**
1366    * Generate initialization values for JavaScript to populate message area
1367    * with the messages related to list processing.
1368    *
1369    * @throws JspException - an error ha occured
1370    */

1371   private void getMessagesInitScript(
1372   ) throws JspException JavaDoc
1373   {
1374      StringBuffer JavaDoc sbHtml = new StringBuffer JavaDoc();
1375      String JavaDoc strTypeOfData;
1376
1377      strTypeOfData = getDataTypeName();
1378
1379      // checking if parent has sorting select
1380
sbHtml.append("if (parent.document.getElementById(\"");
1381      sbHtml.append(strTypeOfData);
1382      sbHtml.append("listmessages");
1383      sbHtml.append("\") != null)\n");
1384      sbHtml.append("{\n");
1385
1386      // Now generate list of message
1387
sbHtml.append(MessageListTag.generateMessageList(pageContext));
1388      
1389      // Now add the messages to the message area after cleaning any existing
1390
// messages
1391
sbHtml.append(" parent.addListMessages(\"");
1392      sbHtml.append(strTypeOfData);
1393      sbHtml.append("\", arrMessages, arrMessageTypes, true);\n");
1394      sbHtml.append("}\n");
1395
1396      TagUtils.write(pageContext, sbHtml.toString());
1397   }
1398   
1399   /**
1400    * @return boolean - true if checkbox will be present in each row of the table
1401    */

1402   public boolean isCheckActive(
1403   )
1404   {
1405      return ((Boolean.TRUE.toString().equalsIgnoreCase(m_strCheck))
1406             || ("1".equals(m_strCheck)));
1407   }
1408
1409   /**
1410    * @return boolean - true if table columns will be sortable
1411    */

1412   public boolean isSortableActive(
1413   )
1414   {
1415      return ((Boolean.TRUE.toString().equalsIgnoreCase(m_strSortable))
1416             || ("1".equals(m_strSortable)));
1417   }
1418   
1419   /**
1420    * Function returns flag signaling if there is used filter list or not.
1421    *
1422    * @return boolean - frue = there is used list of filters
1423    * - false = there is used list of dataobjects
1424    */

1425   protected boolean isFilter()
1426   {
1427      // default value is false
1428
return false;
1429   }
1430}
1431
Popular Tags