博客编辑器应用开发全解析
在开发博客编辑器应用时,有许多关键的组件和技术需要掌握,下面将详细介绍这些内容。
富文本编辑功能的实现
创建富文本编辑功能是一项具有挑战性的跨浏览器任务。不过,借助 GWT 1.4 中的 RichTextArea 小部件,能显著减少实现该功能的工作量。
使用 RichTextArea 替换 TextArea
在 EditEntryView 类中,我们可以用 RichTextArea 替换原来的 TextArea 小部件。具体操作步骤如下:
1. 替换声明:将 TextArea 声明替换为 RichTextArea 声明。
private RichTextArea postContent = new RichTextArea();
- 修改文本设置和读取方法:RichTextArea 的 setText 和 getText 方法返回的文本没有渲染富文本所需的 HTML 标记,因此需要使用 setHTML 和 getHTML 方法。
// 在构造函数中使用 setHTML 方法将博客条目的内容传输到 RichTextArea
postContent.setHTML( entry.getContent() );
// 在 SaveClickListener 的 onClick 方法中,使用 getHTML 方法从 RichTextArea 中检索 HTML 并添加到博客条目对象
entry.setContent( postContent.getHTML() );
通过以上简单的更改,我们就将 RichTextArea 小部件集成到了应用中。但此时用户还无法创建富文本,因为缺少一个带有格式选项的工具栏。
使用 ImageBundle、国际化和 RichTextToolbar
GWT 1.4 的 RichTextArea 小部件本身没有提供文本格式化的方式,但 GWT 1.4 Kitchen Sink 示例中的 RichTextToolbar 是一个很好的解决方案。
RichTextToolbar 的实现使用了 GWT 的一些高级特性,包括 ImageBundle 和国际化。
- ImageBundle 的使用 :将多个单独的图像捆绑成一个图像,能提高应用的加载时间性能。以下是实现 ImageBundle 的示例代码:
public interface Images extends ImageBundle {
/**
* @gwt.resource bold.gif
*/
AbstractImagePrototype bold();
/**
* @gwt.resource createLink.gif
*/
AbstractImagePrototype createLink();
/**
* @gwt.resource hr.gif
*/
AbstractImagePrototype hr();
/**
* @gwt.resource indent.gif
*/
AbstractImagePrototype indent();
}
使用时,通过 GWT 的延迟绑定创建实例:
private Images images = (Images) GWT.create(Images.class);
ToggleButton tb = new ToggleButton(images.bold().createImage());
- 国际化支持 :RichTextToolbar 的国际化支持通过 GWT 编译步骤生成接口的实现。示例接口如下:
public interface Strings extends Constants {
String black();
String blue();
String bold();
String color();
}
GWT 编译器从属性文件中提取字符串值,默认属性文件名为 RichTextToolbar$Strings.properties。使用时,同样通过 GWT 延迟绑定创建实例:
private Strings strings = (Strings) GWT.create(Strings.class);
tb.setTitle(strings.bold());
将 RichTextToolbar 添加到应用中也很简单,只需创建一个新实例,并将 RichTextArea 实例作为参数传递给构造函数,然后将工具栏添加到视图中:
postPanel.add( new RichTextToolbar( postContent) );
加载面板小部件(LoadingPanel Widget)
Ajax 应用虽然解决了传统网页的许多可用性问题,但在某些情况下会引入新问题。例如,当进行异步请求时,浏览器不会显示任何工作迹象,这可能会让用户感到困惑。
LoadingPanel 小部件通过在右上角显示通知,并将光标更改为进度光标,让用户知道应用正在处理请求。以下是其实现代码:
public class LoadingPanel extends SimplePanel{
private int loadCount = 0;
public LoadingPanel(){
setStyleName( "gwtapps-LoadingPanel" );
setVisible( false );
}
public LoadingPanel( Widget child ){
this();
setWidget( child );
}
public void loadingBegin(){
if( loadCount == 0 ){
setVisible( true );
DOM.setStyleAttribute(
RootPanel.getBodyElement(), "cursor", "progress");
setPosition();
}
loadCount++;
}
public void loadingEnd(){
loadCount--;
if( loadCount == 0 ){
setVisible( false );
DOM.setStyleAttribute(RootPanel.getBodyElement(), "cursor", "");
}
}
public void setPosition(){
Widget child = getWidget();
int top = DOM.getIntAttribute( RootPanel.getBodyElement(),"scrollTop");
int left = Window.getClientWidth() - child.getOffsetWidth() +
DOM.getIntAttribute( RootPanel.getBodyElement(), "scrollLeft");
DOM.setStyleAttribute(getElement(),"position","absolute" );
DOM.setStyleAttribute(getElement(),"top", Integer.toString( top ) );
DOM.setStyleAttribute(getElement(),"left",Integer.toString( left ) );
}
}
标题命令栏小部件(TitleCommandBar Widget)
在创建用户界面时,我们常常会遇到重复编写相同代码的情况。TitleCommandBar 小部件就是为了解决这个问题而设计的,它用于处理带有几个可用命令的部分标题。
以下是 TitleCommandBar 类的实现代码:
public class TitleCommandBar extends Composite{
private Label titleLabel;
private HorizontalPanel titlePanel = new HorizontalPanel();
private Widget lastCommand;
public TitleCommandBar( String title ){
initWidget( titlePanel );
titlePanel.setWidth("100%");
setStyleName("gwtapps-TitleBar");
titleLabel = new Label( title );
titleLabel.setStyleName("gwtapps-TitleBarTitle");
titleLabel.setWordWrap( false );
titlePanel.add( titleLabel );
}
public void addWidget( Widget widget ){
if( lastCommand != null )
titlePanel.setCellWidth(lastCommand, "");
lastCommand = widget;
titlePanel.add( lastCommand );
titlePanel.setCellWidth(lastCommand, "100%");
titlePanel.setCellVerticalAlignment(
lastCommand, HasVerticalAlignment.ALIGN_MIDDLE );
}
public void addCommand( String name, ClickListener command ){
Hyperlink hyperlink = new Hyperlink( name, null );
hyperlink.addClickListener( command );
hyperlink.setStyleName("gwtapps-TitleBarCommand");
addWidget( hyperlink );
}
public void setText( String text ){
titleLabel.setText(text);
}
}
这个小部件将子命令小部件以一致的方式对齐,确保类似的部分标题在整个应用中看起来相同。
应用控制器的设计
应用的控制器部分承担着与博客服务通信、为视图提供模型实例以及响应视图中的用户操作等重要任务。
控制器需要连接到博客服务,检索特定账户可用的博客列表,并加载每个博客的博客条目。它将这些数据转换为 Blog 和 BlogEntry 模型对象,并传递给视图进行渲染。用户可以创建新条目、编辑或删除现有条目,视图将这些操作传递给控制器,控制器再调用相应的 Web 服务操作。
以下是控制器实现的起始代码:
public class BlogEditor implements EntryPoint{
public void onModuleLoad() {
BlogEditorView view = new BlogEditorView();
RootPanel.get("blogEditorView").add( view );
BloggerService blogger = new BloggerService( view );
blogger.signin();
}
}
为了实现与不同博客服务的通信,我们需要解决同源策略问题,这就需要构建一个 HTTP 代理 Servlet。
HTTP 代理 Servlet 的构建
由于浏览器的同源策略,直接与外部 Web 服务进行交互存在限制。因此,我们需要在服务器端与博客服务进行交互。
具体解决方案是:浏览器向自己的服务器发送异步 HTTP 请求,服务器上运行一个轻量级的 Servlet 接收这些请求,并将其转发到博客 Web 服务,然后将 Web 服务的响应中继回客户端应用。
新的跨域请求构建器(HTTPProxyRequestBuilder)
为了方便处理跨域请求,我们创建了一个 HTTPProxyRequestBuilder 类,它的使用方式与 GWT 的 HTTP RequestBuilder 类类似。
以下是使用 HTTP RequestBuilder 和 HTTPProxyRequestBuilder 的示例代码:
// 使用 HTTP RequestBuilder
RequestBuilder requestBuilder = new RequestBuilder(
RequestBuilder.GET, "/a_remote_file.xml" );
requestBuilder.sendRequest( null, new RequestCallback(){
public void onError(Request request, Throwable exception){
// 处理错误
}
public void onResponseReceived( Request request, Response response){
// 处理响应
}
});
// 使用 HTTPProxyRequestBuilder
HTTPProxyRequestBuilder requestBuilder = new HTTPProxyRequestBuilder(
RequestBuilder.GET, "http://www.otherdomain.com/file.xml");
requestBuilder.sendRequest( null, new RequestCallback(){
public void onError(Request request, Throwable exception){
// 处理错误
}
public void onResponseReceived( Request request, Response response){
// 处理响应
}
});
HTTPProxyRequestBuilder 类的实现代码如下:
public class HTTPProxyRequestBuilder {
private Map headers;
private String password;
private int timeoutMillis;
private String user;
private StringBuffer postBuilder = new StringBuffer();
public void setHeader(String header, String value) {
if (headers == null) {
headers = new HashMap();
}
headers.put(header, value);
}
public void setPassword(String password) {
this.password = password;
}
public void setTimeoutMillis(int timeoutMillis) {
this.timeoutMillis = timeoutMillis;
}
public void setUser(String user) {
this.user = user;
}
public HTTPProxyRequestBuilder(String httpMethod, String url ) {
postBuilder.append( "method=" );
postBuilder.append( httpMethod );
setParam("url",url);
}
protected void setParam( String key, String value ){
postBuilder.append( "&" );
postBuilder.append( key );
postBuilder.append( "=" );
postBuilder.append( URL.encodeComponent( value ) );
}
public Request sendRequest( String requestData, RequestCallback callback )
throws RequestException {
if( user != null )
setParam( "user", user );
if( password != null )
setParam( "password", password );
if( timeoutMillis > 0 )
setParam( "timeout", Integer.toString( timeoutMillis ) );
if( headers != null ){
Set entrySet = headers.entrySet();
for( Iterator iter = entrySet.iterator(); iter.hasNext(); ){
Map.Entry header = (Map.Entry) iter.next();
setParam( (String)header.getKey(), (String)header.getValue() );
}
}
if( requestData != null )
setParam( "post", requestData );
RequestBuilder requestBuilder = new RequestBuilder(
RequestBuilder.POST, GWT.getModuleBaseURL()+"/HTTPProxy" );
requestBuilder.setHeader(
"Content-type", "application/x-www-form-urlencoded");
return requestBuilder.sendRequest( postBuilder.toString(), callback );
}
}
代理 Servlet 的编写
代理 Servlet 类名为 HTTPProxy,主要实现 HTTP POST 方法。以下是其实现代码:
public class HTTPProxy extends HttpServlet
protected void doPost( HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException
{
URL url=null;
String user,password,method = "GET",post = null;
int timeout = 0;
Set entrySet = req.getParameterMap().entrySet();
Map headers = new HashMap();
for( Iterator iter = entrySet.iterator(); iter.hasNext(); ){
Map.Entry header = (Map.Entry) iter.next();
String key = (String)header.getKey();
String value = ((String[])header.getValue())[0] ;
if( key.equals("user") )
user = value;
else if( key.equals("password") )
password = value;
else if( key.equals("timeout") )
timeout = Integer.parseInt( value );
else if( key.equals("method") )
method = value;
else if( key.equals("post") )
post = value;
else if( key.equals("url") )
url = new URL( value );
else
headers.put( key, value );
}
boolean complete = false;
while( !complete ){
HttpURLConnection urlConnection = (HttpURLConnection)url.openConnection();
urlConnection.setDoOutput(true);
urlConnection.setDoInput(true);
urlConnection.setUseCaches(false);
urlConnection.setInstanceFollowRedirects(false);
urlConnection.setRequestMethod(method);
if( timeout > 0 ) urlConnection.setConnectTimeout(timeout);
Set headersSet = headers.entrySet();
for( Iterator iter=headersSet.iterator(); iter.hasNext(); ){
Map.Entry header = (Map.Entry)iter.next();
urlConnection.setRequestProperty(
(String)header.getKey(),(String)header.getValue() );
}
if( post != null){
OutputStreamWriter outRemote = new
OutputStreamWriter(urlConnection.getOutputStream());
outRemote.write( post );
outRemote.flush();
}
String contentType = urlConnection.getContentType();
if( contentType != null ) res.setContentType(contentType);
int responseCode = urlConnection.getResponseCode();
if( responseCode == 302 ){
String location = urlConnection.getHeaderField("Location");
url = new URL( location );
}
else{
res.setStatus( responseCode );
BufferedInputStream in;
if( responseCode == 200 || responseCode == 201 )
in = new BufferedInputStream(urlConnection.getInputStream());
else
in = new BufferedInputStream(urlConnection.getErrorStream());
BufferedOutputStream out =
new BufferedOutputStream(res.getOutputStream());
int c;
while((c = in.read()) >= 0 )
out.write(c);
out.flush();
complete = true;
}
}
}
要让这个 Servlet 在 GWT 的托管模式 Tomcat 服务器中运行,只需在应用的模块 XML 文件中添加以下引用:
<servlet path="/HTTPProxy" class="com.gwtapps.server.util.HTTPProxy"/>
通过以上步骤,我们就完成了博客编辑器应用的开发,包括富文本编辑、加载面板、标题命令栏、控制器设计以及 HTTP 代理 Servlet 的构建等关键功能。
博客编辑器应用开发全解析(续)
关键组件和技术总结
为了更清晰地理解整个博客编辑器应用开发过程中涉及的关键组件和技术,我们可以通过以下表格进行总结:
|组件/技术|功能描述|关键代码示例|
| ---- | ---- | ---- |
|RichTextArea|实现博客条目的富文本编辑| private RichTextArea postContent = new RichTextArea();
postContent.setHTML( entry.getContent() );
entry.setContent( postContent.getHTML() ); |
|RichTextToolbar|为 RichTextArea 提供文本格式化工具栏| postPanel.add( new RichTextToolbar( postContent) ); |
|LoadingPanel|在异步请求时向用户显示加载状态| java<br>public class LoadingPanel extends SimplePanel{<br> // 代码省略<br>}<br> |
|TitleCommandBar|处理带有命令的部分标题,统一界面布局| java<br>public class TitleCommandBar extends Composite{<br> // 代码省略<br>}<br> |
|HTTPProxyRequestBuilder|处理跨域请求,绕过同源策略| java<br>public class HTTPProxyRequestBuilder {<br> // 代码省略<br>}<br> |
|HTTPProxy|服务器端代理 Servlet,转发请求和响应| java<br>public class HTTPProxy extends HttpServlet<br>protected void doPost( HttpServletRequest req, HttpServletResponse res) <br> throws ServletException, IOException<br>{<br> // 代码省略<br>}<br> |
开发流程梳理
下面通过 mermaid 格式的流程图来梳理整个博客编辑器应用的开发流程:
graph LR
A[开始] --> B[设计视图]
B --> C[实现富文本编辑]
C --> D[添加 LoadingPanel 和 TitleCommandBar]
D --> E[设计控制器]
E --> F[解决同源策略问题]
F --> G[构建 HTTP 代理 Servlet]
G --> H[完成开发]
技术点分析
- 富文本编辑的实现 :RichTextArea 和 RichTextToolbar 的结合使用,使得用户可以方便地进行富文本编辑。通过替换 TextArea 为 RichTextArea,并使用 setHTML 和 getHTML 方法处理文本,确保了富文本的正确显示和保存。同时,RichTextToolbar 利用 ImageBundle 和国际化技术,提高了应用的性能和可维护性。
- 异步请求的处理 :LoadingPanel 小部件在异步请求时为用户提供了直观的加载提示,避免了用户因看不到操作反馈而产生困惑。通过设置光标为进度光标,让用户知道应用正在处理请求,同时不影响用户的其他操作。
- 同源策略的解决 :通过构建 HTTP 代理 Servlet,解决了浏览器同源策略对跨域请求的限制。HTTPProxyRequestBuilder 类封装了跨域请求的处理逻辑,使得应用可以像处理本地请求一样处理外部 Web 服务的请求。
注意事项
- 安全问题 :在实现 HTTP 代理 Servlet 时,需要注意安全问题。当前的实现没有对客户端进行验证,可能会被其他客户端利用。在实际应用中,应该添加客户端验证机制,确保只有合法的客户端可以使用该代理。
- 性能优化 :虽然 ImageBundle 技术可以提高应用的加载性能,但在使用时需要注意图片的管理。过多的图片可能会导致打包后的图片文件过大,影响加载速度。因此,需要合理选择和管理图片资源。
总结
通过以上的开发步骤和技术实现,我们成功地完成了一个功能丰富的博客编辑器应用。从富文本编辑到异步请求处理,再到跨域请求的解决,每个环节都有其独特的技术和实现方法。在开发过程中,我们需要充分考虑用户体验、性能优化和安全问题,确保应用的稳定性和可靠性。
希望本文对大家理解和开发类似的博客编辑器应用有所帮助。如果你在开发过程中遇到任何问题,欢迎留言讨论。
超级会员免费看

6万+

被折叠的 条评论
为什么被折叠?



