All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.rapidoid.gui.GUI Maven / Gradle / Ivy

package org.rapidoid.gui;

/*
 * #%L
 * rapidoid-gui
 * %%
 * Copyright (C) 2014 - 2017 Nikolche Mihajlovski and contributors
 * %%
 * 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.
 * #L%
 */

import org.rapidoid.annotation.Authors;
import org.rapidoid.annotation.Since;
import org.rapidoid.beany.Beany;
import org.rapidoid.cls.Cls;
import org.rapidoid.commons.AnyObj;
import org.rapidoid.commons.Dates;
import org.rapidoid.commons.English;
import org.rapidoid.commons.Str;
import org.rapidoid.gui.input.*;
import org.rapidoid.gui.reqinfo.IReqInfo;
import org.rapidoid.gui.reqinfo.ReqInfo;
import org.rapidoid.gui.var.ReqDataVar;
import org.rapidoid.html.HTML;
import org.rapidoid.html.Tag;
import org.rapidoid.html.TagWidget;
import org.rapidoid.html.customtag.ColspanTag;
import org.rapidoid.html.tag.*;
import org.rapidoid.http.HttpVerb;
import org.rapidoid.lambda.Calc;
import org.rapidoid.lambda.ToMap;
import org.rapidoid.log.Log;
import org.rapidoid.model.Item;
import org.rapidoid.model.Models;
import org.rapidoid.model.Property;
import org.rapidoid.render.Render;
import org.rapidoid.render.Templates;
import org.rapidoid.security.Role;
import org.rapidoid.timeseries.TimeSeries;
import org.rapidoid.u.U;
import org.rapidoid.util.Msc;
import org.rapidoid.util.MscOpts;
import org.rapidoid.util.TUUID;
import org.rapidoid.var.Var;

import java.io.Serializable;
import java.util.*;
import java.util.concurrent.atomic.AtomicLong;

@Authors("Nikolche Mihajlovski")
@Since("2.0.0")
public abstract class GUI extends HTML implements Role {

	public static final Tag NOTHING = span(fa("ban"), " N/A").class_("nothing");

	public static final Tag N_A = NOTHING;

	public static final Tag TRUE = fa("check-square-o");

	public static final Tag FALSE = fa("square-o");

	public static final Btn SAVE = cmd("^Save");

	public static final Btn ADD = cmd("^Add");

	public static final Btn UPDATE = cmd("^Update");

	public static final Btn INSERT = cmd("^Insert");

	public static final Btn DELETE = cmd("!Delete");

	public static final Btn YES_DELETE = cmd("!YesDelete").contents("Yes, delete!");

	public static final Btn REMOVE = cmd("!Remove");

	public static final Btn DESTROY = cmd("!Destroy");

	public static final Btn YES = cmd("^Yes");

	public static final Btn NO = cmd("No");

	public static final Btn OK = cmd("^OK");

	public static final Btn REFRESH = cmd("refresh");

	public static final Btn CANCEL = navigate("Cancel");

	public static final Btn BACK = navigate("Back");

	public static final Btn EDIT = cmd("^Edit");

	public static final Tag WARN = FA.WARNING.style("color: #955; padding-bottom: 23px;");

	private static final AtomicLong ID_GEN = new AtomicLong();

	public static TableTag table_(Object... contents) {
		return table(contents).class_("table table-striped table-hover");
	}

	public static Tag row(ColspanTag... cols) {
		return div((Object[]) cols).class_("row");
	}

	public static Tag rowSeparated(ColspanTag... cols) {
		return row(cols).class_("row row-separated");
	}

	public static Tag row(Object... contents) {
		return row(col12(contents));
	}

	public static Tag columns(Object left, Object right) {
		return row(col6(left), col6(right));
	}

	public static Tag columns(Object left, Object middle, Object right) {
		return row(col4(left), col4(middle), col4(right));
	}

	public static Tag columns(Object left, Object middleLeft, Object middleRight, Object right) {
		return row(col3(left), col3(middleLeft), col3(middleRight), col3(right));
	}

	public static Tag rowSeparated(Object... contents) {
		return row(contents).class_("row row-separated");
	}

	public static Tag container(Object... contents) {
		return div(contents).class_("container");
	}

	public static Tag containerFluid(Object... contents) {
		return div(contents).class_("container-fluid");
	}

	public static Tag icon(String icon) {
		return span().class_("icon-" + icon);
	}

