Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > Java EE (J2EE) и Spring > JSF жизненный цикл


Автор: Ovsyanka 30.10.2008, 13:53
Здравствуйте. Я совсем недавно начал работать с JSF и зачастую программа работает не так, как я планировал, а почему она работает именно так я понять не могу) 
Я сначала коротко опишу задачу и что я сделал:
Мне нужно сделать чат с использованием модального окна.
Есть некоторая страничка. Я добавил ссылку, которая показывает модальное окно. В нем есть таблица, в которой будут показываться входящие сообщения, поле для написания текста сообщения, поле для ввода имени(для кого сообщение), кнопка закрытия и кнопка отправки сообщения. 
Я хочу написать так, чтобы процесс происходил в следующем порядке:
1. Загружается вся форма, включая модальное окно(оно просто не отображается).
2. При нажатии на ссылку, ничего никуда не отправляется, просто показывается модальное окно.
3. При нажатии на кнопку отправить, должен считаться текст(и обнулиться) и имя, затем вызваться chat.sendMessage(ф-я отправки сообщения). А потом обновиться уже таблица с новыми сообщениями. 

Вот с третим пунктом у меня проблемы: все время получается, что у меня вызывается ф-я getNewMessages до sendMessages...
Я пробовал добавить аттрибуты immediate, ajaxSingle, bypassUpdates, но все равно getNewMessages вызывается...
У меня есть книга по Java Server Faces и я пытался найти ответ в ней, но, видимо пока я не в силах понять правильно то, что там написано ))

Вот исходные коды:
    .xhtml
Код

............

<a4j:commandLink type="button" id="openChat" title="Open chat" value="Новых сообщений: #{chat.newMessagesCount}">
    <rich:componentControl for="panelChat" attachTo="openChat" operation="show" event="onclick" />
</a4j:commandLink>

............

<ui:define name="innerForm">
    <rich:modalPanel id="panelChat" width="750" height="500">
        <f:facet name="header">
            <h:panelGroup>
                <h:outputText value="Chat Panel"></h:outputText>
            </h:panelGroup>
        </f:facet>
        <f:facet name="controls">
            <h:panelGroup>
                <a4j:commandButton value="close" reRender="openChat" id="bhidelink" title="close chat" />
                    <rich:componentControl for="panelChat" attachTo="bhidelink" operation="hide" event="onclick" />
            </h:panelGroup>
        </f:facet>
        <h:form id="chatForm">
            <h:dataTable id="listOfMessages" value="#{chat.newMessages}" var="row">
                <h:column>
                    <h:outputText id="test" value="#{row.sender}: " style="font-weight: bold" />
                    <h:outputText value="#{row.text}" />
                </h:column>
            </h:dataTable>
                        
            <h:inputTextarea id="inputMessage" value="#{chat.outgoingMessage}"></h:inputTextarea>
            <h:inputText id="inputTargetName" value="#{chat.targetName}" required="true"></h:inputText>
            <a4j:commandButton immediate="true"  title="send message" id="bSend" value="send" type="button"  
                            action="#{chat.sendMessage}" style="position: absolute; right: 50px; bottom: 0px" />
        </h:form>
    </rich:modalPanel>
</ui:define>

..................


    Chat.java
Код

..................

@Name("chat")
@Scope(ScopeType.SESSION)
public class Chat extends ASeamBaseBean {
    private int newMessagesCount;
    private ChatMessages c;
    private String outgoingMessage;
    private String targetName;
    private String login = this.getCurrentUser().getLogin();
    private static final Log logger = LogFactory.getLog(DictionaryCache.class);
    
    public String getTargetName() {return targetName;}

    public void setTargetName(String targetName) {this.targetName = targetName;}

    @In(value = "ChatMessages")
    public void setC(ChatMessages chatMessages) {
        c = chatMessages;
    }

    public int getNewMessagesCount() {
        newMessagesCount = c.getNewMessagesCount(login);
        return newMessagesCount;
    }

