【0】README
1)本文部分文字描述转自:“Spring In Action(中/英文版)”,旨在review “spring(6) 渲染web视图” 的相关知识;
【1】 理解视图解析
【1.1】视图解析的基础知识以及spring 提供的其他视图解析器
1)spring mvc 定义了一个名为 ViewResolver的接口,如下
public interface ViewResolver { View resolveViewName(String viewName, Locale locale) throws Exception; }
2)当给 resolveViewName()方法传入一个视图名和Locale对象时,它会返回一个View实例。View是另外一个接口:
public interface View { String getContentType(); void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception; }
代码分析:View接口的任务就是接受模型以及 servlet的request 和 response对象,并将输出结果渲染到 response;
3)spring自带了13个视图解析器
对上表的分析(Analysis):
A1)InternalResourceViewResolver: 用于 JSP, TilesViewResolver用于Apache Tiles 视图, FreeMarkerViewResolver and VelocityViewResolver 分别用于 FreeMaker 和 Velocity模板视图;
A2)Thymeleaf: 是一种用来替代JSP 的新兴技术,spring提供了与 Thymeleaf 的原生模板协作的视图解析器;这种模板之所以得到这样的称呼是因为它更像最终产生的HTML,而不是驱动它们的java 代码;
【2】创建JSP视图
1)spring提供了两种支持 JSP 的视图(type)
type1)InternalResourceViewResolver 会将视图名解析为 jsp 文件;如果在jsp页面中使用了 JSP 标准标签库(java server page standard tag library)的话,InternalResourceViewResolver 能够将视图名解析为 JstlView 形式的jsp 文件,从而将JSTL 本地化和资源 bundle变量暴露给
JSTL 的格式化和信息标签;
type2)spring提供了两个JSP 标签库: 一个用于表单到模型的绑定,另一个提供了通用的工具类特性;
【2.1】配置适用于JSP的视图解析器
1)看个荔枝:
@Configuration @EnableWebMvc @ComponentScan(basePackages={"com.spring.chapter5.spittr.web", "com.spring.chapter5.spittr.data"}) public class WebConfig extends WebMvcConfigurerAdapter { @Bean public ViewResolver viewResolver() { // highlight line. InternalResourceViewResolver resolver = new InternalResourceViewResolver(); resolver.setPrefix("/WEB-INF/views/"); resolver.setSuffix(".jsp"); resolver.setExposeContextBeansAsAttributes(true); return resolver; } @Override public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) { configurer.enable(); } }
【2.1.1】解析JSTL 视图(jsp standard tag lib-java标准库标签)
1)intro: InternalResourceViewResolver 最终会将逻辑视图名解析为 InternalResourceView 实例,这个实例会引用jsp文件;
2)如果想让InternalResourceViewResolver 将视图解析为 JstlView,而不是 InternalResourceView 实例的话,只需要设置它的 viewClass属性即可:
@Bean public ViewResolver viewResolver() { InternalResourceViewResolver resolver = new InternalResourceViewResolver(); resolver.setPrefix("/WEB-INF/views/"); resolver.setSuffix(".jsp"); resolver.setViewClass(org.springframework.web.servlet.view.JstlView.class); return resolver; }
3)同样,在xml中的配置为:
<bean id="viewResolver" class="org.springframework.web.servlet.view. InternalResourceViewResolver" p:prefix="/WEB-INF/views/" p:suffix=".jsp" p:viewClass="org.springframework.web.servlet.view.JstlView" /> <span style="font-family: verdana, Arial, Helvetica, sans-serif; font-size: 14px; background-color: rgb(255, 255, 255);"> </span>
【2.2】使用 spring 的 jsp库
1)标签库的作用: 能够避免在脚本块中直接编写 java 代码;(干货——标签库的作用)
2)spring提供了两个JSP 标签库:一个用于表单到模型的绑定,另一个提供了通用的工具类特性;
【2.2.1】将表单绑定到模型上
1)intro:spring 的表单绑定jsp 标签库包含了14个标签,他们中的大多数都用用来渲染HTML 中的表单标签;
2)为了使用表单标签库,需要在jsp 页面上对其声明;
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="sf" %>
3)在注册JSP 中使用这些标签后,会得到如下程序:<sf:form method="POST" commandName="spitter">
First Name: <sf:input path="firstName" /><br /> Last Name: <sf:input path="lastName" /><br /> Email: <sf:input path="email" /><br /> Username: <sf:input path="username" /><br /> Password: <sf:password path="password" /><br /> <input type="submit" value="Register" /> </sf:form>
代码分析(Analysis): <sf:form>标签 会渲染为一个 HTML<form> 标签,通过 commandName 属性构建针对某个模型对象的上下文信息;这就需要我们必须要有一个key==spitter的对象;
4)我们可以指定email 域:
Email: <sf:input path="email" type="email" /><br/>
4.1)得到的 HTML 如下所示:
Email: <input id="email" name="email" type="email" value="jack"/><br/>
5)为了矫正用户输入错误,需要使用 标签 <sf:errors>
【2.2.2】展现错误
1)将 <sf:errors> 用到 registerForm.jsp 中的代码片段中;
<sf:form method="POST" commandName="spitter"> First Name: <sf:input path="firstName" /> <sf:errors path="firstName" /><br/> ... </sf:form>
代码分析(Analysis): 这里path 属性设置为 firstName,即指定了要显示 Spitter 模型对象中哪个属性的的错误;
看个荔枝)如果用户提交字母”J“的话,那么如下的HTML 片段就是针对 firstName 输入域所显示的内容:
First Name: <input id="firstName" name="firstName" type="text" value="J"/> <span id="firstName.errors">size must be between 2 and 30</span>
2)进一步修改错误的样式,使其更加突出显示;设置 cssClass属性:
<sf:form method="POST" commandName="spitter"> First Name: <sf:input path="firstName" /> <sf:errors path="firstName" cssClass="error" /> <br /> ... </sf:form>
2.1)再定义一个css样式:
span.error { color: red; }
3)problem+solution:
3.1)problem:在输入域的旁边展现错误信息是一种很好的方式,但是会带来布局问题;
3.2)solution:将所有的错误信息都 在同一个地方进行显示;我们可以移除每个输入域上的 <sf:errors> 元素,并将其放到表单的顶部,如下所示:
<sf:form method="POST" commandName="spitter" > <sf:errors path="*" element="div" cssClass="errors" /> ... </sf:form>
对以上代码分析(Analysis):这里的paht被设置为了 “*”,这是一个通配符选择器;会告诉 <sf: errors> 展现所有属性的所有错误;
4)通过为每个输入域设置 cssErrorClass 属性,显示需要修正的输入域;
<sf:form method="POST" commandName="spitter"> <sf:label path="firstName" cssErrorClass="error">First Name</sf:label>: <sf:input path="firstName" cssErrorClass="error" /> <br /> ... </sf:form>
对以上代码的分析(Analysis):
A1)<sf:label> 标签:使用path 来指定它属于模型对象中的 哪个属性;在本例中,将其设置为 firstName,它会绑定 spitter对象的 firstName属性;
A2)如果校验错误的话,渲染得到 的<label>元素中,class属性将会被设置为 error:
<label for="firstName" class="error">First Name</label>
A3)还可以设置一些css 属性
label.error { color: red; } input.error { }
5)如何让错误信息更加易读。可以在校验注解上设置 message属性,使其引用对用户更为友好的信息,这些信息定义在属性文件中;并将该属性文件放在根目录下;
// ValidationMessages.properties 文件内容如下(放置在根类路径下,即 project/src/ 目录下):
firstName.size=First name must be between {min} and {max} characters long. lastName.size=Last name must be between {min} and {max} characters long. username.size=Username must be between {min} and {max} characters long. password.size=Password must be between {min} and {max} characters long. email.valid=The email address must be valid.
【2.2.3】spring 通用的标签库
1)要使用spring通用的标签库,在页面上对其进行声明;
<%@ taglib uri="http://www.springframework.org/tags" prefix="s" %>
2)spring的jsp 标签库提供了多个便利的标签;
【2.2.4】展现国际化消息
1)考虑首页info:
<h1>Welcome to Spittr!</h1>
1.1)考虑将其国际化,<s:message>标签就是一个很好地方案;
<h1><s:message code="spittr.welcome" /></h1>
对以上代码的分析(Analysis):该标签将会工具 key=spittr.welcome 的信息源来渲染文本;
1.2)spring有多个信息源类,都实现了 MessageSource接口,最为常见的是ResourceBundleMessageSource.
1.3)intro to ResourceBundleMessageSource:它会从属性文件中加载信息,该属性文件的名称根据基础名称(base name)衍生而来的。如下的 @Bean 方法配置了 ResourceBundleMessageSource:
@Bean public MessageSource messageSource() { ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource(); messageSource.setBasename("messages"); // 核心在于设置basename属性值. return messageSource; }
对以上代码的分析(Analysis):将basename属性值设置为 messages后,SourceBundleMessageSource就会视图在根路径的属性文件中解析信息,这些属性文件的名称是根据这个基础名称衍生得到的;
1.4)还可以使用ReloadableResourceBundleMessageSource,它能够重新加载信息属性,而不必重新编译或重启应用,其配置如下:
@Bean public MessageSource messageSource() { ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource(); messageSource.setBasename("file:///etc/spittr/messages"); messageSource.setCacheSeconds(10); return messageSource; }
对以上代码的分析(Analysis):
A1)关键区别在于 basename 属性设置为在应用的外部查找(而不是像 ResourceBundleMessageSource 在类路径下查找);
A2)basename的属性可以设置在类路径下,文件系统中或web 应用的根路径下查找属性;
2)创建属性文件
2.1)创建默认的属性文件,名为 messages.properties:它要么位于根路径下(使用ResourceBundleMessageSource),要么位于pathname属性指定的路径下 (使用ReloadableResourceBundleMessageSource);
2.2)对于 spittr.welcome 信息来讲,需要如下条目:
spittr.welcome=Welcome to Spittr!
2.3)problem+solution:
2.3.1)problem:如果你不再创建其他信息文件的话,我们所做的事情就是将jsp中硬编码的信息抽取到了属性文件中,仍然是硬编码;
2.3.2)solution:如果想要设置西班牙语的话,需要创建另外一个 名为 message_es.properties 的属性文件
spittr.welcome=Bienvenidos a Spittr!
【2.2.5】创建URL
1)intro:<s:url>的主要任务就是创建 URL,然后将其赋值给一个变量或渲染到相应中, 它是 JSTL 中 <c:url> 标签的替代者;(干货——<s:url> 是 JSTL 中 <c:url> 标签的替代者)
2)看几个荔枝:
2.1)<a href="<s:url href="/spitter/register" />">Register</a>,如果应用的servlet上下文名为 spittr,则 <s:url> 将会渲染为:
<a href="/spittr/spitter/register">Register</a>
2.2)将<s:url> 进行赋值,并使用:
<s:url href="/spitter/register" var="registerUrl" /> <a href="${registerUrl}">Register</a>
2.3)default case, URL是在页面作用域内创建的。但通过设置scope 属性,可以让 <s:url> 在应用作用域内,会话作用域内或请求作用域内创建URL:(干货——default case,URL是在页面作用域内创建的)
<s:url href="/spitter/register" var="registerUrl" scope="request" />
2.4)如果希望添加参数的话,使用 <s:param>标签;
<s:url href="/spittles" var="spittlesUrl"> <s:param name="max" value="60" /> <s:param name="count" value="20" /> </s:url>
3)各种需求组团袭来
3.1)假设我们要为特定用户的基本信息页面创建一个URL,<s:param> 标签可以承担此任; (干货——这里用到了占位符{username})
<s:url href="/spitter/{username}" var="spitterUrl"> <s:param name="username" value="jbauer" /> </s:url>
3.2)解决URL 的转义需求:希望将渲染得到的URL 内容展现在 web 页面上(而不是作为超链接),这就要求 <s:url> 进行HTML 转义,将 htmlEscape属性设置为 true;
<s:url value="/spittles" htmlEscape="true"> <s:param name="max" value="60" /> <s:param name="count" value="20" /> </s:url> 所渲染得到的结果为: /spitter/spittles?max=60&count=20
3.3)解决在 javascript中使用URL的话,应该将 javascriptEscape 属性设置为 true;
<s:url value="/spittles" var="spittlesJSUrl" javaScriptEscape="true"> <s:param name="max" value="60" /> <s:param name="count" value="20" /> </s:url> <script> var spittlesUrl = "${spittlesJSUrl}" </script> 渲染得到的结果为: <script> var spittlesUrl = "\/spitter\/spittles?max=60&count=20" </script>
【2.2.6】转义内容(<s:escapeBody> 标签)
1)intro:<s:escapeBody> 标签是一个转义标签;
2)任务来啦:
2.1)任务一:需要将 "<" and ">" 字符替换为 <, >;<s:escapeBody> 可以做到这一点;
<s:escapeBody htmlEscape="true"> <h1>Hello</h1> </s:escapeBody> 所渲染得到的结果为: <h1>Hello</h1>
2.2)通过设置 javaScriptEscape属性,<s:escapeBody> 标签还支持 JavaScript 转义:
<s:escapeBody javaScriptEscape="true"> <h1>Hello</h1> </s:escapeBody>
【3】使用 Apache Tiles视图定义布局
1)intro:为了更好的定义视图布局,引入了布局引擎,如Apache Tiles;(干货——引入了布局引擎);
【3.1】配置 Tiles 视图解析器
1)intro:为了在spring 中使用 Tiles,需要配置几个 bean
1.1)需要一个 TilesConfigurer bean:负责定位和加载 Tile 定义并协调生成 Tiles;
1.2)还需要TilesViewResolver bean: 将逻辑视图名称解析为 Tile定义;
2)设置的具体操作:
2.1)需要一个 TilesConfigurer bean:负责定位和加载 Tile 定义并协调生成 Tiles;
@Bean public TilesConfigurer tilesConfigurer() { TilesConfigurer tiles = new TilesConfigurer(); tiles.setDefinitions(new String[] { "/WEB-INF/layout/tiles.xml" }); tiles.setCheckRefresh(true); return tiles; }
对以上代码的分析(Analysis):
A1)所要设置的最重要的属性是 definitions:这个属性接收一个 string 类型的 数组,其中每个条目都指定一个 Tile定义的 xml 文件;
A2)我们可以在数组中指定多个文件,并使用通配符;
tiles.setDefinitions(new String[] { "/WEB-INF/**/tiles.xml" });
对以上代码的分析(Analysis):
A1)我们要求 TilesConfigurer 加载 /WEB-INF/ 目录下的所有名字为 tiles.xml 的文件;
A2)上述代码使用了 Ant 风格的通配符(**);
2.2)还需要TilesViewResolver bean: 将逻辑视图名称解析为 Tile定义;
@Bean public ViewResolver viewResolver() { return new TilesViewResolver(); }
通过XML 配置的代码如下:
<bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles3.TilesConfigurer"> <property name="definitions"> <list> <value>/WEB-INF/layout/tiles.xml.xml</value> <value>/WEB-INF/views/**/tiles.xml</value> </list> </property> </bean> <bean id="viewResolver" class="org.springframework.web.servlet.view.tiles3.TilesViewResolver" />
Conclusion)
C1)TilesConfigurer 会加载 Tile定义并与 Apache Tiles协作;
C2)而 TilesViewResolver 会将逻辑视图名称解析为引用 Tile 定义的视图。它是通过查找与 逻辑视图名称相匹配的Tile 定义实现该功能的;
【3.1.1】 定义Tiles
1)intro:Apache Tiles 提供了一个文档类型定义(document type definition, DTD),用来在 XML文件中指定Tile的定义。每个定义中需要包含一个 <definition> 元素,这个元素会有一个或多个 <put-attribute>元素;
2)看个荔枝:如下的xml 文档为 Spittr 应用定义了几个 Tile:
<?xml version="1.0" encoding="ISO-8859-1" ?> <!DOCTYPE tiles-definitions PUBLIC "-//Apache Software Foundation//DTD Tiles Configuration 3.0//EN" "http://tiles.apache.org/dtds/tiles-config_3_0.dtd"> <tiles-definitions> <definition name="base" template="/WEB-INF/layout/page.jsp"> <put-attribute name="header" value="/WEB-INF/layout/header.jsp" /> <put-attribute name="footer" value="/WEB-INF/layout/footer.jsp" /> </definition> <definition name="home" extends="base"> // hightlight line: 这里用到了继承,碉堡了. <put-attribute name="body" value="/WEB-INF/views/home.jsp" /> </definition> <definition name="registerForm" extends="base"> <put-attribute name="body" value="/WEB-INF/views/registerForm.jsp" /> </definition> <definition name="profile" extends="base"> <put-attribute name="body" value="/WEB-INF/views/profile.jsp" /> </definition> <definition name="spittles" extends="base"> <put-attribute name="body" value="/WEB-INF/views/spittles.jsp" /> </definition> <definition name="spittle" extends="base"> <put-attribute name="body" value="/WEB-INF/views/spittle.jsp" /> </definition> </tiles-definitions>
对以上代码的分析(Analysis):
A1)每个 <definition> 元素都定义了一个 Tile,它最终引用的是一个 JSP 模板;
A2)某个 Tile 可能还引用其他的JSP模板,对于base Tile 来讲,引用的是一个头部 jsp 模板 和 一个底部 jsp 模板;
3)base Tile 所引用的page.jap 模板如下面程序清单所示:
<%@ taglib uri="http://www.springframework.org/tags" prefix="s" %> <%@ taglib uri="http://tiles.apache.org/tags-tiles" prefix="t" %> <%@ taglib prefix="tiles" uri="http://tiles.apache.org/tags-tiles" %> <%@ page session="false" %> <html> <head> <title>Spittr</title> <link rel="stylesheet" type="text/css" href="<s:url value="/resources/style.css" />" > </head> <body> <div id="header"> <t:insertAttribute name="header" /> </div> <div id="content"> <t:insertAttribute name="body" /> </div> <div id="footer"> <t:insertAttribute name="footer" /> </div> </body> </html>
对以上代码的分析(Analysis):重点在于 使用 Tile标签库中的 <t:insertAttribute> jsp 标签来插入其他的模板;插入了 header, body, footer 的模板;
【4】 使用Thymeleaf 模板
1)problem+solution:
1.1)problem:jsp最明显的问题:在于它看起来像 HTML 或 XML,但它其实并不是。大多数的jsp 模板都采用了HTML 的形式,但 又掺杂了各种jsp 标签库的标签,使其变得很是混乱;
1.2)solution:Thymelead模板:能在接收原始HTML 的地方进行编辑和渲染。因为它没有和 servlet规范耦合,因此 Thymeleaf 模板能够进入jsp 所无法涉足的领域;
【4.1】配置 Thymeleaf 视图解析器
1)intro:为了要在spring中 使用 Thymeleaf,我们要配置三个启用 Thymeleaf 与 spring集成的bean;
1.1)ThymeleafViewResolver:将逻辑视图名解析为 Thymeleaf 模板视图;
1.2)SpringTemplateEngine:处理模板并渲染结果;
1.3)TemplateResolver: 加载 Thymeleaf模板;
2)如下是声明这些bean 的 java配置;
@Bean public ViewResolver viewResolver(SpringTemplateEngine templateEngine) { ThymeleafViewResolver viewResolver = new ThymeleafViewResolver(); viewResolver.setTemplateEngine(templateEngine); return viewResolver; } @Bean public TemplateEngine templateEngine(TemplateResolver templateResolver) { SpringTemplateEngine templateEngine = new SpringTemplateEngine(); templateEngine.setTemplateResolver(templateResolver); return templateEngine; } @Bean public TemplateResolver templateResolver() { TemplateResolver templateResolver = new ServletContextTemplateResolver(); templateResolver.setPrefix("/WEB-INF/templates/"); templateResolver.setSuffix(".html"); templateResolver.setTemplateMode("HTML5"); return templateResolver; }
2.1)通过XML 声明这些bean 的java配置
<bean id="viewResolver" class="org.thymeleaf.spring3.view.ThymeleafViewResolver" p:templateEngine-ref="templateEngine" /> <bean id="templateEngine" class="org.thymeleaf.spring3.SpringTemplateEngine" p:templateResolver-ref="templateResolver" /> <bean id="templateResolver" class="org.thymeleaf.templateresolver.ServletContextTemplateResolver" p:prefix="/WEB-INF/templates/" p:suffix=".html" p:templateMode="HTML5" />
Conclusion)不管采用何种配置,Thymeleaf都可以将响应中的模板渲染到 spring mvc 控制器所处理的请求中;
3)ThymeleafViewResolver
3.1)intro:ThymeleafViewResolver 是 spring mvc 中 ViewResolver的一个实现类;
3.2)需要注意的是:ThymeleafViewResolver bean 中注入了一个对 SpringTemplateEngine bean 的引用;
3.2)SpringTemplateEngine 会在spring中启用 Thymeleaf引擎,用来解析模板,并基于这些模板渲染结果;
3.4)TemplateResolver会最终定位和查找模板,它也使用了prefix 和 suffix 属性。前缀和后缀将会与 逻辑视图名组合使用,进而定位 Thymeleaf 引擎,它的templateMode属性被设置为 HTML5,这表明我们预期要解析的模板会渲染成 HTML5 输出;
【4.2】定义 Thymeleaf 模板
1)intro: Thymeleaf之所以能够发挥作用,是因为它通过自定义的命名空间,为标准的HTML 标签集合添加 Thymeleaf属性;
2)使用 Thymeleaf模板的 home.html源码
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"> <head> <title>Spittr</title> <link rel="stylesheet" type="text/css" th:href="@{/resources/style.css}"></link> </head> <body> <h1>Welcome to Spittr</h1> <a th:href="@{/spittles}">Spittles</a> | <a th:href="@{/spitter/register}">Register</a> </body> </html>
【4.2.1】借助Thymeleaf实现表单绑定
1)review 表单绑定
1.1) 下面展现了在 registration.jsp 中的firname 输入域:
<sf:label path="firstName" cssErrorClass="error">First Name</sf:label>: <sf:input path="firstName" cssErrorClass="error" /><br/>
2)考虑如下的 Thymeleaf模板片段,它会渲染first name输入域:
<label th:class="${#fields.hasErrors(‘firstName‘)}? ‘error‘"> First Name</label>: <input type="text" th:field="*{firstName}" th:class="${#fields.hasErrors(‘firstName‘)}? ‘error‘" /><br/>
对以上代码分析(Analysis):
A1)不再使用jsp 标签中的 cssClassName 属性,而是在标准的HTML 标签中上使用 th:class 属性;th:class 属性会渲染成为一个 class属性;
A2)<input> 标签使用了 th:field 属性,用来引用后端对象的 firstname域;
3)以下代码暂时了完整的注册表单模板
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Insert title here</title> </head> <body> <form method="POST" th:object="${spitter}"><div class="errors" th:if="${#fields.hasErrors(‘*‘)}"> <ul> <li th:each="err : ${#fields.errors(‘*‘)}" th:text="${err}">Input is incorrect</li> </ul> Listing 6.7 Registration page, using Thymeleaf to bind a form to a command object Display errors 192 CHAPTER 6 Rendering web views </div> <label th:class="${#fields.hasErrors(‘firstName‘)}? ‘error‘"> First Name</label>: <input type="text" th:field="*{firstName}" th:class="${#fields.hasErrors(‘firstName‘)}? ‘error‘" /><br /> <label th:class="${#fields.hasErrors(‘lastName‘)}? ‘error‘"> Last Name</label>: <input type="text" th:field="*{lastName}" th:class="${#fields.hasErrors(‘lastName‘)}? ‘error‘" /><br /> <label th:class="${#fields.hasErrors(‘email‘)}? ‘error‘"> Email</label>: <input type="text" th:field="*{email}" th:class="${#fields.hasErrors(‘email‘)}? ‘error‘" /><br /> <label th:class="${#fields.hasErrors(‘username‘)}? ‘error‘"> Username</label>: <input type="text" th:field="*{username}" th:class="${#fields.hasErrors(‘username‘)}? ‘error‘" /><br /> <label th:class="${#fields.hasErrors(‘password‘)}? ‘error‘"> Password</label>: <input type="password" th:field="*{password}" th:class="${#fields.hasErrors(‘password‘)}? ‘error‘" /><br /> <input type="submit" value="Register" /> </form> </body> </html>