/*
* Copyright 2014 astamuse company,Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.astamuse.asta4d.web.form.field.impl;
import static com.astamuse.asta4d.render.SpecialRenderer.Clear;
import java.util.LinkedList;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.jsoup.nodes.Element;
import com.astamuse.asta4d.Configuration;
import com.astamuse.asta4d.extnode.GroupNode;
import com.astamuse.asta4d.render.ElementNotFoundHandler;
import com.astamuse.asta4d.render.ElementSetter;
import com.astamuse.asta4d.render.Renderable;
import com.astamuse.asta4d.render.Renderer;
import com.astamuse.asta4d.render.transformer.ElementTransformer;
import com.astamuse.asta4d.util.SelectorUtil;
import com.astamuse.asta4d.web.form.field.OptionValueMap;
import com.astamuse.asta4d.web.form.field.OptionValuePair;
import com.astamuse.asta4d.web.form.field.PrepareRenderingDataUtil;
import com.astamuse.asta4d.web.form.field.SimpleFormFieldWithOptionValueRenderer;
import com.astamuse.asta4d.web.util.ClosureVarRef;
public class AbstractRadioAndCheckboxRenderer extends SimpleFormFieldWithOptionValueRenderer {
private static final String ToBeHiddenLaterFlagAttr = Configuration.getConfiguration().getTagNameSpace() + ":" +
"ToBeHiddenLaterFlagAttr";
@Override
public Renderer renderForEdit(String editTargetSelector, Object value) {
final List<String> valueList = convertValueToList(value);
Renderer renderer = Renderer.create("input", "checked", Clear);
// we have to iterate the elements because the attr selector would not work for blank values.
renderer.add("input", new ElementSetter() {
@Override
public void set(Element elem) {
String val = elem.attr("value");
if (valueList.contains(val)) {
elem.attr("checked", "");
}
}
});
return Renderer.create(editTargetSelector, renderer);
}
/**
* By default, there must be an id for every radio/checkbox item.
*
* @return
*/
protected boolean allowNonIdItems() {
return false;
}
protected Renderer retrieveAndCreateValueMap(final String editTargetSelector, final String displayTargetSelector) {
Renderer render = Renderer.create();
if (PrepareRenderingDataUtil.retrieveStoredDataFromContextBySelector(editTargetSelector) == null) {
final List<Pair<String, String>> inputList = new LinkedList<>();
final List<OptionValuePair> optionList = new LinkedList<>();
render.add(editTargetSelector, new ElementSetter() {
@Override
public void set(Element elem) {
inputList.add(Pair.of(elem.id(), elem.attr("value")));
}
});
render.add(":root", new Renderable() {
@Override
public Renderer render() {
Renderer render = Renderer.create();
for (Pair<String, String> input : inputList) {
String id = input.getLeft();
final String value = input.getRight();
if (StringUtils.isEmpty(id)) {
if (allowNonIdItems()) {
optionList.add(new OptionValuePair(value, value));
} else {
String msg = "The target item[%s] must have id specified.";
throw new IllegalArgumentException(String.format(msg, editTargetSelector));
}
} else {
render.add(SelectorUtil.attr("for", id), Renderer.create("label", new ElementSetter() {
@Override
public void set(Element elem) {
optionList.add(new OptionValuePair(value, elem.text()));
}
}));
render.add(":root", new Renderable() {
@Override
public Renderer render() {
PrepareRenderingDataUtil.storeDataToContextBySelector(editTargetSelector, displayTargetSelector,
new OptionValueMap(optionList));
return Renderer.create();
}
});
}
} // end for loop
return render;
}
});
}
return render;
}
protected Renderer setDelayedHiddenFlag(final String targetSelector) {
// hide the input element
final List<String> duplicatorRefList = new LinkedList<>();
final List<String> idList = new LinkedList<>();
Renderer renderer = Renderer.create(targetSelector, new ElementSetter() {
@Override
public void set(Element elem) {
String duplicatorRef = elem.attr(RadioPrepareRenderer.DUPLICATOR_REF_ATTR);
if (StringUtils.isNotEmpty(duplicatorRef)) {
duplicatorRefList.add(duplicatorRef);
}
idList.add(elem.id());
}
});
return renderer.add(":root", new Renderable() {
@Override
public Renderer render() {
Renderer render = Renderer.create().disableMissingSelectorWarning();
for (String ref : duplicatorRefList) {
render.add(SelectorUtil.attr(RadioPrepareRenderer.DUPLICATOR_REF_ID_ATTR, ref), ToBeHiddenLaterFlagAttr, "");
}
for (String id : idList) {
render.add(SelectorUtil.attr(RadioPrepareRenderer.LABEL_REF_ATTR, id), ToBeHiddenLaterFlagAttr, "");
}
for (String id : idList) {
render.add(SelectorUtil.attr("label", "for", id), ToBeHiddenLaterFlagAttr, "");
}
render.add(targetSelector, ToBeHiddenLaterFlagAttr, "");
// render.addDebugger("after set hidden flag");
return render.enableMissingSelectorWarning();
}
});
}
@Override
public Renderer renderForDisplay(final String editTargetSelector, final String displayTargetSelector, final Object value) {
Renderer render = Renderer.create().disableMissingSelectorWarning();
// retrieve and create a value map here
render.add(retrieveAndCreateValueMap(editTargetSelector, displayTargetSelector));
// render.add(super.renderForDisplay(editTargetSelector, displayTargetSelector, nonNullString));
// hide the edit element
render.add(setDelayedHiddenFlag(editTargetSelector));
final List<String> valueList = convertValueToList(value);
// render the shown value to target element by displayTargetSelector
render.add(displayTargetSelector, new Renderable() {
@Override
public Renderer render() {
return Renderer.create(displayTargetSelector, valueList, v -> {
return renderToDisplayTarget(displayTargetSelector,
retrieveDisplayStringFromStoredOptionValueMap(displayTargetSelector, v));
});
}
});
// if the element by displayTargetSelector does not exists, simply add a span to show the value.
// since ElementNotFoundHandler has been delayed, so the Renderable is not necessary
render.add(new ElementNotFoundHandler(displayTargetSelector) {
@Override
public Renderer alternativeRenderer() {
return addAlternativeDom(editTargetSelector, valueList);
}
});
// delay to hide all
render.add(":root", new Renderable() {
@Override
public Renderer render() {
return hideTarget(SelectorUtil.attr(ToBeHiddenLaterFlagAttr));
}
});
// delay to remove the redundant attr
render.add(":root", new Renderable() {
@Override
public Renderer render() {
Renderer render = Renderer.create().disableMissingSelectorWarning();
return render.add(SelectorUtil.attr(ToBeHiddenLaterFlagAttr), ToBeHiddenLaterFlagAttr, Clear)
.enableMissingSelectorWarning();
}
});
return render.enableMissingSelectorWarning();
}
protected Renderer addAlternativeDom(final String editTargetSelector, final List<String> valueList) {
Renderer renderer = Renderer.create();
// renderer.addDebugger("entry root");
// renderer.addDebugger("entry root:edit target:", editTargetSelector);
final List<String> matchedIdList = new LinkedList<>();
final List<String> unMatchedIdList = new LinkedList<>();
renderer.add(editTargetSelector, new ElementSetter() {
@Override
public void set(Element elem) {
if (valueList.contains((elem.attr("value")))) {
matchedIdList.add(elem.id());
} else {
unMatchedIdList.add(elem.id());
}
}
});
renderer.add(":root", new Renderable() {
@Override
public Renderer render() {
Renderer renderer = Renderer.create().disableMissingSelectorWarning();
// renderer.addDebugger("before hide unmatch");
// renderer.addDebugger("before add match");
if (matchedIdList.isEmpty()) {
renderer.add(addDefaultAlternativeDom(editTargetSelector, valueList));
} else {
// do nothing for remaining the existing label element
// but we still have to revive the possibly existing duplicate container
for (final String inputId : matchedIdList) {
final List<String> matchedDuplicatorRefList = new LinkedList<>();
final String labelRefSelector = SelectorUtil.attr(RadioPrepareRenderer.LABEL_REF_ATTR, inputId);
final String labelDefaultSelector = SelectorUtil.attr(SelectorUtil.tag("label"), "for", inputId);
renderer.add(labelRefSelector, new ElementSetter() {
@Override
public void set(Element elem) {
String ref = elem.attr(RadioPrepareRenderer.DUPLICATOR_REF_ATTR);
if (StringUtils.isNotEmpty(ref)) {
matchedDuplicatorRefList.add(ref);
}
}
});
renderer.add(new ElementNotFoundHandler(labelRefSelector) {
@Override
public Renderer alternativeRenderer() {
return Renderer.create(labelDefaultSelector, new ElementSetter() {
@Override
public void set(Element elem) {
String ref = elem.attr(RadioPrepareRenderer.DUPLICATOR_REF_ATTR);
if (StringUtils.isNotEmpty(ref)) {
matchedDuplicatorRefList.add(ref);
}
}// end set
});
}// end alternativeRenderer
});// end ElementNotFoundHandler
renderer.add(":root", new Renderable() {
@Override
public Renderer render() {
Renderer renderer = Renderer.create().disableMissingSelectorWarning();
for (String ref : matchedDuplicatorRefList) {
renderer.add(SelectorUtil.attr(RadioPrepareRenderer.DUPLICATOR_REF_ID_ATTR, ref),
ToBeHiddenLaterFlagAttr, Clear);
}
renderer.add(labelRefSelector, ToBeHiddenLaterFlagAttr, Clear);
renderer.add(labelDefaultSelector, ToBeHiddenLaterFlagAttr, Clear);
return renderer.enableMissingSelectorWarning();
}
});
}
}
return renderer.enableMissingSelectorWarning();
}
});
return renderer;
}
protected Renderer addDefaultAlternativeDom(final String editTargetSelector, final List<String> valueList) {
final List<String> duplicatorRefList = new LinkedList<>();
final List<String> idList = new LinkedList<>();
ClosureVarRef<Boolean> editTargetExists = new ClosureVarRef<Boolean>(false);
Renderer renderer = Renderer.create(editTargetSelector, new ElementSetter() {
@Override
public void set(Element elem) {
String duplicatorRef = elem.attr(RadioPrepareRenderer.DUPLICATOR_REF_ATTR);
if (StringUtils.isNotEmpty(duplicatorRef)) {
duplicatorRefList.add(duplicatorRef);
}
idList.add(elem.id());
editTargetExists.set(true);
}
});
/*
renderer.add(":root", () -> {
return Renderer.create().addDebugger("current root for addDefaultAlternativeDom");
});
*/
renderer.add(":root", new Renderable() {
@Override
public Renderer render() {
// skip create display alternative DOM if edit target does not exist.
if (editTargetExists.get()) {
// it is OK
} else {
return Renderer.create();
}
String attachTargetSelector;
if (duplicatorRefList.size() > 0) {
attachTargetSelector = SelectorUtil.attr(RadioPrepareRenderer.DUPLICATOR_REF_ID_ATTR,
duplicatorRefList.get(duplicatorRefList.size() - 1));
} else if (idList.size() == 0) {
String msg = "The target item[%s] must have id specified.";
throw new IllegalArgumentException(String.format(msg, editTargetSelector));
} else {
attachTargetSelector = SelectorUtil.id(idList.get(idList.size() - 1));
}
return new Renderer(attachTargetSelector, new ElementTransformer(null) {
@Override
public Element invoke(Element elem) {
GroupNode group = new GroupNode();
Element editClone = elem.clone();
group.appendChild(editClone);
for (String v : valueList) {
String nonNullString = retrieveDisplayStringFromStoredOptionValueMap(editTargetSelector, v);
group.appendChild(createAlternativeDisplayElement(nonNullString));
}
return group;
}// invoke
});// new renderer
}// render()
});// renderable
return renderer;
}
}