	public static Tag glyphicon(String glyphicon) {
		return span().class_("glyphicon glyphicon-" + glyphicon);
	}

	public static Tag fa(String fontAwesomeIcon) {
		return i().class_("fa fa-" + fontAwesomeIcon);
	}

	public static Tag awesomeFw(String fontAwesomeIcon) {
		return span().class_("fa fa-fw fa-" + fontAwesomeIcon);
	}

	public static ATag a_glyph(String glyphicon, Object... contents) {
		return a_void(glyphicon(glyphicon), NBSP, contents);
	}

	public static ATag a_awesome(String fontAwesomeIcon, Object... contents) {
		return a_void(fa(fontAwesomeIcon), NBSP, contents);
	}

	public static Btn btn(Object... contents) {
		Btn btn = Cls.customizable(Btn.class).contents(contents);

		for (Object content : contents) {
			if (content instanceof String) {
				String cmd = (String) content;
				btn = btn.command(cmd);
				break;
			}
		}

		return btn;
	}

	public static BtnMenu btnMenu() {
		return new BtnMenu();
	}

	public static Tag nav_(boolean fluid, boolean inverse, Tag brand, Object[] navbarContent) {
		brand = brand.class_("navbar-brand");
		Tag hdr = div(btnCollapse(), brand).class_("navbar-header");

		Tag collapsable = div(navbarContent).class_("collapse navbar-collapse").id("collapsable");

		Tag cnt = div(hdr, collapsable).class_(containerMaybeFluid(fluid));

		String navDefOrInv = inverse ? "navbar-inverse" : "navbar-default";
		return nav(cnt).class_("navbar " + navDefOrInv).role("navigation");
	}

	public static String containerMaybeFluid(boolean fluid) {
		return fluid ? "container-fluid" : "container";
	}

	public static ButtonTag btnCollapse() {
		ButtonTag btn = button(span("Toggle navigation").class_("sr-only"), icon("bar"), icon("bar"), icon("bar"));

		btn = btn.type("button").class_("navbar-toggle collapsed").attr("data-toggle", "collapse")
			.attr("data-target", "#collapsable");

		return btn;
	}

	public static Tag navbarMenu(boolean onLeft, int activeIndex, Object... menuItems) {
		Tag menu = ul().class_("nav navbar-nav navbar-" + leftOrRight(onLeft));

		for (int i = 0; i < menuItems.length; i++) {
			Object item = menuItems[i];

			Tag li = li(item);
			if (i == activeIndex) {
				li = li.class_("active");
			}

			menu = menu.append(li);
		}

		return menu;
	}

	public static Tag navbarDropdown(boolean onLeft, Tag menu, Object... subItems) {
		Tag ul1 = ul_li(subItems).class_("dropdown-menu").role("menu");
		menu = menu.class_("dropdown-toggle").attr("data-toggle", "dropdown");
		Tag drop1 = li(menu, ul1).class_("dropdown");
		return ul(drop1).class_("nav navbar-nav navbar-" + leftOrRight(onLeft));
	}

	public static Tag menuDivider() {
		return li().class_("divider");
	}

	public static FormTag navbarForm(boolean onLeft, Object btnContent, String[] fields, String[] placeholders) {
		U.must(fields.length == placeholders.length, "");

		Tag ctrls = div().class_("form-group");

		for (int i = 0; i < fields.length; i++) {
			InputTag inp = input().type("text").class_("form-control").name(fields[i]).id("navbar-" + fields[i])
				.placeholder(placeholders[i]);
			ctrls = ctrls.append(inp);
		}

		ButtonTag btn = button(btnContent).class_("btn btn-default").type("submit");
		return form(ctrls, btn).class_("navbar-form navbar-" + leftOrRight(onLeft));
	}

	public static FormTag navbarSearchForm(String action) {
		return navbarForm(false, fa("search"), arr("q"), arr("Search")).attr("action", action).attr("method",
			"GET");
	}

	public static Object navbarPage(boolean fluid, Tag brand, Object[] navbarContent, Object pageContent) {
		Object cont = div(pageContent).class_(containerMaybeFluid(fluid));
		Tag navbar = nav_(fluid, false, brand, navbarContent);
		return arr(navbar, cont);
	}

	public static Object cleanPage(boolean fluid, Object pageContent) {
		return div(pageContent).class_(containerMaybeFluid(fluid));
	}

	private static String leftOrRight(boolean onLeft) {
		return onLeft ? "left" : "right";
	}

	public static Tag caret() {
		return span().class_("caret");
	}

