001    /*
002      Copyright (C) 2002-2003 Laurent Martelli <laurent@aopsys.com>
003      
004      This program is free software; you can redistribute it and/or modify
005      it under the terms of the GNU Lesser General Public License as
006      published by the Free Software Foundation; either version 2 of the
007      License, or (at your option) any later version.
008    
009      This program is distributed in the hope that it will be useful,
010      but WITHOUT ANY WARRANTY; without even the implied warranty of
011      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
012      GNU Lesser General Public License for more details.
013    
014      You should have received a copy of the GNU Lesser General Public License
015      along with this program; if not, write to the Free Software
016      Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
017    
018    package org.objectweb.jac.aspects.gui.web;
019    
020    import java.io.IOException;
021    import java.io.PrintWriter;
022    import java.util.Collection;
023    import java.util.HashMap;
024    import java.util.Hashtable;
025    import java.util.Iterator;
026    import java.util.List;
027    import java.util.Map;
028    import java.util.Vector;
029    import org.apache.log4j.Logger;
030    import org.objectweb.jac.aspects.gui.*;
031    import org.objectweb.jac.core.Naming;
032    import org.objectweb.jac.core.rtti.ClassItem;
033    import org.objectweb.jac.core.rtti.CollectionItem;
034    import org.objectweb.jac.core.rtti.FieldItem;
035    import org.objectweb.jac.core.rtti.MemberItem;
036    import org.objectweb.jac.core.rtti.MethodItem;
037    import org.objectweb.jac.util.ExtArrays;
038    
039    public class Table extends AbstractCollection
040        implements HTMLViewer, TableListener
041    {
042        static Logger logger = Logger.getLogger("gui.table");
043        static Logger loggerEvents = Logger.getLogger("gui.events");
044    
045        HTMLViewer[] cellViewers;
046        ExtendedTableModel tableModel;
047    
048        public Table(ViewFactory factory, DisplayContext context,
049                     CollectionItem collection, Object substance,
050                     ExtendedTableModel model, 
051                     org.objectweb.jac.aspects.gui.CollectionItemView itemView) {
052            super(factory,context,collection,substance,model,itemView);
053            this.tableModel = model;
054            this.multiLineCollection = itemView.getMultiLineCollection();
055            this.groupBy = itemView.getGroupBy();
056            if (model!=null) {
057                this.cellViewers = new HTMLViewer[tableModel.getHeaders().length];
058                setCellRenderers();
059            }
060        }
061    
062        CollectionItem multiLineCollection;
063        FieldItem groupBy;
064    
065        public void setColumnsInfo(String[] headers, FieldItem[] fields, 
066                                   ClassItem[] classes, ClassItem[] viewerClasses) {
067            if (!(headers.length==classes.length && 
068                  classes.length==viewerClasses.length &&
069                  viewerClasses.length==fields.length)) {
070                throw new RuntimeException("headers, fields, classes and "+
071                                           "viewerClasses must be the same size");
072            }
073            this.cellViewers = new HTMLViewer[headers.length];
074        }
075    
076        protected void setCellRenderers() {
077            MemberItem[] members = tableModel.getMembers();
078            String[] headers = tableModel.getHeaders();
079    
080            for (int i=0; i<members.length; i++) {
081                if (members[i] instanceof FieldItem) {
082                    cellViewers[i] = 
083                        (HTMLViewer)tableModel.getCellRenderer(this,i,factory,context);
084                } else if (members[i] instanceof MethodItem) {
085                    MethodItem method = (MethodItem)members[i];
086                    try {
087                        cellViewers[i] = (HTMLViewer)factory.createView(
088                            method.getName(),
089                            "Method",
090                            new Object[] {substance,method},context);
091                    } catch (Exception e) {
092                        logger.error("Failed to instanciate TableCellRenderer "+
093                                     method.getName()+" for column "+headers[i],e);
094                    }
095                }
096                if (cellViewers[i] instanceof FieldView)
097                    ((FieldView)cellViewers[i]).setAutoUpdate(false);
098                if (cellViewers[i] instanceof CollectionView)
099                    ((CollectionView)cellViewers[i]).setEditor(false);
100            }      
101        }
102    
103        public void sort() {
104            if (sorter!=null)
105                sorter.sort(this);
106        }
107    
108        protected boolean showRefreshButton() {
109            return super.showRefreshButton() 
110                || (filterEditors!=null && !filterEditors.isEmpty());
111        }
112    
113        public void onRefreshCollection() {
114            if (filterEditors != null) {
115                JacRequest request = ((WebDisplay)context.getDisplay()).getRequest();        
116                Iterator it = filterEditors.values().iterator();
117                while (it.hasNext()) {
118                    ObjectChooser chooser = (ObjectChooser)it.next();
119                    chooser.readValue(request.getParameter(chooser.getLabel()));
120                }
121            }
122            checkRange();
123            super.onRefreshCollection();
124        }
125    
126        protected void init() {
127            if (model!=null) {
128                sorter = ((ExtendedTableModel)model).getSorter();
129                filter = ((ExtendedTableModel)model).getFilter();
130                if (filter!=null) {
131                    filterEditors = filter.buildFilterEditors(factory,context);
132                    Iterator it = filterEditors.values().iterator();
133                    while (it.hasNext()) {
134                        ObjectChooser chooser = (ObjectChooser)it.next();
135                        chooser.setParentView(this);
136                    }
137                }
138            }
139        }
140    
141        // FieldItem -> HTMLViewer
142        Map filterEditors;
143    
144        /**
145         * Removes editors of embedded added object
146         */
147        protected void clearFilterEditors() {
148            if (filterEditors!=null) {
149                Iterator it = filterEditors.values().iterator();
150                while (it.hasNext()) {
151                    FieldEditor editor = (FieldEditor)it.next();
152                    context.removeEditor(editor);
153                }
154                filterEditors.clear();
155            }
156        }
157    
158        /**
159         * Sorts the collection.
160         *
161         * @param column the index of the column used to sort
162         */
163        public void sort(int column) {
164            sorter.sortByColumn(column, true);
165        }
166    
167        // HTMLViewer interface
168    
169        public void genHTML(PrintWriter out) throws IOException {
170            MethodItem adder = collection.getAdder();
171            boolean embeddedAdder = 
172                adder!=null && GuiAC.getView(adder,itemView.getName()).isEmbedded() && 
173                GuiAC.isAutoCreate(collection);
174            if (model.getRowCount()==0 && !embeddedAdder) {
175                out.println("none");
176            }
177    
178            genHeader(out);
179    
180            if (model.getRowCount()==0 && !embeddedAdder && filter==null) {
181                return;
182            }
183    
184            logger.debug(itemView+".Embedded editors="+itemView.isEmbeddedEditors());
185    
186            String[] headers = tableModel.getHeaders();
187    
188            out.println("<table class=\"table\">");
189            out.println("  <thead>");
190            out.println("    <tr>");
191            if (showRowNumbers) {
192                out.println("      <th style=\"width:"+
193                            ((""+model.getRowCount()).length())+"ex\" class=\"empty\"></th>");
194            }
195            for (int i=0; i<headers.length; i++) {
196                if (headers[i]!=null) {
197                    out.print("      <th>");
198                    out.print(sortLink(i,headers[i]));
199                    out.println("</th>");
200                } else {
201                    out.print("      <th class=\"empty\"></th>");
202                }
203            }
204            if (viewableItems)
205                out.println("      <th style=\"width:1px\" class=\"empty\"></th>"); // view button
206            if (GuiAC.isRemovable(collection))
207                out.println("      <th style=\"width:1px\" class=\"empty\"></th>"); // remove button
208            out.println("    </tr>");
209            out.println("  </thead>");
210    
211            boolean bodyOpened = false;
212    
213            Object defaultsObject = null;
214       
215            // Column filters
216            if (filter!=null) {
217                genColumnFilters(out);
218            }
219    
220            bodyOpened = false;
221            // Default value editors in the table
222            if (GuiAC.hasEditableDefaultValues(collection) && embeddedAdder) {
223                try {
224                    ClassItem componentType = collection.getComponentType();
225                    defaultsObject = Naming.getObject("defaults."+collection.getLongName());
226                    if (defaultsObject==null) {
227                        Naming.setName("defaults."+collection.getLongName());
228                        defaultsObject = componentType.newInstance();
229                        clearDefaultEditors();
230                    }
231                    out.println("  <tbody class=\"defaultObject\">");
232                    out.println("    <tr class=\"vspace\"></tr>");
233                    out.println("    <tr class=\"defaultObject\">");
234                    FieldItem[] defaultsFields = GuiAC.getDefaultsAttributesOrder(componentType);
235                    if (showRowNumbers) {
236                        out.println("      <td></td>");
237                    }
238                    for (int col=0; col<tableModel.getColumnCount(); col++) {
239                        out.print("      <td>");
240                        MemberItem member = tableModel.getMembers()[col];
241                        if (defaultsFields!=null && !ExtArrays.contains(defaultsFields,member)) {
242                            out.println("</td>");
243                            continue;
244                        }
245                        GuiAC.pushGraphicContext(member);
246                        try {
247                            if (member instanceof FieldItem && 
248                                !(member instanceof CollectionItem)) 
249                            {
250                                MethodItem setter = ((FieldItem)member).getSetter();
251                                if (GuiAC.isCreationAttribute((FieldItem)member) &&
252                                    setter!=null) {
253                                    // <BAD>WE SHOULD ONLY BUILD THE EDITORS ONCE</BAD>
254                                    FieldEditor editor = GenericFactory.getEditorComponent(
255                                        factory, context, defaultsObject, setter, 0, true, null);
256                                    defaultsEditors.add(editor);
257                                    context.addEditor(editor);
258                                    ((HTMLViewer)editor).genHTML(out);
259                                    editor.setEmbedded(true);
260                                    //editorContainer.addEditor(editor);
261                                    //container.addView(editor);
262                                }
263                            } else if (member instanceof CollectionItem) {
264                                if (GuiAC.isCreationAttribute((FieldItem)member)) {
265                                    CollectionItem collection = (CollectionItem)member;
266                                    HTMLViewer view = (HTMLViewer)
267                                        GenericFactory.getCollectionPane(
268                                            factory,context,defaultsObject,null,null,collection);
269                                    view.genHTML(out);
270                                }
271                            }
272                        } finally {
273                            GuiAC.popGraphicContext();
274                        }
275                        out.println("</td>");
276                    }
277                    out.println("      <td>"+eventURL("set","onSetDefaults","")+"</td>");
278                    out.println("    </tr>");
279                    out.println("    <tr class=\"vspace\"></tr>");
280                    out.println("  </tbody>");
281                } catch (Exception e) {
282                    e.printStackTrace();
283                }
284            }
285    
286            logger.debug("multiLineCollection="+
287                         (multiLineCollection!=null?multiLineCollection.getName():"none"));
288    
289            // Actual rows
290            Object currentGroup = null;
291            int groupSpan = 1;
292            MemberItem[] members = tableModel.getMembers();
293            boolean first = true; // First line of multi-line collection or groupBy?
294            int groupIndex = 0;
295            for (int index=startIndex; 
296                 (!split || index<startIndex+rowsPerPage) && index<model.getRowCount(); 
297                 index++) 
298            {
299                Object substance = tableModel.getObject(index);
300                if (!bodyOpened) {
301                    openTBody(out);
302                    bodyOpened = true;
303                }
304                if (multiLineCollection!=null) {
305                    first = true; // First line of multi-line collection ?
306                    Collection multi = multiLineCollection.getActualCollectionThroughAccessor(substance);
307                    String rowspan = " rowspan=\""+multi.size()+"\"";
308                    Iterator it = multi.iterator();
309                    if (it.hasNext()) {
310                        while (it.hasNext()) {
311                            if (first)
312                                groupIndex++;
313                            openRow(out,index,groupIndex%2==0);
314                            Object multiSubstance = it.next();
315                            if (first && showRowNumbers) {
316                                out.println("      <td class=\"index\""+rowspan+">"+(index+1)+"</td>");
317                            }
318                            for (int col=0; col<tableModel.getColumnCount(); col++) {
319                                MemberItem member = members[col];
320                                if (member instanceof FieldItem 
321                                    && ((FieldItem)member).startsWith(multiLineCollection)) {
322                                    FieldItem multiField = 
323                                        ((FieldItem)member).getRelativeField(multiLineCollection);
324                                    genCell(out,index,col,multiSubstance,
325                                            multiField,
326                                            multiField.getThroughAccessor(multiSubstance),
327                                            "");
328                                } else if (first) {
329                                    genCell(out,index,col,substance,member,
330                                            tableModel.getValueAt(index,col),rowspan);
331                                }
332                            }
333                            if (first) {
334                                if (viewableItems)
335                                    genViewCell(out,index,rowspan);
336                                genRemoveCell(out,index,rowspan);
337                            }
338                            out.println("    </tr>");
339                            first = false;
340                        }
341                    } else {
342                        genRow(out,index,substance,members);
343                    }
344                } else if (groupBy!=null) {
345                    Object groupValue = groupBy.getThroughAccessor(substance);
346                    if (currentGroup!=groupValue) {
347                        logger.debug(index+": New group "+groupValue);
348                        currentGroup = groupValue;
349                        groupSpan = 1;
350                        first = true;
351                        for (int i=index+1; 
352                             (!split || i<startIndex+rowsPerPage) && i<model.getRowCount(); 
353                             i++) { 
354                            if (groupBy.getThroughAccessor(tableModel.getObject(i))!=currentGroup)
355                                break;
356                            groupSpan++;
357                        }
358                    }
359                    if (first)
360                        groupIndex++;
361                    openRow(out,index,groupIndex%2==1);
362                    if (showRowNumbers) {
363                        out.println("      <td class=\"index\">"+(index+1)+"</td>");
364                    }
365                    for (int col=0; col<tableModel.getColumnCount(); col++) {
366                        MemberItem member = members[col];
367                        if (member instanceof FieldItem 
368                            && (((FieldItem)member).startsWith(groupBy) || member==groupBy)) {
369                            if (first) {
370                                String rowspan = groupSpan>1 ? (" rowspan=\""+groupSpan+"\"") : "";
371                                genCell(out,index,col,substance,member,
372                                        tableModel.getValueAt(index,col),rowspan);
373                            }
374                        } else {
375                            genCell(out,index,col,substance,member,
376                                    tableModel.getValueAt(index,col),"");
377                        }
378                    }
379                    if (viewableItems)
380                        genViewCell(out,index,"");
381                    genRemoveCell(out,index,"");
382                    out.println("    </tr>");
383                    first = false;
384                } else {
385                    genRow(out,index,substance,members);
386                }
387    
388            }
389            if (bodyOpened) {
390                out.println("  </tbody>");
391                bodyOpened = false;
392            }
393    
394            // Embedded adder in the table
395            String focusId = null; // Id of HTML element to focus (if any)
396            if (GuiAC.isAddable(substance,collection)) {
397                try {
398                    ClassItem componentType = collection.getComponentType();
399                    if (embeddedAdder) {
400                        if (addedObject==null) {
401                            addedObject = componentType.newInstance();
402                            EventHandler.initAutocreatedObject(
403                                addedObject,substance,collection);
404                            initAddedObject(addedObject,defaultsObject);
405                            clearEmbeddedEditors(true);
406                        }
407                        out.println("  <tbody>");
408                        out.println("    <tr class=\"vspace\"></tr>");
409                        out.println("    <tr class=\"addedObject\">");
410                        if (showRowNumbers) {
411                            out.println("      <td></td>");
412                        }
413                        for (int col=0; col<tableModel.getColumnCount(); col++) {
414                            out.print("      <td>");
415                            MemberItem member = tableModel.getMembers()[col];
416                            GuiAC.pushGraphicContext(member);
417                            String keyPressEvent = 
418                                "return commitFormOnEnter(event,this,'event=onAddEmbedded&source="+getId()+"')\"";
419                            try {
420                                if (member instanceof FieldItem && 
421                                    !(member instanceof CollectionItem))
422                                {
423                                    MethodItem setter = ((FieldItem)member).getSetter();
424                                    if (GuiAC.isCreationAttribute((FieldItem)member) && 
425                                        setter!=null) {
426                                        FieldEditor editor = GenericFactory.getEditorComponent(
427                                            factory, context, addedObject, setter, 0, true, null);
428                                        if (focusId==null)
429                                            focusId = editor.getLabel();
430                                        embeddedEditors.add(editor);
431                                        ((HTMLEditor)editor).setAttribute("onkeypress",keyPressEvent);
432                                        ((HTMLViewer)editor).genHTML(out);
433                                        editor.setEmbedded(true);
434                                        context.addEditor(editor);
435                                        //editorContainer.addEditor(editor);
436                                        //container.addView(editor);
437                                    }
438                                } else if (member instanceof CollectionItem) {
439                                    if (GuiAC.isCreationAttribute((FieldItem)member)) {
440                                        CollectionItem coll = (CollectionItem)member;
441                                        CollectionItem index = (CollectionItem)
442                                            coll.getComponentType().getAttribute(GuiAC.INDEXED_FIELD_SELECTOR);
443                                        if (index==null) {
444                                            HTMLViewer view = (HTMLViewer)
445                                                GenericFactory.getCollectionPane(
446                                                    factory,context,addedObject,null,null,coll);
447                                            view.genHTML(out);
448                                        } else {
449                                            FieldEditor editor = factory.createEditor(
450                                                "editor "+Naming.getName(substance)+"."+coll.getName(),
451                                                "IndicesSelector",
452                                                new Object[] {coll,addedObject,
453                                                              new ListModel(coll,addedObject),
454                                                              GuiAC.getView(coll,itemView.getName())},
455                                                context);
456                                            embeddedEditors.add(editor);
457                                            ((HTMLEditor)editor).setAttribute("onkeypress",keyPressEvent);
458                                            ((HTMLViewer)editor).genHTML(out);
459                                            editor.setEmbedded(true);
460                                            context.addEditor(editor);
461                                        }
462                                    }
463                                }
464                            } catch(Exception e) {
465                                logger.error("Failed to gen HTML for embedded object to add, column "+
466                                             tableModel.getHeaders()[col],e);
467                            } finally {
468                                GuiAC.popGraphicContext();
469                            }
470                            out.println("</td>");
471                        }
472                        out.println("      <td>"+eventURL(GuiAC.getLabelAdd(),"onAddEmbedded","")+"</td>");
473                        out.println("    </tr>");
474                        out.println("  </tbody>");
475                    }
476                } catch (Exception e) {
477                    e.printStackTrace();
478                }
479            }
480    
481            FieldItem additionalRow = itemView.getAdditionalRow();
482            if (additionalRow!=null) {
483                out.println("  <tbody class=\"additionalRow\">");
484                out.println("    <tr>");
485                if (showRowNumbers) {
486                    out.println("      <td class=\"empty\"></td>");
487                }
488                Object row = additionalRow.getThroughAccessor(substance);
489                for (int col=0; col<tableModel.getColumnCount(); col++) {
490                    MemberItem member = members[col];
491                    if (member instanceof FieldItem && row!=null) {
492                        genCell(out,-1,col,row,member,((FieldItem)member).getThroughAccessor(row),"");
493                    } else {
494                        genCell(out,-1,col,row,member,null,"");
495                    }
496                }
497                if (viewableItems) {
498                    out.println("      <td class=\"empty\"></td>");
499                }
500                if (GuiAC.isRemovable(collection)) {
501                    out.println("      <td class=\"empty\"></td>");
502                }
503                out.println("    </tr>");            
504                out.println("  </tbody>");
505            }
506    
507            out.println("</table>");
508    
509            if (focusId!=null) {
510                out.println("<script type=\"text/javascript\">"+
511                            "element=getElementById('"+focusId+"');"+
512                            "element.focus();"+
513                            "window.scroll(0,10000);"+
514                            "</script>");
515            }
516        }
517    
518        protected void genRow(PrintWriter out, int index, Object substance,
519                              MemberItem[] members) {
520            openRow(out,index,index%2==0);
521            if (showRowNumbers) {
522                out.println("      <td class=\"index\">"+(index+1)+"</td>");
523            }
524            for (int col=0; col<tableModel.getColumnCount(); col++) {
525                MemberItem member = members[col];
526                if (multiLineCollection!=null 
527                    && member instanceof FieldItem 
528                    && ((FieldItem)member).startsWith(multiLineCollection)) {
529                    out.println("      <td></td>");
530                } else {
531                    genCell(out,index,col,substance,member,
532                            tableModel.getValueAt(index,col),"");
533                }
534            }
535            if (viewableItems)
536                genViewCell(out,index,"");
537            genRemoveCell(out,index,"");
538            out.println("    </tr>");
539        }
540    
541        protected void genCell(PrintWriter out, int index, int col, 
542                               Object substance, MemberItem member, Object value,
543                               String rowspan) 
544        {
545            out.print("      <td"+rowspan+">");
546            try {
547                HTMLViewer viewer = null;
548                if (member instanceof FieldItem) {
549                    FieldItem field = (FieldItem)member;
550                    if (itemView.isEmbeddedEditors(field))
551                        viewer = getFieldEditor(field,substance);
552                }
553                if (viewer!=null) {
554                    viewer.genHTML(out);
555                } else {
556                    if (cellViewers[col] instanceof Method) {
557                        out.println("      <a href=\""+eventURL("onTableInvoke")+
558                                    "&index="+index+"&method="+
559                                    (member).getName()+"\">"+
560                                    (GuiAC.getLabel(member))+"</a>");
561                      
562                    } else {
563                        FieldView cellViewer = (FieldView)cellViewers[col];
564                        if (cellViewer!=null) {
565                            if (cellViewer instanceof TableCellViewer) {
566                                ((TableCellViewer)cellViewer).setRow(index);
567                                ((TableCellViewer)cellViewer).setColumn(col);
568                            }
569                            if (cellViewer instanceof LinkGenerator) {
570                                ((LinkGenerator)cellViewer).setEnableLinks(itemView.areLinksEnabled());
571                            }
572                            if (cellViewer instanceof CollectionView) 
573                                ((CollectionView)cellViewer).updateModel(substance);
574                            else {
575                                cellViewer.setSubstance(substance);
576                                cellViewer.setValue(value);
577                            }
578                            if (cellViewer instanceof ReferenceView) {
579                                ((ReferenceView)cellViewer).setEventURL(
580                                    eventURL("onCellSelection")+"&row="+index+"&col="+col);
581                            }
582                            ((HTMLViewer)cellViewer).genHTML(out);
583                        } else {
584                            if (value!=null) {
585                                out.print(GuiAC.toString(value));
586                            }
587                        }
588                    }
589                }
590            } catch (Exception e) {
591                logger.error(
592                    "Failed to genHTML for cell["+
593                    tableModel.getHeaders()[col]+","+index+
594                    "] of "+collection.getName(),e);
595            }
596            out.println("</td>");
597        }
598    
599        protected void genColumnFilters(PrintWriter out) throws IOException {
600            out.println("  <tbody class=\"filters\">");
601            out.println("    <tr class=\"filters\">");
602            if (showRowNumbers) {
603                out.println("      <td></td>");
604            }
605            for (int col=0; col<tableModel.getColumnCount(); col++) {
606                MemberItem member = tableModel.getMembers()[col];
607                if (member instanceof FieldItem &&
608                    filter.isFiltered((FieldItem)member)) {
609                    out.print("      <td>");
610                    ((HTMLViewer)filterEditors.get(member)).genHTML(out);
611                    out.println("      </td>");
612                } else {
613                    out.println("      <td></td>");
614                }
615            }
616            out.println("    </tr>");
617            out.println("  </tbody>");            
618        }
619    
620        protected void genViewCell(PrintWriter out, int index, String rowspan) {
621            out.print("      <td"+rowspan+">");
622            if (collection.getAttribute(GuiAC.NEW_WINDOW)!=null) {
623                out.print("<a target=\""+collection.getName()+"\""+
624                          " href=\""+eventURL("onView")+
625                                  "&index="+index+"&\">"+
626                          "details</a>");
627            } else {
628                out.print(viewLink(index));
629            }
630            out.println("</td>");
631        }
632    
633        protected void genRemoveCell(PrintWriter out, int index, String rowspan) {
634            if (GuiAC.isRemovable(collection)) {
635                out.print("<td"+rowspan+">");
636                if (isEditor)
637                    out.print(removeLink(index));
638                out.println("</td>");
639            }
640        }
641    
642        protected boolean viewOnDoubleClick = true;
643        /**
644         * Print opening tag for a row
645         *
646         * @param out where to wrte the HTML code
647         * @param index index of the row to open
648         * @param even wether the <tr> should have the "even" or "odd" class
649         */
650        protected void openRow(PrintWriter out, int index, boolean even) {
651            String event = "";
652            String cls = "";
653            if (viewOnDoubleClick && viewableItems) {
654                event = "ondblclick=\"openURL('"+eventURL("onView")+"&index="+index+"')\"";
655                cls = " highlight";
656            }
657            if (selected==index)
658                out.println("    <tr class=\"selected"+cls+"\" "+event+">");
659            else
660                out.println("    <tr class=\""+
661                            (even?"even":"odd")+cls+"\" "+event+">");
662        }
663    
664        /**
665         * Print opening TBODY tag containg rows
666         * @param out
667         */
668        protected void openTBody(PrintWriter out) {
669            out.println("  <tbody"+((viewOnDoubleClick&&viewableItems)?" class=\"highlight\"":"")+">");
670        }
671    
672        /**
673         * Returns a cell editor for a field of an object. Editors are
674         * cached, so you'll always get the same object for the same field
675         * and substance, unless clearCellEditors() is called.
676         * @see #clearCellEditors()
677         */
678        protected HTMLViewer getFieldEditor(FieldItem field, Object substance) {
679            HTMLViewer editor = null;
680            Map fieldEditors = (Map)cellEditors.get(field);
681            if (fieldEditors!=null) {
682                editor = (HTMLViewer)fieldEditors.get(substance);
683            } else {
684                fieldEditors = new HashMap();
685                cellEditors.put(field,fieldEditors);
686            }
687            if (editor==null) {
688                if (!(field instanceof CollectionItem))
689                {
690                    MethodItem setter = field.getSetter();
691                    if (setter!=null) {
692                        editor = (HTMLViewer)GenericFactory.getEditorComponent(
693                            factory, context, field.getSubstance(substance), setter, 0, true, null);
694                    }
695                } else  {
696                    CollectionItem coll = (CollectionItem)field;
697                    CollectionItem index = (CollectionItem)
698                        coll.getComponentType().getAttribute(GuiAC.INDEXED_FIELD_SELECTOR);
699                    if (index==null) {
700                        editor = (HTMLViewer)GenericFactory.getCollectionPane(
701                            factory,context,substance,null,itemView,coll);
702                    } else {
703                        editor = (HTMLViewer)factory.createEditor(
704                            "editor "+Naming.getName(substance)+"."+coll.getName(),
705                            "IndicesSelector",
706                            new Object[] {coll,substance,
707                                          new ListModel(coll,substance),
708                                          GuiAC.getView(coll,itemView.getName())},
709                            context);
710                    }
711                }
712                ((View)editor).setParentView(this);
713                fieldEditors.put(substance,editor);
714                if (editor instanceof FieldEditor) {
715                    ((FieldEditor)editor).setEmbedded(true);
716                    context.addEditor((FieldEditor)editor);
717                }
718                if (editor instanceof ReferenceEditor) {
719                    ((ReferenceEditor)editor).setEditable(false);
720                }
721            }
722            if (editor instanceof HTMLEditor) {
723                ((HTMLEditor)editor).setAttribute("ondblclick","event.stopPropagation();");
724            }
725            logger.debug("editor["+field.getName()+","+substance+"] -> "+editor);
726            return editor;
727        }
728    
729        /**
730         * Removes editors of embedded added object
731         */
732        protected void clearEmbeddedEditors(boolean validate) {
733            Iterator it = embeddedEditors.iterator();
734            while (it.hasNext()) {
735                FieldEditor editor = (FieldEditor)it.next();
736                editor.close(validate);
737                context.removeEditor(editor);
738            }
739            embeddedEditors.clear();
740        }
741    
742        /**
743         * Removes editors of embedded added object
744         */
745        protected void clearDefaultEditors() {
746            Iterator it = defaultsEditors.iterator();
747            while (it.hasNext()) {
748                FieldEditor editor = (FieldEditor)it.next();
749                context.removeEditor(editor);
750            }
751            defaultsEditors.clear();
752        }
753    
754        // FieldItem -> (Object -> FieldEditor)
755        Hashtable cellEditors = new Hashtable();
756    
757        /**
758         * Removes editors of embedded added object
759         */
760        protected void clearCellEditors(boolean validate) {
761            Iterator it = cellEditors.values().iterator();
762            while (it.hasNext()) {
763                Iterator editors = ((Map)it.next()).values().iterator();
764                while (editors.hasNext()) {
765                    View editor = (View)editors.next();
766                    if (editor!=null)
767                        editor.close(validate);
768                    context.removeEditor(editor);
769                }
770            }
771            cellEditors.clear();
772        }
773    
774        public void close(boolean validate) {
775            super.close(validate);
776            clearEmbeddedEditors(validate);
777            clearDefaultEditors();
778            clearCellEditors(validate);
779            clearFilterEditors();
780        }
781    
782        /**
783         * Initialize fields of added object from one of defaultsObject
784         */
785        protected void initAddedObject(Object addedObject, Object defaultsObject) {
786            if (defaultsObject==null || addedObject==null) {
787                return;
788            }
789            for (int col=0; col<tableModel.getColumnCount(); col++) {
790                MemberItem member = tableModel.getMembers()[col];
791                if (member instanceof FieldItem && !(member instanceof CollectionItem)) {
792                    FieldItem field = (FieldItem)member;
793                    MethodItem setter = field.getSetter();
794                    if (GuiAC.isCreationAttribute(field) && setter!=null) {
795                        Object value = field.getThroughAccessor(defaultsObject);
796                        try {
797                            field.set(addedObject,value);
798                        } catch (Exception e) {
799                            logger.error("Failed to set default value for field "+field,e);
800                        }
801                    }
802                }
803            }
804        }
805    
806        public void onCellSelection(int row, int col) {
807            MemberItem[] members = tableModel.getMembers();
808            EventHandler.get().onSelection(context,(FieldItem)members[col],
809                                           tableModel.getObject(row,col),
810                                           null,null,true);
811        }
812    
813        List embeddedEditors = new Vector();
814        Object addedObject = null;
815    
816        public void onEmbeddedAddToCollection() {
817            JacRequest request = WebDisplay.getRequest();
818            loggerEvents.debug("onEmbeddedAddToCollection "+collection.getName()+" "+request);
819            for (int i=0; i<embeddedEditors.size(); i++) {
820                FieldEditor editor = (FieldEditor)embeddedEditors.get(i);
821                editor.close(true);
822            }
823            EventHandler.get().onInvoke(
824                context,
825                new InvokeEvent(
826                    this,
827                    substance,collection.getAdder(),
828                    new Object[] {addedObject}),
829                false,
830                null,null);
831            clearEmbeddedEditors(true);
832            /**
833               // onView(addedObject); 
834    
835               It does not work because 
836    
837               a) onInvoke() above is asynchronous and starts its own
838               thread, so we are not sure addedObject is added
839               "completely" (the table model may not be up to date for
840               instance)
841    
842               b) InvokeThread already calls WebDisplay.refresh(), so
843               calling it again in onView() will fail
844    
845            */
846            addedObject = null;
847        }
848       
849        List defaultsEditors = new Vector();
850       
851        public void onSetDefaults() {
852            loggerEvents.debug("onSetDefaults "+collection.getName());
853            initAddedObject(addedObject,
854                            Naming.getObject("defaults."+collection.getLongName()));
855            context.getDisplay().refresh();
856        }
857       
858        public void onHeaderClick(int column) {
859            SortCriteria criteria = sorter.getSortCriteria(column);
860            if (criteria!=null) {
861                if (criteria.isAscending()) {
862                    criteria.toggleAscending();
863                    sorter.sortByColumn(column,criteria.isAscending());
864                } else {
865                    sorter.sortByColumn(-1,criteria.isAscending());            
866                }
867            } else {
868                sorter.sortByColumn(column,true);
869            }
870            context.getDisplay().refresh();
871        }
872    
873        public View onRowEvent(int row, int col) {
874            FieldView cellViewer = 
875                (FieldView)tableModel.getCellRenderer(this,col,factory,context);
876            cellViewer.setValue(tableModel.getValueAt(row,col));
877            return cellViewer;
878        }
879    
880    }