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