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.Collections;
027import java.util.List;
028
029import javax.el.ELException;
030import javax.faces.FacesException;
031import javax.faces.component.UIComponent;
032import javax.faces.model.DataModel;
033import javax.faces.model.ListDataModel;
034import javax.faces.view.facelets.ComponentConfig;
035import javax.faces.view.facelets.FaceletContext;
036import javax.faces.view.facelets.FaceletException;
037import javax.faces.view.facelets.FaceletHandler;
038import javax.faces.view.facelets.TagAttribute;
039import javax.faces.view.facelets.TagConfig;
040import javax.faces.view.facelets.TagHandler;
041
042import org.apache.commons.lang.StringUtils;
043
044import com.sun.faces.facelets.tag.TagAttributeImpl;
045import com.sun.faces.facelets.tag.TagAttributesImpl;
046import com.sun.faces.facelets.tag.jstl.core.ForEachHandler;
047
048/**
049 * Repeat handler, similar to the standard ForEach handler.
050 * <p>
051 * This component encapsulates a c:forEach tag inside a nxu:set tag, to be able to control when the sub-components
052 * should be re-created in the case of an ajax re-render.
053 *
054 * @author Anahide Tchertchian
055 */
056public class RepeatTagHandler extends TagHandler {
057
058    @SuppressWarnings({ "unchecked", "rawtypes" })
059    protected static final DataModel EMPTY_MODEL = new ListDataModel(Collections.emptyList());
060
061    /**
062     * @since 5.7
063     */
064    protected static final String ITERATION_VAR_PREFIX = "nxuRepeat_";
065
066    /**
067     * @since 5.7
068     */
069    protected final TagConfig config;
070
071    /**
072     * @deprecated, user {@link #items} instead
073     */
074    @Deprecated
075    protected final TagAttribute value;
076
077    /**
078     * @since 5.7
079     */
080    protected final TagAttribute items;
081
082    /**
083     * @since 5.7
084     */
085    protected final TagAttribute itemsId;
086
087    protected final TagAttribute var;
088
089    protected final TagAttribute index;
090
091    /**
092     * @since 5.7
093     */
094    protected final TagAttribute status;
095
096    /**
097     * @since 5.7
098     */
099    protected final TagAttribute begin;
100
101    /**
102     * @since 5.7
103     */
104    protected final TagAttribute end;
105
106    /**
107     * @since 5.7
108     */
109    protected final TagAttribute step;
110
111    /**
112     * @since 5.7
113     */
114    protected final TagAttribute tranzient;
115
116    /**
117     * @since 5.7
118     */
119    protected final TagAttribute varStatus;
120
121    public RepeatTagHandler(TagConfig config) {
122        super(config);
123        this.config = config;
124        items = getAttribute("items");
125        itemsId = getAttribute("itemsId");
126        value = getAttribute("value");
127        var = getAttribute("var");
128        index = getAttribute("index");
129        status = getAttribute("status");
130        begin = getAttribute("begin");
131        end = getAttribute("end");
132        step = getAttribute("step");
133        tranzient = getAttribute("transient");
134        varStatus = getAttribute("varStatus");
135    }
136
137    protected TagAttribute getItemsAttribute() {
138        TagAttribute itemsAttr = items;
139        if (items == null) {
140            // BBB
141            itemsAttr = value;
142        }
143        return itemsAttr;
144    }
145
146    protected String getTagConfigId(FaceletContext ctx) {
147        String refId = null;
148        if (itemsId != null) {
149            refId = itemsId.getValue(ctx);
150        }
151
152        if (StringUtils.isBlank(refId)) {
153            TagAttribute itemsAttr = getItemsAttribute();
154            Object val = null;
155            if (itemsAttr != null) {
156                val = itemsAttr.getObject(ctx);
157                if (val != null) {
158                    refId = val.toString();
159                }
160            }
161        }
162
163        StringBuilder builder = new StringBuilder();
164        if (refId != null) {
165            builder.append(refId);
166        }
167        builder.append(";");
168
169        Integer intValue = new Integer(builder.toString().hashCode());
170        return intValue.toString();
171    }
172
173    /**
174     * Encapsulate the call to a c:forEach tag in an SetTagHandler exposing the value and making sure the tagConfigId
175     * changes when the value changes (see NXP-11434).
176     * <p>
177     * See also NXP-15050: since 6.0, anchor handler in component tree to ensure proper ajax refresh when iteration
178     * value changes.
179     */
180    @Override
181    public void apply(FaceletContext ctx, UIComponent parent) throws IOException, FacesException, FaceletException,
182            ELException {
183        String anchor = String.valueOf(true);
184        FaceletHandler nextHandler = this.nextHandler;
185        TagAttribute varStatusAttr = varStatus;
186        if (index != null) {
187            // wrap the next handler in a set tag handler to expose the index
188            // value from the varStatus attribute.
189            String indexValue = index.getValue(ctx);
190            if (!StringUtils.isBlank(indexValue)) {
191                String varStatusValue = varStatus != null ? varStatus.getValue(ctx) : null;
192                if (StringUtils.isBlank(varStatusValue)) {
193                    // need to create and set it as an attribute for the index
194                    // to be exposed
195                    varStatusAttr = createAttribute(config, "varStatus", getVarName(indexValue + "_varStatus"));
196                } else {
197                    varStatusAttr = createAttribute(config, "varStatus", varStatusValue);
198                }
199                ComponentConfig indexVarConfig = TagConfigFactory.createAliasTagConfig(config, tagId, indexValue,
200                        "#{" + varStatusAttr.getValue() + ".index}", "false", anchor, this.nextHandler);
201                nextHandler = new SetTagHandler(indexVarConfig);
202            }
203        }
204
205        List<TagAttribute> forEachAttrs = new ArrayList<TagAttribute>();
206        forEachAttrs.add(createAttribute(config, "items", "#{" + 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 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}