    public List<TMessage> getNewMessages() {
        logger.debug("getNewMessages");
        List<TMessage> result = new ArrayList<TMessage>();
        for(TMessage msg:c.getMessages(login)){
            if(msg.getStatus()==0) {
                result.add(msg);
                msg.setStatus(1);
            }
        }
        this.newMessages = result;
        return result;
    }

    public void setOutgoingMessage(String outgoingMessage) {this.outgoingMessage = outgoingMessage;}

    public String getOutgoingMessage() {
        logger.debug("getOutgoingMessage");
        return outgoingMessage; 
    }
    
    public String sendMessage(){
        logger.debug("sendMessage");
        c.sendMessage(login, targetName, outgoingMessage);
        this.outgoingMessage="";
        return "send";
    }
}


    ChatMessages.java
Код

....................

@Name("ChatMessages")
@Scope(ScopeType.APPLICATION)
@Startup
public class ChatMessages extends ASeamBaseBean {
    
    private List<TMessage> messages= new ArrayList<TMessage>();

    public int getNewMessagesCount(String name){
        int result = 0;
        for(TMessage m:messages){
            if (m.getStatus()==0) result++;
        }
        return result;
    }

    public List<TMessage> getNewMessages(String name){
        List<TMessage> result = new ArrayList<TMessage>();
        for(TMessage m:messages){
            if (m.getTarget().equals(name)) result.add(m);
        }
        return result;
    }
    
    public void sendMessage(String fromName, String forName,String msg){
        TMessage m = new TMessage(fromName,forName,msg);
        messages.add(m);
    }
}


     TMessage.java
Код

..............

public class TMessage {
    private String sender;
    private String target;
    private String text;
    private int status = 0; //прочитано или нет 0-не прочитано
    
    public TMessage(String fromName, String forName, String text){
        this.sender = fromName;
        this.target = forName;
        this.text = text;
    }
    
    public String getSender() {return sender;}
    public void setSender(String sender) {this.sender = sender;}
    public String getTarget() {return target;}
    public void setTarget(String target) {this.target = target;}
    public String getText() {return text;}
    public void setText(String text) {this.text = text;}
    public int getStatus() {return this.status;}
    public void setStatus(int status) {this.status = status;}
}


Буду благодарен, если кто-то объяснит почему так может происходить или подскажет как иначе мне это реализовать.
Извините, если я что-то сказать забыл, я практически не писал на форумах раньше.
Спасибо)

Автор: necromancer 30.10.2008, 14:43
это знакомая до боли проблема обновления дерева копнонент.
Могу посоветовать так:
у тебя выставление листа в таблице идет через value="#{getsomelist}" а попробуй в методе doMessage находить таблицу по ID и напрямую писать туда свой лист.
Недостатки - ты не сможешь выбрать через JSF запись в таблице напрямую (не используя извр методы).

либо же забей на что метод getNew... вызывается 2 раза - первый во время востановления view, второй во время рендеринга.
Просто в нем не делай никакой работы по обновлению листа, а всю работу переложи на метод doMessages, например:
Код

 private List list;
 public List getNewMessages() {
      return list;
 }
 public List doMessages() {
        ... запись сообщения
        list = ... получение списка сообщений
 }


Автор: Ovsyanka 30.10.2008, 15:31
Второй способ у меня вопросов не вызывает, а в первом, я так понимаю находить надо с помощью FacesContext.getCurrentInstance().дальше_не_помню, а потом с помощью UIComponent создавать таблицу?

Спасибо за варианты решения, но я хотел бы еще узнать почему происходит именно так... Или это ошибка и так не должно быть?

А sendMessage вы переименовали в doMessage потому, чтчо считаете, что это более подходящее название этой ф-и?) 

Автор: necromancer 30.10.2008, 16:20
>А sendMessage вы переименовали в doMessage потому, чтчо считаете, что это более подходящее название этой ф-и?)  
не знаю почему ... так... подумалось. =)

вот ссылка 
http://www.ibm.com/developerworks/library/j-jsf2/

если своими словами:
после нажатия кнопки субмит, JSF востанавливает дерево компонент , т.е. у каждого компонента вызывает метод setValue() и выставляет туда значение получившееся после выполнения тела атрибута. Это первый вызов метода. Далее происходит вызов бизнес метода. И уже на последнем этапе JSF дергает тело атирбута для получения новых данных в моделе (второй вызов метода)

