001/**
002 *
003 * Copyright (c) 2014, the Railo Company Ltd. All rights reserved.
004 *
005 * This library is free software; you can redistribute it and/or
006 * modify it under the terms of the GNU Lesser General Public
007 * License as published by the Free Software Foundation; either 
008 * version 2.1 of the License, or (at your option) any later version.
009 * 
010 * This library is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013 * Lesser General Public License for more details.
014 * 
015 * You should have received a copy of the GNU Lesser General Public 
016 * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
017 * 
018 **/
019package lucee.runtime.tag;
020
021import java.io.IOException;
022
023import lucee.commons.lang.StringUtil;
024import lucee.runtime.exp.ExpressionException;
025import lucee.runtime.exp.PageException;
026import lucee.runtime.ext.tag.BodyTagTryCatchFinallyImpl;
027import lucee.runtime.functions.string.CJustify;
028import lucee.runtime.functions.string.LJustify;
029import lucee.runtime.functions.string.RJustify;
030import lucee.runtime.op.Caster;
031
032/**
033* Builds a table in a CFML page. Use the cfcol tag to define table column and row 
034*   characteristics. The cftable tag renders data either as preformatted text, or, with the HTMLTable 
035*   attribute, as an HTML table. Use cftable to create tables if you don't want to write HTML table tag 
036*   code, or if your data can be well presented as preformatted text.
037*
038*
039*
040**/
041public final class Table extends BodyTagTryCatchFinallyImpl {
042    
043    /**
044     * Field <code>ALIGN_LEFT</code>
045     */
046    public static final short ALIGN_LEFT=0;
047    /**
048     * Field <code>ALIGN_CENTER</code>
049     */
050    public static final short ALIGN_CENTER=1;
051    /**
052     * Field <code>ALIGN_RIGHT</code>
053     */
054    public static final short ALIGN_RIGHT=2;
055
056        /** Name of the cfquery from which to draw data. */
057        private lucee.runtime.type.Query query;
058
059        /** Maximum number of rows to display in the table. */
060        private int maxrows=Integer.MAX_VALUE;
061
062        /** Specifies the query row from which to start processing. */
063        private int startrow=1;
064
065        /** Adds a border to the table. Use only when you specify the HTMLTable attribute for the table. */
066        private boolean border;
067
068        /** Displays headers for each column, as specified in the cfcol tag. */
069        private boolean colheaders;
070
071        /** Number of spaces to insert between columns 'default is 2'. */
072        private int colspacing=2;
073
074        /** Renders the table as an HTML 3.0 table. */
075        private boolean htmltable;
076
077        /** Number of lines to use for the table header. The default is 2, which leaves one line between 
078        **              the headers and the first row of the table. */
079        private int headerlines=2;
080
081        StringBuffer header=new StringBuffer();
082        StringBuffer body=new StringBuffer();
083
084    private int initRow;
085
086    private int count=0;
087        private boolean startNewRow;
088
089        @Override
090        public void release()   {
091                super.release();
092                query=null;
093                maxrows=Integer.MAX_VALUE;
094                startrow=1;
095                border=false;
096                colheaders=false;
097                colspacing=2;
098                htmltable=false;
099                headerlines=2;
100                if(header.length()>0)header=new StringBuffer();
101                body=new StringBuffer();
102                count=0;
103        }
104
105        /** set the value query
106        *  Name of the cfquery from which to draw data.
107        * @param query value to set
108         * @throws PageException
109        **/
110        public void setQuery(String query) throws PageException {
111                this.query = Caster.toQuery(pageContext.getVariable(query));
112        }
113
114        /** set the value maxrows
115        *  Maximum number of rows to display in the table.
116        * @param maxrows value to set
117        **/
118        public void setMaxrows(double maxrows)  {
119                this.maxrows=(int)maxrows;
120        }
121
122        /** set the value startrow
123        *  Specifies the query row from which to start processing.
124        * @param startrow value to set
125        **/
126        public void setStartrow(double startrow)        {
127                this.startrow=(int)startrow;
128                if(this.startrow<=0)this.startrow=1;
129        }
130
131        /** set the value border
132        *  Adds a border to the table. Use only when you specify the HTMLTable attribute for the table.
133        * @param border value to set
134        **/
135        public void setBorder(boolean border)   {
136                this.border=border;
137        }
138
139        /** set the value colheaders
140        *  Displays headers for each column, as specified in the cfcol tag.
141        * @param colheaders value to set
142        **/
143        public void setColheaders(boolean colheaders)   {
144                this.colheaders=colheaders;
145        }
146
147        /** set the value colspacing
148        *  Number of spaces to insert between columns 'default is 2'.
149        * @param colspacing value to set
150        **/
151        public void setColspacing(double colspacing)    {
152                this.colspacing=(int)colspacing;
153        }
154
155        /** set the value htmltable
156        *  Renders the table as an HTML 3.0 table.
157        * @param htmltable value to set
158        **/
159        public void setHtmltable(boolean htmltable)     {
160                this.htmltable=htmltable;
161        }
162
163        /** set the value headerlines
164        *  Number of lines to use for the table header. The default is 2, which leaves one line between 
165        *               the headers and the first row of the table.
166        * @param headerlines value to set
167        **/
168        public void setHeaderlines(double headerlines)  {
169                this.headerlines=(int)headerlines;
170                if(this.headerlines<2)this.headerlines=2;
171        }
172
173
174        @Override
175        public int doStartTag() throws PageException    {
176                startNewRow=true;
177                initRow=query.getRecordcount();
178                query.go(startrow,pageContext.getId());
179                pageContext.undefinedScope().addQuery(query);
180                return query.getRecordcount()>=startrow?EVAL_BODY_INCLUDE:SKIP_BODY;
181        }
182
183        @Override
184        public void doInitBody()        {
185                //if(htmltable) body.append("<tr>\n");
186        }
187
188        @Override
189        public int doAfterBody() throws PageException   {
190            if(htmltable) body.append("</tr>\n");
191            else body.append('\n');
192            startNewRow=true;
193            //print.out(query.getCurrentrow()+"-"+query.getRecordcount());
194                return ++count<maxrows && query.next()?EVAL_BODY_AGAIN:SKIP_BODY;
195        }
196
197        @Override
198        public int doEndTag() throws PageException      {
199            try {
200            _doEndTag();
201        } catch (IOException e) {
202            throw Caster.toPageException(e);
203        }
204                return EVAL_PAGE;
205        }
206        private void _doEndTag() throws IOException     {
207            if(htmltable) {
208                    pageContext.forceWrite("<table colspacing=\""+colspacing+"\"");
209                    if(border)  {
210                        pageContext.forceWrite(" border=\"1\"");
211                    }
212                    pageContext.forceWrite(">\n");
213                    if(header.length()>0) {
214                        pageContext.forceWrite("<tr>\n");
215                        pageContext.forceWrite(header.toString());
216                        pageContext.forceWrite("</tr>\n");
217                    }
218                    pageContext.forceWrite(body.toString());
219                    pageContext.forceWrite("</table>");
220                }
221            else {
222                pageContext.forceWrite("<pre>");
223                if(header.length()>0) {
224                        pageContext.forceWrite(header.toString());
225                        pageContext.forceWrite(StringUtil.repeatString("\n",headerlines-1));
226                    }
227                pageContext.forceWrite(body.toString());
228                pageContext.forceWrite("</pre>");
229            }
230        }
231
232    @Override
233    public void doFinally() {
234                try {
235                    pageContext.undefinedScope().removeQuery();
236                        if(query!=null)query.go(initRow,pageContext.getId());
237                } 
238                catch (PageException e) {       }
239    }
240
241    /**
242     * @param strHeader
243     * @param text
244     * @param align
245     * @param width 
246     * @throws ExpressionException
247     */
248    public void setCol(String strHeader, String text, short align, int width) throws ExpressionException {
249    // HTML
250        if(htmltable) {
251            if(colheaders && count==0 && strHeader.trim().length()>0) {
252                header.append("\t<th");
253                addAlign(header,align);
254                addWidth(header,width);
255                header.append(">");
256                header.append(strHeader);
257                header.append("</th>\n");
258            }
259            if(htmltable && startNewRow) {
260                body.append("<tr>\n");
261                startNewRow=false;
262            }
263            
264            body.append("\t<td");
265            addAlign(body,align);
266            addWidth(body,width);
267            body.append(">");
268            body.append(text);
269            body.append("</td>\n");
270        }
271    // PRE
272        else {
273            if(width<0) width=20;
274            if(colheaders && count==0 && strHeader.trim().length()>0) {
275                addPre(header,align,strHeader,width);
276            }
277            addPre(body,align,text,width);
278            
279        }
280    }
281
282    private void addAlign(StringBuffer data,short align) {
283        data.append(" align=\"");
284        data.append(toStringAlign(align));
285        data.append("\"");
286    }
287    
288    private void addWidth(StringBuffer data,int width) {
289        if(width>=-1) {
290            data.append(" width=\"");
291            data.append(width);
292            data.append("%\"");
293        }
294    }
295    
296    private void addPre(StringBuffer data,short align,String value, int length) throws ExpressionException {
297        if(align==ALIGN_RIGHT) data.append(RJustify.call(pageContext,value,length));
298        else if(align==ALIGN_CENTER) data.append(CJustify.call(pageContext,value,length));
299        else data.append(LJustify.call(pageContext,value,length));
300        
301    }
302    
303    private String toStringAlign(short align) {
304        if(align==ALIGN_RIGHT) return "right";
305        if(align==ALIGN_CENTER) return "center";
306        return "left";
307    }
308}