001/*
002 * (C) Copyright 2006-2013 Nuxeo SAS (http://nuxeo.com/) and contributors.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the GNU Lesser General Public License
006 * (LGPL) version 2.1 which accompanies this distribution, and is available at
007 * http://www.gnu.org/licenses/lgpl.html
008 *
009 * This library 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 GNU
012 * Lesser General Public License for more details.
013 *
014 * Contributors:
015 *     <a href="mailto:at@nuxeo.com">Anahide Tchertchian</a>
016 *
017 * $Id$
018 */
019
020package org.nuxeo.ecm.platform.ui.web.tag.handler;
021
022import java.io.IOException;
023import java.util.ArrayList;
024import java.util.Collections;
025import java.util.List;
026
027import javax.el.ELException;
028import javax.faces.FacesException;
029import javax.faces.component.UIComponent;
030import javax.faces.model.DataModel;
031import javax.faces.model.ListDataModel;
032import javax.faces.view.facelets.ComponentConfig;
033import javax.faces.view.facelets.FaceletContext;
034import javax.faces.view.facelets.FaceletException;
035import javax.faces.view.facelets.FaceletHandler;
036import javax.faces.view.facelets.TagAttribute;
037import javax.faces.view.facelets.TagConfig;
038import javax.faces.view.facelets.TagHandler;
039
040import org.apache.commons.lang.StringUtils;
041
042import com.sun.faces.facelets.tag.TagAttributeImpl;
043import com.sun.faces.facelets.tag.TagAttributesImpl;
044import com.sun.faces.facelets.tag.jstl.core.ForEachHandler;
045
046/**
047 * Repeat handler, similar to the standard ForEach handler.
048 * <p>
049 * This component encapsulates a c:forEach tag inside a nxu:set tag, to be able to control when the sub-components
050 * should be re-created in the case of an ajax re-render.
051 *
052 * @author Anahide Tchertchian
053 */
054public class RepeatTagHandler extends TagHandler {
055
056    @SuppressWarnings({ "unchecked", "rawtypes" })
057    protected static final DataModel EMPTY_MODEL = new ListDataModel(Collections.emptyList());
058
059    /**
060     * @since 5.7
061     */
062    protected static final String ITERATION_VAR_PREFIX = "nxuRepeat_";
063
064    /**
065     * @since 5.7
066     */
067    protected final TagConfig config;
068
069    /**
070     * @deprecated, user {@link #items} instead
071     */
072    @Deprecated
073    protected final TagAttribute value;
074
075    /**
076     * @since 5.7
077     */
078    protected final TagAttribute items;
079
080    /**
081     * @since 5.7
082     */
083    protected final TagAttribute itemsId;
084
085    protected final TagAttribute var;
086
087    protected final TagAttribute index;
088
089    /**
090     * @since 5.7
091     */
092    protected final TagAttribute status;
093
094    /**
095     * @since 5.7
096     */
097    protected final TagAttribute begin;
098
099    /**
100     * @since 5.7
101     */
102    protected final TagAttribute end;
103
104    /**
105     * @since 5.7
106     */
107    protected final TagAttribute step;
108
109    /**
110     * @since 5.7
111     */
112    protected final TagAttribute tranzient;
113
114    /**
115     * @since 5.7
116     */
117    protected final TagAttribute varStatus;
118
119    public RepeatTagHandler(TagConfig config) {
120        super(config);
121        this.config = config;
122        items = getAttribute("items");
123        itemsId = getAttribute("itemsId");
124        value = getAttribute("value");
125        var = getAttribute("var");
126        index = getAttribute("index");
127        status = getAttribute("status");
128        begin = getAttribute("begin");
129        end = getAttribute("end");
130        step = getAttribute("step");
131        tranzient = getAttribute("transient");
132        varStatus = getAttribute("varStatus");
133    }
134
135    protected TagAttribute getItemsAttribute() {
136        TagAttribute itemsAttr = items;
137        if (items == null) {
138            // BBB
139            itemsAttr = value;
140        }
141        return itemsAttr;
142    }
143
144    protected String getTagConfigId(FaceletContext ctx) {
145        String refId = null;
146        if (itemsId != null) {
147            refId = itemsId.getValue(ctx);
148        }
149
150        if (StringUtils.isBlank(refId)) {
151            TagAttribute itemsAttr = getItemsAttribute();
152            Object val = null;
153            if (itemsAttr != null) {
154                val = itemsAttr.getObject(ctx);
155                if (val != null) {
156                    refId = val.toString();
157                }
158            }
159        }
160
161        StringBuilder builder = new StringBuilder();
162        if (refId != null) {
163            builder.append(refId);
164        }
165        builder.append(";");
166
167        Integer intValue = new Integer(builder.toString().hashCode());
168        return intValue.toString();
169    }
170
171    /**
172     * Encapsulate the call to a c:forEach tag in an SetTagHandler exposing the value and making sure the tagConfigId
173     * changes when the value changes (see NXP-11434).
174     * <p>
175     * See also NXP-15050: since 6.0, anchor handler in component tree to ensure proper ajax refresh when iteration
176     * value changes.
177     */
178    @Override
179    public void apply(FaceletContext ctx, UIComponent parent) throws IOException, FacesException, FaceletException,
180            ELException {
181        String anchor = String.valueOf(true);
182        FaceletHandler nextHandler = this.nextHandler;
183        TagAttribute varStatusAttr = varStatus;
184        if (index != null) {
185            // wrap the next handler in a set tag handler to expose the index
186            // value from the varStatus attribute.
187            String indexValue = index.getValue(ctx);
188            if (!StringUtils.isBlank(indexValue)) {
189                String varStatusValue = varStatus != null ? varStatus.getValue(ctx) : null;
190                if (StringUtils.isBlank(varStatusValue)) {
191                    // need to create and set it as an attribute for the index
192                    // to be exposed
193                    varStatusAttr = createAttribute(config, "varStatus",
194                            getVarName(String.format("%s_%s", indexValue, "varStatus")));
195                } else {
196                    varStatusAttr = createAttribute(config, "varStatus", varStatusValue);
197                }
198                ComponentConfig indexVarConfig = TagConfigFactory.createAliasTagConfig(config, tagId,
199                        indexValue, String.format("#{%s.index}", varStatusAttr.getValue()), "false", anchor,
200                        this.nextHandler);
201                nextHandler = new SetTagHandler(indexVarConfig);
202            }
203        }
204
205        List<TagAttribute> forEachAttrs = new ArrayList<TagAttribute>();
206        forEachAttrs.add(createAttribute(config, "items", String.format("#{%s}", getVarName("items"))));
207        forEachAttrs.addAll(copyAttributes(config, var, begin, end, step, varStatusAttr, tranzient));
208        TagConfig forEachConfig = TagConfigFactory.createTagConfig(config, tagId, new TagAttributesImpl(
209                forEachAttrs.toArray(new TagAttribute[] {})), nextHandler);
210        ForEachHandler forEachHandler = new ForEachHandler(forEachConfig);
211
212        String setTagConfigId = getTagConfigId(ctx);
213        TagAttribute itemsAttr = getItemsAttribute();
214        ComponentConfig aliasConfig = TagConfigFactory.createAliasTagConfig(config, setTagConfigId,
215                getVarName("items"), itemsAttr != null ? itemsAttr.getValue() : null, "false", anchor, forEachHandler);
216        FaceletHandler handler = new SetTagHandler(aliasConfig);
217
218        // apply
219        handler.apply(ctx, parent);
220    }
221
222    protected String getVarName(String id) {
223        return String.format("%s%s", ITERATION_VAR_PREFIX, id);
224    }
225
226    protected TagAttribute createAttribute(TagConfig tagConfig, String name, String value) {
227        return new TagAttributeImpl(tagConfig.getTag().getLocation(), "", name, name, value);
228    }
229
230    protected TagAttribute copyAttribute(TagConfig tagConfig, TagAttribute attribute) {
231        return new TagAttributeImpl(tagConfig.getTag().getLocation(), "", attribute.getLocalName(),
232                attribute.getLocalName(), attribute.getValue());
233    }
234
235    protected List<TagAttribute> copyAttributes(TagConfig tagConfig, TagAttribute... attributes) {
236        List<TagAttribute> res = new ArrayList<TagAttribute>();
237        if (attributes != null) {
238            for (TagAttribute attr : attributes) {
239                if (attr != null) {
240                    res.add(copyAttribute(tagConfig, attr));
241                }
242            }
243        }
244        return res;
245    }
246
247}