28、博客编辑器与即时通讯应用开发解析

GWT构建博客与IM应用

博客编辑器与即时通讯应用开发解析

在当今的互联网应用开发领域,构建功能强大且用户体验良好的应用是开发者们的追求。本文将深入探讨博客编辑器应用和即时通讯应用的开发要点、设计思路以及具体实现。

博客编辑器应用

博客编辑器应用的开发已经完成了视图中所有可用操作的实现,控制器也能与博客服务进行充分交互,以获取数据并填充视图。要使用这个应用,只需使用你的博客账户登录,如果没有账户,也可以免费创建一个。

该应用的一大亮点在于,它完全在浏览器中运行,无需其他依赖。借助 Google Web Toolkit(GWT),我们能够使用 Java 强大的面向对象结构和开发工具来构建设计精良的应用,而在应用部署时无需依赖 Java。部署过程也非常简单,使用 GWT 生成的编译脚本 BlogEditor-compile 后,将 www 目录中的文件复制即可。这些文件是该版本应用特有的简单 HTML 文件,若更新应用,文件名会改变,用户只需刷新浏览器就能自动获取新版本。

即时通讯应用

即时通讯应用是基于定义良好的 GWT - RPC 接口构建的典型示例。每个加载该应用的客户端都会连接到服务器上的一个 servlet,并通过 RPC 进行方法调用。由于每个客户端都连接到同一服务器,因此客户端之间可以进行协作。

协作应用模式

传统桌面应用通常是为个人工作设计的,但在现实世界中,人们更多地以团队形式工作,需要多人协作处理同一文档。协作应用模式通过将协作系统直接构建到应用中,解决了手动协作的不便。借助互联网的普及,构建协作应用变得更加容易,因为数据可以在不同来源之间轻松传输。要使用协作模式构建应用,需要将模型定义为通过控制器访问的集中式模型,每个协作客户端与控制器交互以操作模型,控制器负责通知其他客户端模型的变化。

应用类型 协作方式 特点
传统桌面应用 个人工作为主 单用户操作,缺乏协作机制
基于协作模式的应用 通过控制器协作 多用户协作,实时同步模型变化
graph LR
    classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px

    A(客户端A):::process --> B(控制器):::process
    C(客户端B):::process --> B(控制器):::process
    D(客户端C):::process --> B(控制器):::process
    B --> A
    B --> C
    B --> D
    B --> E(模型):::process
    E --> B
即时通讯设计

即时通讯的主要功能是在两人或多人之间提供即时文本通信,比电子邮件更具对话性,比电话更不具干扰性。通过 Ajax 将这种应用引入浏览器,能让人们在任何有浏览器的地方进行通信,无需安装客户端软件。

我们将创建一个专门为网页设计的即时通讯应用,它将作为嵌入式小部件与网页集成,为用户提供在线状态显示和即时通信功能。客户端加载网页时,GWT 应用也会加载并连接到服务器。用户可以输入姓名,接收服务器发送的事件,如其他用户上线、离线以及消息等,也可以向其他用户发送消息。

即时通讯服务器将作为具有 GWT - RPC 接口的 GWT servlet 运行,客户端连接并调用 servlet 的方法来支持即时通讯功能。该应用的内部设计遵循 Model - View - Controller(MVC)架构。

模型设计

即时通讯应用的模型包含三个类: ContactList Contact Message 。这些类虽然简单,但有助于组织代码。如果要为应用添加更多功能,将更多数据放入模型中,就能体会到分离应用组件的好处。

以下是模型类的代码实现:

public class ContactList {
    private Contact me;
    private List contacts = new ArrayList();
    public ContactList( String name ){
        me = new Contact( name );
    }
    public Contact getMe(){
        return me;
    }
    public void addContact( Contact contact){
        contacts.add( contact );
    }
    public int getContactCount(){
        return contacts.size();
    }
    public Contact getContact( int index ){
        return (Contact) contacts.get(index);
    }
}

