diff --git a/pom.xml b/pom.xml
index 3b2fc5ab..1bb81ee0 100644
--- a/pom.xml
+++ b/pom.xml
@@ -93,6 +93,9 @@
1.8
true
+
+ -parameters
+
diff --git a/src/main/java/j2html/attributes/LambdaAttribute.java b/src/main/java/j2html/attributes/LambdaAttribute.java
new file mode 100644
index 00000000..bc8f95cf
--- /dev/null
+++ b/src/main/java/j2html/attributes/LambdaAttribute.java
@@ -0,0 +1,26 @@
+package j2html.attributes;
+
+// Written by Benjamin Weber (http://benjiweber.co.uk/blog/author/benji/)
+
+import j2html.reflection.MethodFinder;
+import java.util.function.Function;
+
+public interface LambdaAttribute extends MethodFinder, Function {
+
+ default String name() {
+ checkParametersEnabled();
+ return parameter(0).getName();
+ }
+
+ default String value() {
+ checkParametersEnabled();
+ return String.valueOf(this.apply(name()));
+ }
+
+ default void checkParametersEnabled() {
+ if ("arg0".equals(parameter(0).getName())) {
+ throw new IllegalStateException("You also need java 8u60 or newer for parameter reflection to work");
+ }
+ }
+
+}
diff --git a/src/main/java/j2html/reflection/MethodFinder.java b/src/main/java/j2html/reflection/MethodFinder.java
new file mode 100644
index 00000000..493bcb65
--- /dev/null
+++ b/src/main/java/j2html/reflection/MethodFinder.java
@@ -0,0 +1,48 @@
+package j2html.reflection;
+
+// Written by Benjamin Weber (http://benjiweber.co.uk/blog/author/benji/)
+
+import java.io.Serializable;
+import java.lang.invoke.SerializedLambda;
+import java.lang.reflect.Method;
+import java.lang.reflect.Parameter;
+import java.util.Arrays;
+import java.util.Objects;
+
+public interface MethodFinder extends Serializable {
+
+ default SerializedLambda serialized() {
+ try {
+ Method replaceMethod = getClass().getDeclaredMethod("writeReplace");
+ replaceMethod.setAccessible(true);
+ return (SerializedLambda) replaceMethod.invoke(this);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ default Class> getContainingClass() {
+ try {
+ String className = serialized().getImplClass().replaceAll("/", ".");
+ return Class.forName(className);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ default Method method() {
+ SerializedLambda lambda = serialized();
+ Class> containingClass = getContainingClass();
+ return Arrays.stream(containingClass.getDeclaredMethods())
+ .filter(method -> Objects.equals(method.getName(), lambda.getImplMethodName()))
+ .findFirst()
+ .orElseThrow(UnableToGuessMethodException::new);
+ }
+
+ default Parameter parameter(int n) {
+ return method().getParameters()[n];
+ }
+
+ class UnableToGuessMethodException extends RuntimeException {
+ }
+}
diff --git a/src/main/java/j2html/tags/Tag.java b/src/main/java/j2html/tags/Tag.java
index ab1ff291..6cad4c0a 100644
--- a/src/main/java/j2html/tags/Tag.java
+++ b/src/main/java/j2html/tags/Tag.java
@@ -2,6 +2,7 @@
import j2html.attributes.Attr;
import j2html.attributes.Attribute;
+import j2html.attributes.LambdaAttribute;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
@@ -79,7 +80,7 @@ public T attr(String attribute, Object value) {
setAttribute(attribute, value == null ? null : String.valueOf(value));
return (T) this;
}
-
+
/**
* Adds the specified attribute. If the Tag previously contained an attribute with the same name, the old attribute is replaced by the specified attribute.
*
@@ -134,11 +135,6 @@ public boolean equals(Object obj) {
return ((Tag) obj).render().equals(this.render());
}
- /**
- * Convenience methods that call attr with predefined attributes
- *
- * @return itself for easy chaining
- */
public T withClasses(String... classes) {
StringBuilder sb = new StringBuilder();
for (String s : classes) {
@@ -147,6 +143,19 @@ public T withClasses(String... classes) {
return attr(Attr.CLASS, sb.toString().trim());
}
+ public T withAttrs(LambdaAttribute... lambdaAttributes) {
+ for (LambdaAttribute attr : lambdaAttributes) {
+ attr(attr.name(), attr.value());
+ }
+ return (T) this;
+ }
+
+ /**
+ * Convenience methods that call attr with predefined attributes
+ *
+ * @return itself for easy chaining
+ */
+
public T isAutoComplete() {
return attr(Attr.AUTOCOMPLETE, null);
}
diff --git a/src/test/java/j2html/reflection/NamedValueTest.java b/src/test/java/j2html/reflection/NamedValueTest.java
new file mode 100644
index 00000000..dcfbc0b0
--- /dev/null
+++ b/src/test/java/j2html/reflection/NamedValueTest.java
@@ -0,0 +1,17 @@
+package j2html.reflection;
+
+import j2html.attributes.LambdaAttribute;
+import org.junit.Test;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class NamedValueTest {
+
+ @Test
+ public void testNamedValueWorks() {
+ LambdaAttribute pair = five -> 5;
+ assertThat("five", is(pair.name()));
+ assertThat("5", is(pair.value()));
+ }
+
+}
diff --git a/src/test/java/j2html/tags/TagTest.java b/src/test/java/j2html/tags/TagTest.java
index e82b734a..8c821c15 100644
--- a/src/test/java/j2html/tags/TagTest.java
+++ b/src/test/java/j2html/tags/TagTest.java
@@ -1,8 +1,13 @@
package j2html.tags;
+import j2html.Config;
+import j2html.model.DynamicHrefAttribute;
+import org.junit.Test;
+import static j2html.TagCreator.a;
import static j2html.TagCreator.body;
import static j2html.TagCreator.div;
import static j2html.TagCreator.footer;
+import static j2html.TagCreator.form;
import static j2html.TagCreator.header;
import static j2html.TagCreator.html;
import static j2html.TagCreator.iff;
@@ -13,9 +18,6 @@
import static j2html.TagCreator.tag;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
-import j2html.Config;
-import j2html.model.DynamicHrefAttribute;
-import org.junit.Test;
public class TagTest {
@@ -98,11 +100,24 @@ public void testDynamicAttribute() throws Exception {
ContainerTag testTagWithAttrValueNull = new ContainerTag("a").attr(new DynamicHrefAttribute());
assertThat(testTagWithAttrValueNull.render(), is(""));
}
-
+
@Test
public void testDynamicAttributeReplacement() throws Exception {
ContainerTag testTagWithAttrValueNull = new ContainerTag("a").attr("href", "/link").attr(new DynamicHrefAttribute());
assertThat(testTagWithAttrValueNull.render(), is(""));
}
+ @Test
+ public void testParameterNameReflectionAttributes() throws Exception {
+ String expectedAnchor = "example.com";
+ String actualAnchor = a("example.com").withAttrs(href -> "http://example.com").render();
+ assertThat(actualAnchor, is(expectedAnchor));
+ String expectedForm = "";
+ String actualForm = form().withAttrs(method -> "post", action -> "/form-path").with(
+ input().withAttrs(name -> "email", type -> "email"),
+ input().withAttrs(name -> "password", type -> "password")
+ ).render();
+ assertThat(actualForm, is(expectedForm));
+ }
+
}