001/*
002 * Copyright (c) 2009 The openGion Project.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
013 * either express or implied. See the License for the specific language
014 * governing permissions and limitations under the License.
015 */
016package org.opengion.plugin.view;
017
018import java.util.List;
019
020import org.opengion.fukurou.util.StringUtil;
021import org.opengion.fukurou.util.TagBuffer;
022import org.opengion.fukurou.util.XHTMLTag;
023import org.opengion.hayabusa.common.HybsSystem;
024import org.opengion.hayabusa.common.HybsSystemException;
025import org.opengion.hayabusa.html.FormatterType;
026import org.opengion.hayabusa.html.TableFormatter;
027import org.opengion.hayabusa.html.ViewAjaxTreeTableParam;
028
029/**
030 * JavaScript のツリー階層を持ったテーブル表示を行う、ツリーテーブル表示クラスです。
031 *
032 * AbstractViewForm により、setter/getterメソッドのデフォルト実装を提供しています。
033 * 各HTMLのタグに必要な setter/getterメソッドのみ、追加定義しています。
034 *
035 * AbstractViewForm を継承している為、ロケールに応じたラベルを出力させる事が出来ます。
036 *
037 * @og.rev 7.3.2.3 (2021/04/09) システム定数のJSP_IMGを使用します。(※ SYS.JSP + SYS.IMAGE_DIR)
038 * @og.group 画面表示
039 *
040 * @version     4.0
041 * @author      Hiroki Nakamura
042 * @since       JDK5.0,
043 */
044public class ViewForm_HTMLAjaxTreeTable extends ViewForm_HTMLCustomTable  {
045        /** このプログラムのVERSION文字列を設定します。 {@value} */
046        private static final String VERSION = "8.5.5.1 (2024/02/29)" ;
047
048//      // 6.4.4.2 (2016/04/01) JSP + "/image/" にする。
049//      private static final String JSPIMG = HybsSystem.sys( "JSP" ) + "/image/" ;
050        // 8.0.3.0 (2021/12/17) hayabusa.taglib.ViewAjaxTreeParamTag へ移動
051        private static final String JSPIMG = HybsSystem.sys( "JSP_IMG" ) + "/" ;        // 互換性の関係で最後に"/"を追加
052
053        private int[]                   childSearchKeys ;
054        private String                  childSearchJsp  ;
055        private String                  levelClm                ;
056        private int                             levelClmPos             = -1;
057        private String                  imgCollapsed    ;
058        private String                  imgExpanded             ;
059        private String                  imgNoSub                ;
060        private boolean                 expandAll               ;               // 4.3.3.0 (2008/10/01)
061        private int                             childViewStartNo= -1;   // 4.3.3.0 (2008/10/01)
062        private int                             expCtrlClmPos   = -1;   // 4.3.5.0 (2008/02/01)
063
064        /**
065         * デフォルトコンストラクター
066         *
067         * @og.rev 6.4.2.0 (2016/01/29) PMD refactoring. Each class should declare at least one constructor.
068         */
069        public ViewForm_HTMLAjaxTreeTable() { super(); }                // これも、自動的に呼ばれるが、空のメソッドを作成すると警告されるので、明示的にしておきます。
070
071        /**
072         * DBTableModel から HTML文字列を作成して返します。
073         * startNo(表示開始位置)から、pageSize(表示件数)までのView文字列を作成します。
074         * 表示残りデータが pageSize 以下の場合は、残りのデータをすべて出力します。
075         *
076         * @og.rev 4.3.3.0 (2008/10/01) noTransition属性,childViewStartNo属性対応
077         * @og.rev 4.3.7.4 (2009/07/01) tbodyタグの入れ子を解消(FireFox対応)
078         * @og.rev 6.4.3.4 (2016/03/11) tdに、[カラム]が無いケースで、次の[カラム]のクラス属性が、前方すべてのtdにセットされてしまう対応。
079         * @og.rev 6.4.4.2 (2016/04/01) TableFormatterのタイプ別値取得処理の共通部をまとめる。
080         * @og.rev 6.4.5.0 (2016/04/08) メソッド変更( getColumnDbType(int) → getClassName(int) )
081         * @og.rev 6.8.1.1 (2017/07/22) ckboxTD変数は、<td> から <td に変更します(タグの最後が記述されていない状態でもらう)。
082         *
083         * @param       strNo           表示開始位置
084         * @param       pageSize        表示件数
085         * @return      DBTableModelから作成された HTML文字列
086         * @og.rtnNotNull
087         */
088        @Override
089        public String create( final int strNo, final int pageSize )  {
090                if( getRowCount() == 0 ) { return ""; }                         // 暫定処置
091
092                initParam();
093
094                // 4.3.3.0 (2008/10/01) 子データ差分取得用
095                // 6.3.9.1 (2015/11/27) Found 'DD'-anomaly for variable(PMD)
096                final int startNo = childViewStartNo >= 0 ? childViewStartNo : strNo;
097
098                if( headerFormat == null ) {
099                        makeDefaultFormat();
100                }
101
102                headerFormat.makeFormat( getDBTableModel() );
103
104                final StringBuilder out = new StringBuilder( BUFFER_LARGE )
105                        .append( getCountForm( startNo,pageSize ) )
106                        .append( getHeader() );
107
108                if( bodyFormatsCount == 0 ) {
109                        bodyFormats = new TableFormatter[BODYFORMAT_MAX_COUNT];
110                        bodyFormats[0] = headerFormat ;
111                        bodyFormatsCount ++ ;
112                }
113                else {
114                        for( int i=0; i<bodyFormatsCount; i++ ) {
115                                bodyFormats[i].makeFormat( getDBTableModel() );
116                        }
117                }
118
119                int bgClrCnt = 0;
120                final int lastNo = getLastNo( startNo, pageSize );                              // 6.3.9.1 (2015/11/27) forループの近くに移動
121                for( int row=startNo; row<lastNo; row++ ) {
122                        if( isSkip( row ) || isSkipNoEdit( row ) ) { continue; }        // 4.3.1.0 (2008/09/08)
123                        for( int i=0; i<bodyFormatsCount; i++ ) {
124                                final TableFormatter bodyFormat = bodyFormats[i];
125                                if( ! bodyFormat.isUse( row,getDBTableModel() ) ) { continue; }         // 3.5.4.0 (2003/11/25)
126                                out.append("<tbody").append( getBgColorCycleClass( bgClrCnt++,row ) );
127                                if( isNoTransition() ) {                                        // 4.3.3.0 (2008/10/01)
128                                        out.append( getHiddenRowValue( row ) );
129                                }
130                                out.append('>')                 // 6.0.2.5 (2014/10/31) char を append する。
131                                        .append( bodyFormat.getTrTag() );
132
133                                if( isNumberDisplay() ) {
134                                        final String ckboxTD = "<td" + bodyFormat.getRowspan();         // 6.8.1.1 (2017/07/22)
135                                        out.append( makeCheckbox( ckboxTD,row,0 ) );
136                                }
137
138                                int cl = 0;
139                                for( ; cl<bodyFormat.getLocationSize(); cl++ ) {
140                                        String fmt = bodyFormat.getFormat(cl);
141                                        final int loc = bodyFormat.getLocation(cl);
142                                        if( ! bodyFormat.isNoClass() && loc >= 0 ) {
143                                                // 6.4.3.4 (2016/03/11) tdに、[カラム]が無いケースで、次の[カラム]のクラス属性が、前方すべてのtdにセットされてしまう対応。
144                                                final int idx = fmt.lastIndexOf( "<td" );
145                                                if( idx >= 0 ) {                // matchしてるので、あるはず
146                                                        // 8.5.4.2 (2024/01/12) class 属性がフォーマット中に存在する場合、追記になる。
147                                                        fmt = insertClassName( fmt,loc,idx );                   // 8.5.4.2 (2024/01/12)
148
149                                        //              final String tdclass = " class=\"" + getClassName(loc) + "\" ";                 // 6.4.5.0 (2016/04/08)
150                                        //              fmt = fmt.substring( 0,idx+3 ) + tdclass + fmt.substring( idx+3 ) ;
151                                                }
152                                        }
153                                        out.append( fmt );
154                                        if( loc >= 0 ) {
155                                                if( levelClm != null && levelClm.equals( getDBColumn( loc ).getName() ) ) {
156                                                        out.append( getLvlClmTag( row ) );
157                                                }
158                                                else {
159                                                        // 6.4.4.2 (2016/04/01) 処理の共通部をまとめる。
160                                                        out.append( getTypeCaseValue( bodyFormat.getType(cl),row,loc ) );
161                                                }
162                                        }
163                                        else {
164                                                out.append( bodyFormat.getSystemFormat(row,loc) );
165                                        }
166                                }
167                                out.append( bodyFormat.getFormat(cl) )
168                                        .append("</tbody>").append( CR );
169                        }
170                }
171
172                if( footerFormat != null ) {
173                        // 6.3.9.0 (2015/11/06) 引数にTableFormatterを渡して、処理の共有化を図る。
174                        out.append( getTableFoot( footerFormat ) );
175                }
176
177                out.append("</table>").append( CR )
178                        .append( getScrollBarEndDiv() )
179                        .append( getParameterTag() );
180
181                return out.toString();
182        }
183
184        /**
185         * フォーマットを設定します。
186         *
187         * @og.rev 8.5.5.1 (2024/02/29) switch文にアロー構文を使用
188         *
189         * @param       list    TableFormatterのリスト
190         */
191        @Override
192        public void setFormatterList( final List<TableFormatter> list ) {               // 4.3.3.6 (2008/11/15) Generics警告対応
193                bodyFormats = new TableFormatter[BODYFORMAT_MAX_COUNT];
194
195                bodyFormatsCount = 0;
196                // 7.2.9.4 (2020/11/20) PMD:This for loop can be replaced by a foreach loop
197                for( final TableFormatter format : list ) {
198//              for( int i=0; i<list.size(); i++ ) {
199//                      final TableFormatter format = list.get( i );            // 4.3.3.6 (2008/11/15) Generics警告対応
200                        // 8.5.5.1 (2024/02/29) switch文にアロー構文を使用
201//                      switch( format.getFormatType() ) {
202//                              case TYPE_HEAD : headerFormat = format; break;
203//                              case TYPE_BODY : bodyFormats[bodyFormatsCount++] = format; break;
204//                              case TYPE_FOOT : footerFormat = format; break;
205//                              default : final String errMsg = "FormatterType の定義外の値が指定されました。";
206//                              // 4.3.4.4 (2009/01/01)
207//                              throw new HybsSystemException( errMsg );
208//                      }
209                        switch( format.getFormatType() ) {
210                                case TYPE_HEAD -> headerFormat = format;
211                                case TYPE_BODY -> bodyFormats[bodyFormatsCount++] = format;
212                                case TYPE_FOOT -> footerFormat = format;
213                                default -> {
214                                        final String errMsg = "FormatterType の定義外の値が指定されました。";
215                                        // 4.3.4.4 (2009/01/01)
216                                        throw new HybsSystemException( errMsg );
217                                }
218                        }
219                }
220        }
221
222        /**
223         * フォーマッターが設定されていない場合は、DBTableModelの情報からデフォルトの
224         * フォーマッターを作成します。
225         *
226         * @og.rev 4.3.3.6 (2008/11/15) columnDisplay,noDisplay対応
227         * @og.rev 4.3.5.0 (2008/02/01) 全展開コントロール用カラムへの対応
228         */
229        private void makeDefaultFormat() {
230                final String[] clms = getDBTableModel().getNames();
231                final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE )
232                                        .append( "<tr>" );
233                for( int i=0; i<clms.length; i++ ) {
234                        if( isColumnDisplay( i ) && i != expCtrlClmPos ) {                              // 4.3.3.6 (2008/11/15) 4.3.5.0 (2008/02/01)
235                                buf.append( "<td>[" ).append( clms[i] ).append( "]</td>" );     // 6.4.4.2 (2016/04/01)
236                        }
237                }
238                buf.append( "</tr>" );
239
240                final TableFormatter formatter = new TableFormatter();
241                formatter.setFormat( buf.toString() );
242                formatter.setFormatType( FormatterType.TYPE_HEAD );
243
244                headerFormat = formatter;
245        }
246
247        /**
248         * フォーマットメソッドを使用できるかどうかを問い合わせます。
249         *
250         * @return      フォーマットメソッドを使用できるか
251         */
252        @Override
253        public boolean canUseFormat() {
254                return true;
255        }
256
257        /**
258         * 初期パラメーターを設定します。
259         *
260         * @og.rev 4.3.3.0 (2008/10/01) 初期全展開の属性追加
261         * @og.rev 4.3.5.0 (2008/02/01) 全展開時の状態をコントロールするためのフラグを追加
262         * @og.rev 8.0.3.0 (2021/12/17) JSPIMG の連結に、StringUtil.urlAppend を使用する。
263         */
264        private void initParam() {
265                final String[] tmp      = StringUtil.csv2Array( getParam( ViewAjaxTreeTableParam.CHILD_SEARCH_KEYS, "" ) );
266                childSearchKeys = new int[tmp.length];
267                for( int i=0; i<tmp.length; i++ ) {
268                        childSearchKeys[i] = getDBTableModel().getColumnNo( tmp[i] );
269                }
270                childSearchJsp  = getParam( ViewAjaxTreeTableParam.CHILD_SEARCH_JSP, "getChildTag.jsp" );
271                levelClm                = getParam( ViewAjaxTreeTableParam.LVL_CLM_KEY, "LVL" );
272                levelClmPos             = getDBTableModel().getColumnNo( levelClm );
273//              imgCollapsed    = getParam( ViewAjaxTreeTableParam.IMG_COLLAPSED, "collapsed.gif" );
274//              imgExpanded     = getParam( ViewAjaxTreeTableParam.IMG_EXPANDED, "expanded.gif" );
275//              imgNoSub        = getParam( ViewAjaxTreeTableParam.IMG_NO_SUB, "nosub.gif" );
276                imgCollapsed    = StringUtil.urlAppend( JSPIMG, getParam( ViewAjaxTreeTableParam.IMG_COLLAPSED, "collapsed.gif" ) );    // 8.0.3.0 (2021/12/17)
277                imgExpanded     = StringUtil.urlAppend( JSPIMG, getParam( ViewAjaxTreeTableParam.IMG_EXPANDED, "expanded.gif" ) );              // 8.0.3.0 (2021/12/17)
278                imgNoSub        = StringUtil.urlAppend( JSPIMG, getParam( ViewAjaxTreeTableParam.IMG_NO_SUB, "nosub.gif" ) );                   // 8.0.3.0 (2021/12/17)
279                expandAll               = Boolean.valueOf( getParam( ViewAjaxTreeTableParam.EXPAND_ALL, "false" ) );                                                    // 4.3.2.0 (2008/09/11)
280                childViewStartNo= Integer.parseInt( getParam( ViewAjaxTreeTableParam.CHILD_VIEW_START_NO, "-1" ) );                                             // 6.0.2.4 (2014/10/17) メソッド間違い
281                final String expCtrlClm = getParam( ViewAjaxTreeTableParam.EXPAND_CONTROL_CLM_KEY, "EXPAND_CONTROL" );                                  // 4.3.5.0 (2008/02/01)
282                expCtrlClmPos   = getDBTableModel().getColumnNo( expCtrlClm, false );
283        }
284
285        /**
286         * JavaScriptに渡すためのパラメータをhiddenタグで出力します。
287         *
288         * @og.rev 4.3.3.0 (2008/10/01) 初期全展開対応
289         * @og.rev 4.3.5.0 (2008/02/01) 全展開時の状態をコントロールするためのフラグを追加
290         * @og.rev 6.4.2.0 (2016/01/29) alt属性にtitle属性を追記。
291         * @og.rev 6.4.3.4 (2016/03/11) forループを、2回まわさずに、1回にまとめます。
292         * @og.rev 8.0.3.0 (2021/12/17) JSPIMG の連結に、StringUtil.urlAppend を使用する。
293         * @og.rev 8.5.3.0 (2023/09/08) DynamicAttributes対応
294         *
295         * @param       row     行番号
296         * @return      HTMLタグ
297         * @og.rtnNotNull
298         */
299        private String getLvlClmTag( final int row ) {
300                // 6.4.2.0 (2016/01/29) ソースを見ていたら、共通値が結構あったので、再利用します。
301                final String lvlClmVal = getValue( row, levelClmPos );
302
303                // 6.4.3.4 (2016/03/11) forループを、2回まわさずに、1回にまとめます。
304                final StringBuilder keys = new StringBuilder( BUFFER_MIDDLE ).append( "command," ).append( levelClm );
305                final StringBuilder vals = new StringBuilder( BUFFER_MIDDLE ).append( "NEW,"     ).append( lvlClmVal );
306
307                // 7.2.9.4 (2020/11/20) PMD:This for loop can be replaced by a foreach loop
308                for( final int clm : childSearchKeys ) {
309//              for( int i=0; i<childSearchKeys.length; i++ ) {
310//                      final int clm = childSearchKeys[i];
311                        keys.append( ',' ).append( getColumnName( clm ) );              // 6.0.2.5 (2014/10/31) char を append する。
312                        vals.append( ',' ).append( getValue( row, clm ) );              // 6.0.2.5 (2014/10/31) char を append する。
313                }
314
315                // 6.3.9.1 (2015/11/27) Found 'DD'-anomaly for variable(PMD)
316                final String imgsrc ;
317                final StringBuilder clazz = new StringBuilder( BUFFER_MIDDLE );
318                clazz.append( "lvlctl unreplaceable" );
319                if( expandAll ) { // 4.3.3.0 (2008/10/01)
320                        if( row == getRowCount() - 1
321                                        || Integer.parseInt( lvlClmVal ) >= Integer.parseInt( getValue( row+1, levelClmPos ) ) ) {      // 6.4.2.0 (2016/01/29)
322                                final boolean isExp = expCtrlClmPos > -1 && StringUtil.nval( getValue( row, expCtrlClmPos ), false ) ;
323                                if( isExp ) {
324//                                      imgsrc = JSPIMG + imgCollapsed;
325                                        imgsrc = imgCollapsed;                                  // 8.0.3.0 (2021/12/17)
326                                }
327                                else {
328//                                      imgsrc = JSPIMG + imgNoSub;
329                                        imgsrc = imgNoSub;                                              // 8.0.3.0 (2021/12/17)
330                                        clazz.append( " fetched nosub" );
331                                }
332                        }
333                        else {
334//                              imgsrc = JSPIMG + imgExpanded;
335                                imgsrc = imgExpanded;                                           // 8.0.3.0 (2021/12/17)
336                                clazz.append( " fetched expanded" );
337                        }
338                }
339                else {
340//                      imgsrc = JSPIMG + imgCollapsed;
341                        imgsrc = imgCollapsed;                                                  // 8.0.3.0 (2021/12/17)
342                }
343
344                // 6.4.2.0 (2016/01/29) alt属性にtitle属性を追記。
345                final String tag = new TagBuffer( "img" )                       // 6.1.1.0 (2015/01/17) refactoring. 連結記述
346                        .add( "class"   , clazz.toString() )
347                        .add( "src"             , imgsrc )
348                        .add( "alt"             , "Level " + lvlClmVal )                // 6.4.2.0 (2016/01/29) 共通値を設定する。
349                        .add( "title"   , "Level " + lvlClmVal )                // 6.4.2.0 (2016/01/29) alt属性にtitle属性を追記。
350                        .add( "p_lvl"   , lvlClmVal )                                   // 6.4.2.0 (2016/01/29) 共通値を設定する。8.5.3.0 (2023/09/08) 先頭に"p_"付与
351                        .add( "p_keys"  , keys.toString() )                             // 8.5.3.0 (2023/09/08) 先頭に"p_"付与
352                        .add( "p_vals"  , vals.toString() )                             // 8.5.3.0 (2023/09/08) 先頭に"p_"付与
353                        .makeTag();
354
355                return getRendererValue( row, levelClmPos ) + tag;
356        }
357
358        /**
359         * JavaScriptに渡すためのパラメーターをhiddenタグをして出力します。
360         *
361         * @og.rev 8.0.3.0 (2021/12/17) JSPIMG の連結に、StringUtil.urlAppend を使用する。
362         *
363         * @return hiddenタグ
364         * @og.rtnNotNull
365         */
366        private String getParameterTag() {
367                final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE )
368                        .append( XHTMLTag.hidden( ViewAjaxTreeTableParam.CHILD_SEARCH_JSP, childSearchJsp ) )
369//                      .append( XHTMLTag.hidden( ViewAjaxTreeTableParam.IMG_COLLAPSED, JSPIMG + imgCollapsed ) )
370//                      .append( XHTMLTag.hidden( ViewAjaxTreeTableParam.IMG_EXPANDED,  JSPIMG + imgExpanded ) )
371//                      .append( XHTMLTag.hidden( ViewAjaxTreeTableParam.IMG_NO_SUB,    JSPIMG + imgNoSub ) );
372                        .append( XHTMLTag.hidden( ViewAjaxTreeTableParam.IMG_COLLAPSED, imgCollapsed ) )        // 8.0.3.0 (2021/12/17)
373                        .append( XHTMLTag.hidden( ViewAjaxTreeTableParam.IMG_EXPANDED,  imgExpanded  ) )        // 8.0.3.0 (2021/12/17)
374                        .append( XHTMLTag.hidden( ViewAjaxTreeTableParam.IMG_NO_SUB,    imgNoSub     ) );       // 8.0.3.0 (2021/12/17)
375                return buf.toString();
376        }
377}