public class Contact implements IsSerializable{
    private String name;
    public Contact(){}
    public Contact( String name ){
        this.name = name;
    }
    public String getName(){
        return name;
    }
}

public class Message implements IsSerializable{
    private String message;
    public Message(){}
    public Message( String message ){
        this.message = message;
    }
    public String toString(){
        return message;
    }
}

这些类实现了 GWT 的 IsSerializable 接口,允许其实例通过 GWT - RPC 协议传输。

构建互补界面

该应用的视图负责向用户提供其他用户的在线状态和消息的即时反馈。为了不干扰网页的主要使用,我们将即时通讯应用作为现有网页的补充,插入到具有 messengerView ID 的 HTML 元素中,通常显示在页面的侧边栏。

应用视图的事件流程如下:
1. 显示登录视图,要求用户输入显示名称。
2. 用户提供登录名后,显示联系人列表,应用开始接收服务器事件。
3. 视图根据事件渲染模型,可能显示新联系人或新消息。
4. 用户可以通过点击联系人打开聊天窗口,输入消息并发送。

为了实现这些功能,我们需要四个视图类: MessengerView SignInView ContactListView ChatWindowView

graph LR
    classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px

    A(MessengerView):::process --> B(SignInView):::process
    A --> C(ContactListView):::process
    A --> D(ChatWindowView):::process
    B --> A
    C --> A
    D --> A

以下是部分视图类的代码实现:

MessengerView 类

public class MessengerView extends Composite 
    implements WindowCloseListener, WindowResizeListener{
    private DeckPanel mainPanel = new DeckPanel();
    private ContactListView contactListView;
    private SignInView signIn;
    private ContactList contactList;
    private List openChatWindows = new ArrayList();
    private Map allChatWindows = new HashMap();
    private MessengerViewListener listener;
    public MessengerView( MessengerViewListener listener ){
        initWidget( mainPanel );
        this.listener = listener;
        signIn = new SignInView( this );
        mainPanel.add( signIn );
        mainPanel.showWidget(0);
        Window.addWindowCloseListener(this);
        Window.addWindowResizeListener(this);
    }
    // 其他方法...
}

SignInView 类

public class SignInView extends Composite {
    private VerticalPanel mainPanel = new VerticalPanel();
    private TextBox nameBox = new TextBox();
    private MessengerView view;
    public SignInView( MessengerView view ){
        initWidget( mainPanel );
        this.view = view;
        mainPanel.setVerticalAlignment(HasVerticalAlignment.ALIGN_MIDDLE);
        mainPanel.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_CENTER);
        Button signInButton = new Button( "Sign In" );
        VerticalPanel vpanel = new VerticalPanel();
        vpanel.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_CENTER);
        vpanel.add( new Label( "Sign in name:") );
        vpanel.add( nameBox );
        vpanel.add( signInButton );
        mainPanel.add( vpanel );
        nameBox.addKeyboardListener(new KeyboardListenerAdapter() {
            public void onKeyPress(Widget sender, char keyCode, int modifiers){
                if(keyCode == KEY_ENTER )
                    signIn();
            }
        });
        signInButton.addClickListener(new ClickListener() {
            public void onClick(Widget sender){
                signIn();
            }
        });
    }
    public void signIn(){
        String name = nameBox.getText();
        if( name.length() > 0 ){
            mainPanel.clear();
            mainPanel.add( new Label("Signing in...") );
            view.getListener().onSignIn(name);
        }
    }
}

这些视图类相互协作,为用户提供了一个完整的即时通讯体验。通过合理的设计和实现,我们可以开发出功能强大、用户体验良好的应用。在后续的开发中,还可以进一步扩展这些应用的功能,如添加更多的博客服务、优化即时通讯的性能等。

博客编辑器与即时通讯应用开发解析

即时通讯应用(续)
ContactListView 类