	public static Tag jumbotron(Object... contents) {
		return div(contents).class_("jumbotron");
	}

	public static Tag well(Object... contents) {
		return div(contents).class_("well");
	}

	public static ColspanTag col_(int cols, Object... contents) {
		return (ColspanTag) tag(ColspanTag.class, "div", contents).class_("col-md-" + cols);
	}

	public static ColspanTag col1(Object... contents) {
		return col_(1, contents);
	}

	public static ColspanTag col2(Object... contents) {
		return col_(2, contents);
	}

	public static ColspanTag col3(Object... contents) {
		return col_(3, contents);
	}

	public static ColspanTag col4(Object... contents) {
		return col_(4, contents);
	}

	public static ColspanTag col5(Object... contents) {
		return col_(5, contents);
	}

	public static ColspanTag col6(Object... contents) {
		return col_(6, contents);
	}

	public static ColspanTag col7(Object... contents) {
		return col_(7, contents);
	}

	public static ColspanTag col8(Object... contents) {
		return col_(8, contents);
	}

	public static ColspanTag col9(Object... contents) {
		return col_(9, contents);
	}

	public static ColspanTag col10(Object... contents) {
		return col_(10, contents);
	}

	public static ColspanTag col11(Object... contents) {
		return col_(11, contents);
	}

	public static ColspanTag col12(Object... contents) {
		return col_(12, contents);
	}

	public static Tag mid2(Object... contents) {
		return row(col5(), col2(contents), col5());
	}

	public static Tag mid4(Object... contents) {
		return row(col4(), col4(contents), col4());
	}

	public static Tag mid6(Object... contents) {
		return row(col3(), col6(contents), col3());
	}

	public static Tag mid8(Object... contents) {
		return row(col2(), col8(contents), col2());
	}

	public static Tag mid10(Object... contents) {
		return row(col1(), col10(contents), col1());
	}

	public static Btn cmd(String cmd, Object... args) {

		boolean primary = cmd.startsWith("^");
		boolean danger = cmd.startsWith("!");
		boolean warning = cmd.startsWith("?");

		if (primary || danger || warning) {
			cmd = cmd.substring(1);
		}

		String caption = Str.capitalized(cmd);

		Btn btn = btn(caption);
		if (primary) {
			btn.primary();
		} else if (danger) {
			btn.danger();
		} else if (warning) {
			btn.warning();
		}

		btn.command(cmd, args);

		if (danger || warning) {
			btn.confirm(U.frmt("Please confirm that you want to execute '%s'", cmd));
		}

		return btn;
	}

	public static Btn navigate(String cmd) {
		String caption = Str.capitalized(cmd);
		return btn(caption).go(cmd);
	}

	public static Btn[] cmds(String... commands) {
		Btn[] cmds = new Btn[commands.length];

		for (int i = 0; i < cmds.length; i++) {
			cmds[i] = cmd(commands[i]);
		}

		return cmds;
	}

	public static Tag titleBox(Object... contents) {
		return div(contents).class_("title-box");
	}

	public static Tag right(Object... contents) {
		return span(contents).class_("pull-right");
	}

	public static Panel panel(Object... contents) {
		return Cls.customizable(Panel.class, new Object[]{AnyObj.flat(contents)});
	}

	public static HtmlPage page(Object... contents) {
		return Cls.customizable(HtmlPage.class, new Object[]{AnyObj.flat(contents)});
	}

	public static Grid grid(Iterable items) {
		return Cls.customizable(Grid.class).items(items);
	}

	public static  KVGrid grid(Map map) {
		return Cls.customizable(KVGrid.class).map(map);
	}

	public static  List grids(Map> maps) {
		List grids = U.list();

		for (Map.Entry> entry : maps.entrySet()) {
			String key = entry.getKey();
			Map map = entry.getValue();

			grids.add(h4(b(key, ":")));
			grids.add(grid(map));
		}

		return grids;
	}

	public static Pager pager(String param) {
		return Cls.customizable(Pager.class, param);
	}

	public static Form show(Object bean, String... properties) {
		return show(Models.item(bean), properties);
	}

	public static Form show(final Item item, String... properties) {
		// FIXME make fluent and customizable!
		return new Form(FormMode.SHOW, item, properties);
	}

	public static Form edit(Object bean, String... properties) {
		return edit(Models.item(bean), properties);
	}

	public static Form edit(final Item item, String... properties) {
		// FIXME make fluent and customizable!
		return new Form(FormMode.EDIT, item, properties);
	}