FacesContext.getCurrentInstance().getView().findComponent("<formid>:<componentid>").get|setValue
учти при вызове getValue у найденого компонента будет выполнено тело атрибута у соотв. тэга.

>потом с помощью UIComponent создавать таблицу
нет необходимости, таблица уже пристуствует на view нужно просто получить к ней доступ и записать туда свой лист.

так же можно инициализировать значения в момент получения компонента (см. биндинг компонент), но данный метод имеет кучу недостатков ИМХО, в том числе завязывает логику с представлением и реализацией JSF.

Автор: Ovsyanka 1.11.2008, 12:58
Спасибо) только вот у меня же указан аттрибут type="button", читая книгу, я так понял, что в таком случае форма отправляться не должна... 

Автор: necromancer 1.11.2008, 13:52
этот аттрибут type ты где собрался указывать? в тэге input? =) в JSF компонентах нет такого тэга =) есть только:
h:commandButton
h:commandLink

форма может быть отправлена и по событию из JS

Автор: Ovsyanka 5.11.2008, 10:50
Ну так он у меня указан в a4j:CommandButton (см. .xhtml 32,33 строки)

Автор: necromancer 5.11.2008, 10:55
ты наверное попутал =) с вот этим:
immediate="true" 
но сути проблемы это не отменяет. дерево компонент все равно будет инициализироватся а потмо еще рендерится, т.е. вызов будет 2 раза (ИМХО)

Автор: Ovsyanka 5.11.2008, 11:08
Ну как так перепутал? )) 
Код

            <a4j:commandButton immediate="true"  title="send message" id="bSend" value="send" type="button"  
                            action="#{chat.sendMessage}" style="position: absolute; right: 50px; bottom: 0px" />

Вот у меня здесь type="button" указан...

Автор: necromancer 5.11.2008, 12:51
immediate    
True means, that the default ActionListener should be executed immediately (i.e. during Apply Request Values phase of the request processing lifecycle), rather than waiting until the Invoke Application phase

type    
This attribute specifies a type of control to create. The possible values are "submit", "reset", "image" and "button". The default value for this attribute is "submit"

http://www.jboss.org/file-access/default/members/jbossrichfaces/freezone/docs/devguide/en/html_single/index.html#commandButton

нигде в документации не сказано что type влияет на отправку запроса. Он влияет лишь на тип элемента. Так как submit - заставляет браузер посылать запрос. А button нет, но это не относится к commandButton, он посылает запрос при помощи JS.
Зачем нужен тип(type)? ну например для CSS или для каких то действий через JS.

например тэг <a4j:commandButton image=""/> переключит в type="image" но ведь из за этого оно не перестанет посылать запросы на сервер =)

Автор: Ovsyanka 5.11.2008, 13:05
Ага, я понял так: 
Не важно какого типа кнопка, она всегда посылает запрос на сервер. то есть получается, что нельзя сделать кнопку, которая не посылала бы запрос... 
Тогда в моем случае, кроме тех вариантов, что ты предложил можно использовать ссылку(CommandLink можно?)

А про type="button" я в книжке прочитал... видимо, не так понял.

Автор: necromancer 5.11.2008, 14:05
можно:
1. не использовать commandButton
2. использовать commandButton без указания action и eventListener (не уверен что это возможно)
3. Использовать на форме простые html тэги
4. Использовать <h:graphicImage onClick=...
5. написать PhaseListener который бы сбрасывал запрос
6. написать Filter
7. не использовать JSF совсем =)
ну и 8 пункт ты сам придумаешь ;-)

если вопрос разрешен, ставь галочку а мне поднимай репутацию =) ЙОХА!

Автор: Ovsyanka 5.11.2008, 14:29
Да, похоже, что решен.

Основное, что я хотел узнать, это почему у меня отправляется форма. Я узнал - потому, что commandButton всегда ее отправляет вне зависимости от type. 
Кроме этого тут написалось еще несколько полезных мыслей)

Спасибо за помощь, тему закрываю.

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)