ContactListView 类与 SignInView 占据页面的同一位置,其父视图 MessengerView 使用 DeckPanel 来处理两者之间的切换。用户成功登录后, ContactListView 会显示,但最初除了当前用户位于顶部外,没有其他联系人。随着其他用户访问页面并登录到即时通讯应用,控制器和服务器会创建新的 Contact 实例并分发给每个客户端。

以下是 ContactListView 类的代码实现:

public class ContactListView extends SimplePanel {
    private class ContactClickListener implements ClickListener{
        Contact contact;
        ContactClickListener( Contact contact ){ 
            this.contact = contact; 
        }
        public void onClick( Widget sender ){ 
            view.getChatWindowView( contact ); 
        }
    }
    private FlexTable listTable = new FlexTable();
    private MessengerView view;
    public ContactListView( MessengerView view ){
        setWidget( listTable );
        this.view = view;
        listTable.setCellPadding(0);
        listTable.setCellSpacing(0);
        addContact( view.getContactList().getMe() );
        listTable.getRowFormatter().addStyleName(0, "myContactItem" );
        listTable.getColumnFormatter().setWidth(1,"100%");
    }
    public void addContact( Contact contact ){
        int row = listTable.getRowCount();
        Hyperlink link = new Hyperlink( contact.getName(), null );
        listTable.getRowFormatter().setVerticalAlign(
            row,HasVerticalAlignment.ALIGN_TOP);
        listTable.setWidget(row,0,new Image("icon_user.gif"));
        listTable.getRowFormatter().setStyleName(row, "contactItem" );
        if( row != 0 ) listTable.setWidget(row,1,link);
        else listTable.setText(row,1, contact.getName() );
        link.addClickListener( new ContactClickListener(contact) );
    }
    public void removeContact( Contact contact ){
        for( int i=1; i<listTable.getRowCount();  ){
            Hyperlink link = (Hyperlink)listTable.getWidget(i,1);
            if( link.getText().equals( contact.getName() ) )
                listTable.removeRow(i);
            else ++i;
        }
    }
}

该类实现了 GWT 的 SimplePanel ,使用 FlexTable 来布局联系人列表。 addContact 方法用于添加新联系人, removeContact 方法用于移除联系人。每个联系人以可点击的链接形式显示,点击后会打开与该联系人的聊天窗口。

方法 功能
addContact 向联系人列表中添加新联系人
removeContact 从联系人列表中移除指定联系人
graph LR
    classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px

    A(ContactListView):::process --> B(addContact):::process
    A --> C(removeContact):::process
    B --> A
    C --> A
ChatWindowView 类

ChatWindowView 类负责渲染一个允许用户向另一个 Contact 发送消息的视图,并在垂直滚动列表中显示两个联系人之间的消息历史,最新消息显示在底部。

该视图的实现代码可分为三个部分:
1. 构建视图和创建小部件布局

public class ChatWindowView extends Composite{
    private MessengerView view;
    private Contact contactTo;
    private ScrollPanel conversationScroller = new ScrollPanel();
    private VerticalPanel conversationPanel = new VerticalPanel();
    private TextBox input = new TextBox();
    int position = -1;
    public ChatWindowView( MessengerView view, Contact contactTo ){
        this.view = view;
        this.contactTo = contactTo;
        VerticalPanel mainPanel = new VerticalPanel();
        initWidget( mainPanel );
        TitleCommandBar titleBar;
        titleBar = new TitleCommandBar( contactTo.getName() );
        titleBar.addCommand("close", new ClickListener(){ 
            public void onClick( Widget sender ){ 
                ChatWindowView.this.view.closeChatWindow( ChatWindowView.this ); 
            }
        });
        mainPanel.add(titleBar);
        VerticalPanel vpanel = new VerticalPanel();
        vpanel.setHeight("100%");
        vpanel.setWidth("100%");
        mainPanel.add( vpanel );
        mainPanel.setCellHeight(vpanel, "100%");
        conversationScroller.setHeight("100%");
        vpanel.add( conversationScroller );
        vpanel.setCellHeight(conversationScroller, "100%");
        conversationScroller.setWidget( conversationPanel );
        vpanel.add( input );
        input.setWidth("100%");
        input.addKeyboardListener( new InputListener() );
        RootPanel.get().add( this );
    }
}