	public static Form create(Object bean, String... properties) {
		return create(Models.item(bean), properties);
	}

	public static Form create(final Item item, String... properties) {
		// FIXME make fluent and customizable!
		return new Form(FormMode.CREATE, item, properties);
	}

	public static Field field(Item item, Property prop) {
		return new Field(item, prop);
	}

	public static  Property prop(String name, Calc calc) {
		return Models.property(name, calc);
	}

	public static Item item(Object value) {
		return Models.item(value);
	}

	public static Tag media(Object left, Object title, Object body, String targetUrl) {

		Tag mhead = h4(title).class_("media-heading");
		Tag mleft = div(left).class_("media-left");
		Tag mbody = div(mhead, body).class_("media-body");

		String divClass = targetUrl != null ? "media pointer" : "media";
		String js = targetUrl != null ? U.frmt("goAt('%s');", targetUrl) : null;

		return div(mleft, mbody).class_(divClass).onclick(js);
	}

	public static Tag[] mediaList(List found) {
		Tag[] items = new Tag[found.size()];
		int ind = 0;

		for (Object result : found) {
			Object id = Beany.getId(result);
			String uri = uriFor(result);

			Tag left = h6("(ID", NBSP, "=", NBSP, id, ")");
			Object header = span(result.getClass().getSimpleName());
			items[ind++] = media(left, header, small(Beany.beanToNiceText(result, true)), uri);
		}

		return items;
	}

	public static  Var var(String name, Class type, T defaultValue) {
		return new ReqDataVar<>(name, type, defaultValue);
	}

	public static  Var var(String name, T defaultValue) {
		return var(name, Cls.of(defaultValue), defaultValue);
	}

	public static Var var(String name) {
		return var(name, String.class, null);
	}

	public static Var> listVar(String name) {
		Class> type = U.cast(List.class);
		return new ReqDataVar<>(name, type, U.list());
	}

	public static Var> listVar(String name, List defaultValue) {
		Class> type = U.cast(List.class);
		return new ReqDataVar<>(name, type, defaultValue);
	}

	public static Object highlight(String text) {
		return highlight(text, null);
	}

	public static Object highlight(String text, String regex) {
		return Cls.customizable(Highlight.class, text, regex);
	}

	public static EmailInput email() {
		return Cls.customizable(EmailInput.class);
	}

	public static PasswordInput password() {
		return Cls.customizable(PasswordInput.class);
	}

	public static TextInput txt() {
		return Cls.customizable(TextInput.class);
	}

	public static TextArea txtbig() {
		return Cls.customizable(TextArea.class);
	}

	public static Checkbox checkbox() {
		return Cls.customizable(Checkbox.class);
	}

	public static Checkboxes checkboxes() {
		return Cls.customizable(Checkboxes.class);
	}

	public static Radio radio() {
		return Cls.customizable(Radio.class);
	}

	public static Radios radios() {
		return Cls.customizable(Radios.class);
	}

	public static MultiSelect multiSelect() {
		return Cls.customizable(MultiSelect.class);
	}

	public static Dropdown dropdown() {
		return Cls.customizable(Dropdown.class);
	}

	public static Object display(Object item) {
		try {
			return _display(item);

		} catch (Exception e) {
			Log.error("Error while rendering GUI element!", e);
			return N_A;
		}
	}

	@SuppressWarnings("unchecked")
	private static Object _display(Object item) {
		if (item instanceof Tag) {
			return item;
		}

		if (item == null) return N_A;

		if (item instanceof Date) {
			Date date = (Date) item;
			Tag dateSpan = span(Dates.readable(date));
			return tooltip(dateSpan, Dates.details(date));

		} else if (item instanceof Boolean) {
			boolean b = (Boolean) item;
			return b ? TRUE : FALSE;

		} else if (item instanceof UUID) {
			UUID uuid = (UUID) item;
			return uuid.toString();

		} else if (item instanceof TUUID) {
			TUUID tuuid = (TUUID) item;
			return tuuid.toString();

		} else if (item instanceof byte[]) {
			byte[] bytes = (byte[]) item;
			return bytes.length <= 512 ? Str.toWebSafeBinary(bytes) : "(BINARY DATA)";

		} else if (item instanceof Var) {
			Var var = (Var) item;
			return display(var.get());

		} else if (item instanceof Iterable) {
			Iterable iter = (Iterable) item;
			return display(iter.iterator());

		} else if (item instanceof Object[]) {
			Object[] arr = (Object[]) item;
			return display(U.iterator(arr));

		} else if (item instanceof Map) {
			Map map = (Map) item;
			return grid(map);

		} else if (item instanceof ToMap) {
			Map map = ((ToMap) item).toMap();
			return grid(map);

		} else if (item instanceof TagWidget) {
			TagWidget widget = (TagWidget) item;
			return widget.render(null);
		}

		if (isEntity(item)) {
			return a(escape(item.toString())).href(uriFor(item) + "/view");
		}

		if (isBean(item)) {
			return GUI.show(item);
		}

		String str = str(item);

		Tag tag = strTag(str);

		return (str.contains("{") || str.contains("}")) ? span(tag).is("ng-non-bindable", true) : tag;
	}

