-
Notifications
You must be signed in to change notification settings - Fork 4
html
org.nasdanika.html bundle provides fluent API for building HTML code including:
- HTML (low level),
- Bootstrap,
- Font Awesome,
- AngularJS,
- Knockout,
- Application - convenience methods and interfaces for constructing single page router applications with a header, navigation bar, content panels and footer.
The goal of the bundle is to provide a Java developer a set of API's to build modern Web UI (Bootstrap, Font Awesome) and Single-Page Applications (AngularJS, KnockoutJS) without having to switch contexts (Java -> HTML, Bootstrap, ...) and without having to remember all the minutia of the underlying frameworks - API's, enumerations, and IDE code completion reduce mental load.
An instance of HTMLFactory is used to create instances of API interfaces, which are then combined together. In applications where HTML API is used not in the context of a web request DefaultHTMLFactory class can be directly instantiated and used as HTMLFactory implementation. In the context of a web request, e.g. in routes or route operations, and instance of HTMLFactory can be obtained either by adapting HttpServletRequestContext or by specifying a context parameter - in this case the framework will adapt the context to HTMLFactory.
Many HTML interfaces extend Producer and Autocloseable. Many API methods take objects as arguments to build a composite HTML object from parts.
Objects are converted to HTML string (stringified) using the following algorithm (see. UIElementImpl.toHTML() source code):
- If object is
null
then it is treated as a blank string. - If object is String, then it is used as-is.
- If object implements Producer, then its
produce()
method is invoked and return value is recursively passed to stringification. - If object implements FactoryProducer, then its
produce(HTMLFactory)
method is invoked and return value is recursively passed to stringification. - If
Producer.Adapter
was set in the factory withsetProducerAdapter()
method, then the adapter is used to adapt object to producer. If adapter returns non-null value then it is passed to stringification. - If
FactoryProducer.Adapter
was set in the factory withsetFactoryProducerAdapter()
method, then the adapter is used to adapt object to factory producer. If the adapter returns non-null value then it is passed to stringification. - If object is InputStream or Reader, its content is converted to string.
- If object is URL, then its input stream is used to retrieve text content.
- Otherwise object's
toString()
method is invoked.
Example - loading script from a classloader resource:
htmlFactory.tag(TagName.script, getClass().getResource("Script.js"));
When close()
method is invoked on HTML object, the object invokes close
on all its parts. It allows to build complex HTML composites from parts which hold system resources such as OSGi services or database connections and release the resources by invoking close
on the top object.
Producer
and FactoryProducer
are functional interfaces. It allows to assemble HTML objects from lambdas and method references.
Low level HTML API provide means to construct HTML elements and manipulate their content and attributes. Higher level API implementations are built leveraging low-level API. The base interface for most HTML element interfaces is UIElement. HTML Tag can be created using tag(String, Object...)
and tag(TagName, Object...)
methods of HTMLFactory, where is an enumeration of tag names.
div(Object...)
and span(Object...)
are convenience methods - they invoke tag()
behind the scenes.
nextId()
method can be used to generate element ID's.
input(InputType)
method can be used to create elements. is an enumeration of input types. and are created by textArea()
and select()
methods respectively.
fragment(Object...)
method creates a [](http://www.nasdanika.org/server/apidocs/org.nasdanika.html/target/site/apidocs/org/nasdanika/html/Fragment]], which is a collection of HTML elements which can operated on as single unit.
Some other low-level factory methods include link(Object, Object...)
, ol(Iterable<?>)
, and ul(Iterable<?>)
.
CSS styles can be set with style(String,Object)
method or with UIElement style()
method which returns [](http://www.nasdanika.org/server/apidocs/org.nasdanika.html/target/site/apidocs/org/nasdanika/html/Style]] interface providing bindings for frequently used styles, e.g.
Tag tag = htmlFactory.div(content).style().text().size("150%");
The HTML bundle API provides interfaces and methods for the majority of Bootstrap elements. The API attempts to follow Bootstrap documentation as close as possible - interface and method names shall be self-descriptive after getting familiar with Bootstrap.
Bootstrap classes can be set on any UIElement by invoking bootstrap()
method and then one of [](http://www.nasdanika.org/server/apidocs/org.nasdanika.html/target/site/apidocs/org/nasdanika/html/Boostrap]] interface methods, e.g.:
Tag tag = htmlFactory.div(content).bootstrap().grid().container();
Bootstrap forms functionality is available through [](http://www.nasdanika.org/server/apidocs/org.nasdanika.html/target/site/apidocs/org/nasdanika/html/Form]] interface. Inputs shall be created by HTMLFactory and then added to [](http://www.nasdanika.org/server/apidocs/org.nasdanika.html/target/site/apidocs/org/nasdanika/html/FormGroup]], [](http://www.nasdanika.org/server/apidocs/org.nasdanika.html/target/site/apidocs/org/nasdanika/html/FormInputGroup]] or [](http://www.nasdanika.org/server/apidocs/org.nasdanika.html/target/site/apidocs/org/nasdanika/html/InputGroup]] by invoking respective Form methods.
[](http://www.nasdanika.org/server/apidocs/org.nasdanika.html/target/site/apidocs/org/nasdanika/html/HTMLFactory$Glyphicons]] enumeration contains a list of Bootstrap glyphs and glyphicon(Glyphicon)
factory method can be used to create a glyph:
Tag searchGlyph = htmlFactory.glyphicon(Glyphicon.search);
stackModal()
generates JavaScript which allows to stack Bootstrap modals.
Font Awesome glyphs can be added to any UIElement by invoking fontAwesome()
method, and then one of category methods, e.g.:
mySpan.fontAwesome().webApplication(WebApplication.desktop)
fontAwesome()
factory method is a shortcut for span("").fontAwesome()
. [](http://www.nasdanika.org/server/apidocs/org.nasdanika.html/target/site/apidocs/org/nasdanika/html/FontAwesome;Stack]] interface is used for creating stacked icons, it can be created with the factory method fontAwesomeStack()
.
AngularJS directives can be added to UIElement's by invoking angular()
method and then one of methods of returned [](http://www.nasdanika.org/server/apidocs/org.nasdanika.html/target/site/apidocs/org/nasdanika/html/Angular]] instance:
myButton.angular().click("doCoolStuff()")
Similarly to AngularJS directives, Knockout bindings can be added to UIElement's by invoking knockout()
methods and then of of methods of returned [](http://www.nasdanika.org/server/apidocs/org.nasdanika.html/target/site/apidocs/org/nasdanika/html/Knockout]] instance:
myButton.knockout().click("doCoolStuff()")
[](http://www.nasdanika.org/server/apidocs/org.nasdanika.html/target/site/apidocs/org/nasdanika/html/KnockoutVirtualElement|Knockout virtual elements]] can be created with the factory's knockoutVirtualElement(Object... content)
method.
HTMLFactory supports simple templating two interpolate methods which expand {{token name}}
tokens in the source. Source can be
- javadoc>java.lang.String,
- javadoc>java.net.URL,
- javadoc>java.io.InputStream,
- javadoc>java.io.Reader,
- [](http://www.nasdanika.org/server/apidocs/org.nasdanika.html/target/site/apidocs/org/nasdanika/html/Producer]],
- [](http://www.nasdanika.org/server/apidocs/org.nasdanika.html/target/site/apidocs/org/nasdanika/html/FactoryProducer]],
- Object adaptable to Producer or FactoryProducer,
- Any object - converted to String with
toString()
.
See UIElementImpl.stringify() source for more details regarding conversion of a source object to String.
If simple interpolation is not enough you can use JET templates, Mustache for Java or any other template engine you like.
The code below reads Module.js
resource, replaces {{dependencies}}
token with its value, and creates a script tag.
Tag script = htmlFactory.tag(TagName.script, htmlFactory.interpolate(getClass().getResource("MyModule.js", Collections.singletonMap("dependencies", "..."))));
applicationPanel()
factory methods creates an instance of [](http://www.nasdanika.org/server/apidocs/org.nasdanika.html/target/site/apidocs/org/nasdanika/html/ApplicationPanel]], which provides a convenient way to create web pages with a header, navigation bar, several conent panels and a footer.
routerApplication()
factory method generates a single-page application with a Backbone route which loads server-side content using wp>Ajax_(programming) to an HTML element with id
specified in the route path. In this page's URL from the information center http://localhost:8080/information-center/router/doc.html#router/doc-content//information-center/router/doc/bundle/org.nasdanika.web/doc/html.md
:
-
http://localhost:8080/information-center/router/doc.html
- Documentation application route URL. -
router
- router path. -
doc-content
- ID of the HTML element where to load content. -
/information-center/router/doc/bundle/org.nasdanika.web/doc/html.md
- URL of the page to load.
Notice the double-slash in the URL fragment - it is used because the full path to page URL is provided. With a single slash it'd be treated as relative. The same URL can be written as http://localhost:8080/information-center/router/doc.html#router/doc-content/doc/bundle/org.nasdanika.web/doc/html.md
with documentation page path relative to the documentation application route path - doc/bundle/org.nasdanika.web/doc/html.md
.
Stylesheets and scripts used by the application can be set at the HTMLFactory level or specified by the client code. In Nasdanika Foundation Server applications application-wide scripts and stylesheets are configured through extensions in plugin.xml
of the application bundle.
bootstrapRouterApplication()
method is similar to routerApplicationMethod()
but allows to specify a UI [](http://www.nasdanika.org/server/apidocs/org.nasdanika.html/target/site/apidocs/org/nasdanika/html/Theme]]. Nasdanika Application Workspace Wizard generates applications with the default Bootstrap theme and themes from Bootswatch.
The code below was generated by a wizard:
package org.nasdanika.demo.app;
import org.nasdanika.html.Accordion;
import org.nasdanika.html.ApplicationPanel;
import org.nasdanika.html.Breadcrumbs;
import org.nasdanika.html.Fragment;
import org.nasdanika.html.HTMLFactory;
import org.nasdanika.html.HTMLFactory.Glyphicon;
import org.nasdanika.html.Navbar;
import org.nasdanika.html.Theme;
import org.nasdanika.html.UIElement.DeviceSize;
import org.nasdanika.html.UIElement.Event;
import org.nasdanika.html.UIElement.Size;
import org.nasdanika.html.UIElement.Style;
import org.nasdanika.web.HttpServletRequestContext;
import org.nasdanika.web.Action;
import org.nasdanika.web.Route;
import java.util.Date;
/**
* Route to demonstrate/test HTMLFactory capabilities
*
*/
public class DemoAppRoute implements Route {
@Override
public Action execute(HttpServletRequestContext context, Object... args) throws Exception {
final HTMLFactory htmlFactory = context.adapt(HTMLFactory.class);
ApplicationPanel appPanel = htmlFactory.applicationPanel()
.header("Demo App")
.headerLink("#")
.footer(
htmlFactory.link("#", "Contact us"),
" · ",
htmlFactory.link("#", "Privacy Policy"))
.width(800);
Navbar navBar = htmlFactory.navbar("Welcome back, Joe Doe", "#")
.item(htmlFactory.link("#", "Accounts"), true, false)
.item(htmlFactory.link("#", "Customer service"), false, true);
navBar.dropdown("Transfer", false)
.item(htmlFactory.link("#", "Internal"))
.divider()
.header("External transfers")
.item(htmlFactory.link("#", "Wire"))
.item(htmlFactory.link("#", "Payment Gateway"));
Breadcrumbs breadcrumbs = htmlFactory.breadcrumbs()
.item("#", "Home")
.item("#", "My page")
.item(null, "Details");
appPanel.navigation(navBar, breadcrumbs);
appPanel.contentPanel(
htmlFactory.linkGroup()
.item("Item 1", "#", Style.DEFAULT, true)
.item("Item 2", "#", Style.DEFAULT, false)
.item("Item 3", "#", Style.SUCCESS, false))
.width(DeviceSize.LARGE, 2).id("side-panel");
Accordion accordion = htmlFactory.accordion()
.item("Item 1", "Item 1 body")
.item("Item 2", Style.PRIMARY, "Item 2 body")
.item("Item 3", Style.WARNING, "Item 3 body")
.style(Style.SUCCESS);
Fragment body = htmlFactory.fragment(accordion);
// Button groups
Fragment buttonGroups = htmlFactory.fragment();
buttonGroups.content(
htmlFactory.buttonGroup(
htmlFactory.button("A").on(Event.click, "alert('Here we go!!!');"),
htmlFactory.button("B").style(Style.PRIMARY),
htmlFactory.button("C")),
" ",
htmlFactory.buttonGroup(
htmlFactory.button("A").style(Style.WARNING),
htmlFactory.button("B").style(Style.INFO),
htmlFactory.button("C")).size(Size.LARGE),
" ",
htmlFactory.buttonGroup(
htmlFactory.button("A"),
htmlFactory.button("B").style(Style.PRIMARY),
htmlFactory.button("C")).vertical().size(Size.EXTRA_SMALL),
" ",
htmlFactory.buttonGroup(
htmlFactory.button("A"),
htmlFactory.button("B").style(Style.PRIMARY),
htmlFactory.button("C")
.item(htmlFactory.link("#", "C1"))
.divider()
.header("C2")
.item(htmlFactory.link("#", "C2.1"))
.item(htmlFactory.link("#", "C2.2"))),
" ",
htmlFactory.buttonGroup(
htmlFactory.button("A"),
htmlFactory.button("B").style(Style.PRIMARY),
htmlFactory.button("C")
.item(htmlFactory.link("#", "C1"))
.divider()
.header("C2")
.item(htmlFactory.link("#", "C2.1"))
.item(htmlFactory.link("#", "C2.2"))).vertical(),
"<HR/>",
htmlFactory.label(Style.SUCCESS, "This is a button toolbar ", htmlFactory.glyphicon(Glyphicon.arrow_down)),
"<P/>",
htmlFactory.buttonToolbar(
htmlFactory.buttonGroup(
htmlFactory.button("A"),
htmlFactory.button("B").style(Style.PRIMARY),
htmlFactory.button("C")),
htmlFactory.buttonGroup(
htmlFactory.button("X"),
htmlFactory.button("Y").style(Style.PRIMARY),
htmlFactory.button("Z")
.item(htmlFactory.link("#", "Z1"))
.divider()
.header("Z2")
.item(htmlFactory.link("#", "Z2.1"))
.item(htmlFactory.link("#", "Z2.2")))
),
"<HR/>",
htmlFactory.buttonGroup(
htmlFactory.button("A"),
htmlFactory.button("B").style(Style.PRIMARY),
htmlFactory.button("C")
.item(htmlFactory.link("#", "C1"))
.divider()
.header("C2")
.item(htmlFactory.link("#", "C2.1"))
.item(htmlFactory.link("#", "C2.2"))).justified()
);
body.content(htmlFactory.panel(Style.INFO, "Button Groups & Toolbars", buttonGroups, null).id("button-groups"));
appPanel.contentPanel(body).width(DeviceSize.LARGE, 10);
body.content(
htmlFactory.panel(
Style.INFO,
null,
new AutoCloseable() {
@Override
public String toString() {
// Produce dynamic HTML
return htmlFactory
.label(Style.SUCCESS, new Date())
.toString();
}
@Override
public void close() throws Exception {
// Close resources;
}
},
null));
String themeName = context.getRequest().getParameter("theme");
final AutoCloseable app =
htmlFactory.bootstrapRouterApplication(
themeName == null ? null : Theme.valueOf(themeName),
"My Application",
null,
null,
appPanel);
// TODO Auto-generated method stub
return new Action() {
@Override
public void close() throws Exception {
app.close();
}
@Override
public Object execute() throws Exception {
return app.toString();
}
};
}
@Override
public boolean canExecute() {
return true;
}
@Override
public void close() throws Exception {
// NOP
}
}
The library formats output code. If, for some reason, formatting performed by the library is not sufficient, an HTML parser like jsoup can be used for output formatting.