该部分代码创建了视图的主要布局,包括标题栏、消息列表滚动面板和消息输入框。标题栏有一个关闭命令,点击后会调用 MessengerView closeChatWindow 方法关闭窗口。

  1. 响应用户消息输入并添加新消息到消息列表
private class InputListener extends KeyboardListenerAdapter{
    public void onKeyPress(Widget sender, char keyCode, int modifiers){
        if( keyCode == KEY_ENTER ){
            String text = input.getText();
            if( text.length()>0 ){
                Message message = new Message(text);
                addMessage( view.getContactList().getMe(), message );
                view.getListener().onSendMessage( contactTo, message );
                input.setText("");
            }
        }
    }
}

public void addMessage( Message message ){
    addMessage( contactTo, message );
}
public void addMessage( Contact contact, Message message ){
    HTML messageLabel = new HTML( 
        "<b>" + contact.getName() + "</b>: " + message); 
    conversationPanel.add( messageLabel );
    conversationScroller.setScrollPosition(
        conversationPanel.getOffsetHeight());
    messageLabel.setStyleName("convMessage");
}

InputListener 类监听用户的按键事件,当用户按下回车键时,创建一个新的 Message 对象,调用 addMessage 方法将消息添加到视图中,并调用 MessengerViewListener onSendMessage 方法将消息发送给服务器。 addMessage 方法负责更新视图中的消息列表,并将滚动条滚动到最新消息处。

  1. 将视图与浏览器窗口右下角对齐
static final int CHAT_WINDOW_WIDTH = 250;
static final int CHAT_WINDOW_HEIGHT = 200;
static final int CHAT_WINDOW_MARGIN = 5;
static final int CHAT_WINDOW_OFFSET = 20;
public void show(){
    setWidth( CHAT_WINDOW_WIDTH + "px" );
    setHeight(CHAT_WINDOW_HEIGHT + "px");
    setVisible( true );
    setPosition( position );
    input.setFocus( true );
}
public void setPosition( int position ){
    this.position = position;
    int top = Window.getClientHeight() + DOM.getIntAttribute(
        RootPanel.getBodyElement(), "scrollTop") -
        getOffsetHeight() - CHAT_WINDOW_MARGIN;
    int left = Window.getClientWidth() + DOM.getIntAttribute( 
        RootPanel.getBodyElement(), "scrollLeft") -
        CHAT_WINDOW_WIDTH*(position+1) - CHAT_WINDOW_MARGIN*(position+1) -
        CHAT_WINDOW_OFFSET;
    DOM.setStyleAttribute(getElement(),"position","absolute" );
    DOM.setStyleAttribute(getElement(),"top",Integer.toString(top));
    DOM.setStyleAttribute(getElement(),"left",Integer.toString(left) );
    conversationScroller.setScrollPosition(
        conversationPanel.getOffsetHeight());
}

通过设置一些常量值, setPosition 方法根据传入的 position 参数将视图定位到浏览器窗口的右下角,并考虑了窗口滚动的情况。

综上所述,通过对博客编辑器应用和即时通讯应用的开发解析,我们了解了如何使用 GWT 构建功能丰富、用户体验良好的应用。从应用的整体架构设计,到各个模块的具体实现,每个环节都有其独特的设计思路和技术要点。无论是博客编辑器应用的跨平台特性,还是即时通讯应用的协作模式和界面设计,都为我们在开发类似应用时提供了宝贵的参考。在实际开发中,我们可以根据具体需求对这些应用进行扩展和优化,以满足不同用户的需求。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值