001    // Copyright 2007, 2008, 2009, 2011 The Apache Software Foundation
002    //
003    // Licensed under the Apache License, Version 2.0 (the "License");
004    // you may not use this file except in compliance with the License.
005    // You may obtain a copy of the License at
006    //
007    //     http://www.apache.org/licenses/LICENSE-2.0
008    //
009    // Unless required by applicable law or agreed to in writing, software
010    // distributed under the License is distributed on an "AS IS" BASIS,
011    // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012    // See the License for the specific language governing permissions and
013    // limitations under the License.
014    
015    package org.apache.tapestry5.corelib.components;
016    
017    import org.apache.tapestry5.*;
018    import org.apache.tapestry5.annotations.*;
019    import org.apache.tapestry5.beaneditor.PropertyModel;
020    import org.apache.tapestry5.grid.ColumnSort;
021    import org.apache.tapestry5.grid.GridConstants;
022    import org.apache.tapestry5.grid.GridModel;
023    import org.apache.tapestry5.grid.GridSortModel;
024    import org.apache.tapestry5.internal.InternalConstants;
025    import org.apache.tapestry5.internal.TapestryInternalUtils;
026    import org.apache.tapestry5.ioc.Messages;
027    import org.apache.tapestry5.ioc.annotations.Inject;
028    import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
029    
030    import java.util.List;
031    
032    /**
033     * Renders out the column headers for the grid, including links (where appropriate) to control column sorting.
034     * 
035     * @tapestrydoc
036     */
037    @SupportsInformalParameters
038    @Events(InternalConstants.GRID_INPLACE_UPDATE + " (internal event)")
039    public class GridColumns
040    {
041        /**
042         * The object that provides access to bean and data models, which is typically the enclosing Grid component.
043         */
044        @Parameter(value = "componentResources.container")
045        private GridModel gridModel;
046    
047        /**
048         * If true, then the CSS class on each <TH> element will be omitted, which can reduce the amount of output
049         * from the component overall by a considerable amount. Leave this as false, the default, when you are leveraging
050         * the CSS to customize the look and feel of particular columns.
051         */
052        @Parameter
053        private boolean lean;
054    
055        /**
056         * Where to look for informal parameter Blocks used to override column headers.  The default is to look for such
057         * overrides in the GridColumns component itself, but this is usually overridden.
058         */
059        @Parameter("this")
060        private PropertyOverrides overrides;
061    
062        /**
063         * If not null, then each link is output as a link to update the specified zone.
064         */
065        @Parameter
066        private String zone;
067    
068        @SuppressWarnings("unused")
069        @Component(
070                parameters = { "event=sort", "disabled=sortDisabled", "context=columnContext", "class=sortLinkClass",
071                        "zone=inherit:zone" })
072        private EventLink sort, sort2;
073    
074        @Inject
075        @Path("${" + ComponentParameterConstants.GRIDCOLUMNS_ASCENDING_ASSET + "}")
076        private Asset ascendingAsset;
077    
078        @Inject
079        @Path("${" + ComponentParameterConstants.GRIDCOLUMNS_DESCENDING_ASSET + "}")
080        private Asset descendingAsset;
081    
082        @Inject
083        @Path("${" + ComponentParameterConstants.GRIDCOLUMNS_SORTABLE_ASSET + "}")
084        private Asset sortableAsset;
085    
086        @Inject
087        private Messages messages;
088    
089        @Inject
090        private Block standardHeader;
091    
092        /**
093         * Optional output parameter that stores the current column index.
094         */
095        @Parameter
096        @Property
097        private int index;
098    
099        /**
100         * Caches the index of the last column.
101         */
102        private int lastColumnIndex;
103    
104        @Property(write = false)
105        private PropertyModel columnModel;
106    
107        @Inject
108        private ComponentResources resources;
109    
110        void setupRender()
111        {
112            lastColumnIndex = gridModel.getDataModel().getPropertyNames().size() - 1;
113        }
114    
115        public boolean isSortDisabled()
116        {
117            return !columnModel.isSortable();
118        }
119    
120        public String getSortLinkClass()
121        {
122            switch (getSortForColumn())
123            {
124                case ASCENDING:
125                    return GridConstants.SORT_ASCENDING_CLASS;
126    
127                case DESCENDING:
128                    return GridConstants.SORT_DESCENDING_CLASS;
129    
130                default:
131                    return null;
132            }
133        }
134    
135        private ColumnSort getSortForColumn()
136        {
137            GridSortModel sortModel = gridModel.getSortModel();
138    
139            String columnId = columnModel.getId();
140    
141            return sortModel.getColumnSort(columnId);
142        }
143    
144        public String getHeaderClass()
145        {
146            List<String> classes = CollectionFactory.newList();
147    
148            if (!lean) classes.add(columnModel.getId());
149    
150            String sort = getSortLinkClass();
151    
152            if (sort != null) classes.add(sort);
153    
154            if (index == 0) classes.add(GridConstants.FIRST_CLASS);
155    
156            if (index == lastColumnIndex) classes.add(GridConstants.LAST_CLASS);
157    
158            return TapestryInternalUtils.toClassAttributeValue(classes);
159        }
160    
161        public boolean isActiveSortColumn()
162        {
163            return getSortForColumn() != ColumnSort.UNSORTED;
164        }
165    
166        /**
167         * Normal, non-Ajax event handler.
168         */
169    
170        void onSort(String columnId)
171        {
172            gridModel.getSortModel().updateSort(columnId);
173        }
174    
175        /**
176         * Ajax event handler, which carries the zone id.
177         */
178        boolean onSort(String columnId, String zone)
179        {
180            onSort(columnId);
181    
182            resources.triggerEvent(InternalConstants.GRID_INPLACE_UPDATE, new Object[] { zone }, null);
183    
184            // Event is handled, don't trigger further event handler methods.
185    
186            return true;
187        }
188    
189        public Asset getIcon()
190        {
191            switch (getSortForColumn())
192            {
193                case ASCENDING:
194                    return ascendingAsset;
195    
196                case DESCENDING:
197                    return descendingAsset;
198    
199                default:
200                    return sortableAsset;
201            }
202        }
203    
204        public Object getColumnContext()
205        {
206            if (zone == null) return columnModel.getId();
207    
208            return new Object[] { columnModel.getId(), zone };
209        }
210    
211        public String getIconLabel()
212        {
213            switch (getSortForColumn())
214            {
215                case ASCENDING:
216                    return messages.get("ascending");
217                case DESCENDING:
218                    return messages.get("descending");
219                default:
220                    return messages.get("sortable");
221            }
222        }
223    
224        public List<String> getColumnNames()
225        {
226            return gridModel.getDataModel().getPropertyNames();
227        }
228    
229    
230        public void setColumnName(String columnName)
231        {
232            columnModel = gridModel.getDataModel().get(columnName);
233        }
234    
235    
236        public Block getBlockForColumn()
237        {
238            Block override = overrides.getOverrideBlock(columnModel.getId() + "Header");
239    
240            if (override != null) return override;
241    
242            return standardHeader;
243        }
244    }