	private static Tag strTag(String str) {

		if (str == null) return N_A;
		int max = 1000;

		if (str.length() < max) {
			return hardcoded(escape(str));

		} else {
			return tooltip(span(hardcoded(escape(str.substring(0, max))), b(" ...")), str);
		}
	}

	private static boolean isBean(Object obj) {
		return Cls.isBean(obj)
			&& !(obj instanceof Tag)
			&& !(obj instanceof TagWidget);
	}

	private static Object display(Iterator it) {
		Tag wrap = div();
		int count = 0;

		Object item = null;

		while (it.hasNext()) {
			count++;
			item = it.next();
			wrap = wrap.append(div(FA.CIRCLE_O, NBSP, display(item)).class_("value-line"));
		}

		if (count == 0) {
			return span(NOTHING);
		}

		if (count == 1) {
			return item;
		}

		return wrap;
	}

	public static Object[] spaced(Object... contents) {
		Object[] arr = new Object[2 * contents.length - 1];

		int index = 0;
		for (int i = 0; i < contents.length; i++) {
			if (i > 0) {
				arr[index++] = NBSP;
			}
			arr[index++] = contents[i];
		}

		return arr;
	}

	public static Tag inline(Object... contents) {
		Tag ctrls = div(spaced(contents)).class_("form-group");
		return form(ctrls).class_("form-inline");
	}

	public static Layout layout(Object... contents) {
		return Cls.customizable(Layout.class).contents(contents);
	}

	public static Layout layout(Iterable contents) {
		return layout(U.array(contents));
	}

	public static Snippet snippet(String code) {
		return Cls.customizable(Snippet.class, code);
	}

	public static InfiniteStream infinite() {
		return Cls.customizable(InfiniteStream.class);
	}

	public static Layout values(Object... values) {
		List list = U.list();

		for (Object value : values) {
			if (Msc.isArray(value) && !hasGUIElements(value)) {
				value = U.str(value);
			}
			if (value == null || value instanceof Iterable) {
				value = U.str(value);
			}
			list.add(row(value));
		}

		return layout(list);
	}

	private static boolean hasGUIElements(Object value) {
		if (value instanceof Object[]) {
			Object[] arr = (Object[]) value;
			for (Object val : arr) {
				if (val instanceof TagWidget || val instanceof Tag) {
					return true;
				}
			}
		}
		return false;
	}

	public static Card card(Object... contents) {
		return Cls.customizable(Card.class).contents(contents);
	}

	public static Object i18n(String multiLanguageText, Object... formatArgs) {
		return new I18N(multiLanguageText, formatArgs);
	}

	public static Btn xClose(String cmd) {
		Tag sp1 = span(hardcoded("×")).attr("aria-hidden", "true");
		Tag sp2 = span("Close").class_("sr-only");
		return cmd(cmd).class_("close").contents(sp1, sp2);
	}

	public static Debug debug() {
		return Cls.customizable(Debug.class);
	}

	public static Object box(Object... contents) {
		return span(contents).class_("box");
	}

	public static String getCommand() {
		IReqInfo req = req();
		return !req.isGetReq() ? (String) req.posted().get("_cmd") : null;
	}

	public static boolean isEvent() {
		IReqInfo req = req();
		return U.eq(req.params().get("__event__"), "true");
	}

	public static Tag verb(HttpVerb verb) {
		Tag tag = span(verb);
		switch (verb) {
			case GET:
				tag = tag.class_("label label-success");
				break;

			case POST:
				tag = tag.class_("label label-primary");
				break;

			case PUT:
				tag = tag.class_("label label-warning");
				break;

			case DELETE:
				tag = tag.class_("label label-danger");
				break;

			default:
				tag = tag.class_("label label-default");
				break;
		}
		return tag;
	}

	public static Tag trTd(Object... cells) {
		Tag row = tr();

		for (Object cell : cells) {
			row = row.append(td(cell));
		}

		return row;
	}

	public static long newId() {
		return ID_GEN.incrementAndGet();
	}

	public static Object dygraph(String uri, TimeSeries ts, String divClass) {
		List points = U.list();

		NavigableMap values = ts.overview();

		for (Map.Entry e : values.entrySet()) {
			points.add(U.map("date", e.getKey(), "values", e.getValue()));
		}

		Map model = U.map("points", points, "names", U.list(ts.title()), "title", ts.title(),
			"id", newId(), "class", divClass, "uri", Str.triml(uri, "/"));

		Tag graph = hardcoded(Templates.load("dygraphs.html").render(model));

		return div(graph);
	}

	public static Object dygraph(String uri, TimeSeries ts) {
		return dygraph(uri, ts, "rapidoid-dygraph");
	}

	public static String uri(String... parts) {
		return ReqInfo.get().contextPath() + Msc.uri(parts);
	}

	public static String uriFor(Object target) {
		if (!isEntity(target)) {
			return "";
		}

		String typeUri = typeUri(target.getClass());
		return uriFor(typeUri, target);
	}

	public static String uriFor(String baseUri, Object target) {
		if (!isEntity(target)) {
			return "";
		}

		Object id = getIdentifier(target);
		return id != null ? Msc.uri(baseUri, id + "") : "";
	}

	public static String typeUri(Class entityType) {
		return typeUri(entityType.getSimpleName());
	}

	public static String typeUri(String entityType) {
		return uri(English.plural(Str.uncapitalized(entityType)).toLowerCase());
	}

	private static IReqInfo req() {
		return ReqInfo.get();
	}

	public static void markValidationErrors() {
		req().attrs().put("has-validation-errors", true);
	}

	public static boolean hasValidationErrors() {
		return Boolean.TRUE.equals(req().attrs().get("has-validation-errors"));
	}

	private static boolean isEntity(Object item) {
		return MscOpts.hasRapidoidJPA() && GuiJpaUtil.isEntity(item);
	}

	private static Object getIdentifier(Object bean) {
		return MscOpts.hasRapidoidJPA() ? GuiJpaUtil.getIdentifier(bean) : null;
	}

	public static String uri(String path, Map query) {
		StringBuilder sb = new StringBuilder();

		sb.append(path);

		boolean first = true;
		for (Map.Entry e : query.entrySet()) {
			if (first) {
				sb.append("?");
				first = false;
			} else {
				sb.append("&");
			}

			sb.append(Msc.urlEncode(e.getKey()));
			sb.append("=");
			sb.append(Msc.urlEncode(e.getValue()));
		}

		return sb.toString();
	}

	public static Tag copy(Object... content) {
		return div(content).class_("copy-snippet");
	}

	public static boolean isGUI(Object result) {
		return (result instanceof Tag)
			|| (result instanceof HtmlPage)
			|| (result instanceof TagWidget);
	}

	public static MultiWidget multi(Object... elements) {
		return new MultiWidget(elements);
	}

	public static void redirect(String uri) {
		req().setHeader("X-Rapidoid-Redirect", uri);
	}

	public static Tag tooltip(Tag target, String tooltip) {
		return target.data("toggle", "tooltip").data("placement", "top").attr("title", tooltip);
	}

	public static Tag render(String templateFile) {
		return render(templateFile, U.map());
	}

	public static Tag render(String templateFile, Map model) {
		return hardcoded(Render.file(templateFile).model(model));
	}

	public static Tag breadcrumb(Object... segments) {
		Tag breadcrumb = ol().class_("breadcrumb");

		for (Object segment : segments) {
			breadcrumb = breadcrumb.append(li(a_void(segment)));
		}

		return breadcrumb;
	}

	public static Tag breadcrumb(Map segments) {
		Tag breadcrumb = ol().class_("breadcrumb");

		for (Map.Entry e : segments.entrySet()) {
			breadcrumb = breadcrumb.append(li(a(e.getKey()).href(e.getValue())));
		}

		return breadcrumb;
	}

	public static Tag autoRefresh(long intervalMs) {
		return script(U.frmt("Rapidoid.setAutoRefreshInterval(%s);", intervalMs));
	}

	public static Object details(Object item) {
		return mid6(GUI.show(item));
	}

}