Complete the Chat feature module

Một phần của tài liệu single page web applications (Trang 226 - 239)

In this section we’ll update the Chat feature module shown in figure 6.3. We can now take advantage of the Model’s chat and people objects to provide a simulated chat experience. Let’s revisit the Chat UI we mocked up earlier and decide how to modify it to work with the chat object. Figure 6.4 shows what we’d like to accomplish. We can distill this mockup into a list of capabilities that we’d like to add to the Chat feature module. These include:

■ Change the design of the chat slider to include the people list.

■ When the user signs in, perform the following actions: join the chat, open the chat slider, change the chat slider title, and display the list of online people.

If we find the chatee, update it to the new person object.

Figure 6.3 The Chat feature module in our SPA architecture

■ Update the online people list whenever it changes.

■ Highlight the chatee in the online people list and update the display whenever the list changes.

■ Empower the user to send a message and select a chatee from the online peo- ple list.

■ Display messages from the user, other people, and the system in the message log. These messages should all look different, and the message log should scroll smoothly from the bottom up.

■ Revise the interface to support touch controls.

■ When the user signs out, perform the following actions: change the title of the chat slider, erase the message log, and retract the slider.

Let’s start by updating the JavaScript.

6.4.1 Update the Chat JavaScript

We need to update the Chat JavaScript to add the capabilities we just discussed. The primary changes include:

■ Revise the HTML template to include the people list.

■ Create the scrollChat, writeChat, writeAlert, and clearChat methods to manage the message log.

■ Create user input event handlers, onTapList and onSubmitMsg, to allow the user to select a chatee from the people list and send a message. Ensure that touch events are supported.

■ Create the onSetchatee method to handle the Model-published spa-setchatee event. This will change the display of the chatee, change the chat slider title, and provide a system alert in the message window.

■ Create the onListchange method to handle the Model-published spa- listchange event. This will render the people list with the chatee highlighted.

■ Create the onUpdatechat method to handle the Model-published spa-update- chat event. This will display new messages sent by the user, the server, or other people.

Use different colors for alerts, our posts, and incoming messages.

User may select a person from list to chat.

The highlighted person is always the chatee.

Show an updated list of online people.

Set the title to show with whom

we are chatting.

Figure 6.4 What we want in the Chat UI

203 Complete the Chat feature module

■ Create the onLogin and onLogout methods to handle the Model-published spa-login and spa-logout events. The onLogin handler will open the chat slider when a user signs in. The onLogout handler will clear the message log, reset the title, and close the chat slider.

■ Subscribe to all Model-published events and then bind all user input events.

Let’s update the JavaScript file as shown in listing 6.12. Changes are shown in bold:

...

/*global $, spa */

spa.chat = (function () { 'use strict';

//--- BEGIN MODULE SCOPE VARIABLES --- var

configMap = {

main_html : String()

+ '<div class="spa-chat">' + '<div class="spa-chat-head">'

+ '<div class="spa-chat-head-toggle">+</div>' + '<div class="spa-chat-head-title">'

+ 'Chat' + '</div>' + '</div>'

+ '<div class="spa-chat-closer">x</div>' + '<div class="spa-chat-sizer">'

+ '<div class="spa-chat-list">'

+ '<div class="spa-chat-list-box"></div>' + '</div>'

+ '<div class="spa-chat-msg">'

+ '<div class="spa-chat-msg-log"></div>' + '<div class="spa-chat-msg-in">'

+ '<form class="spa-chat-msg-form">' Listing 6.12 Update the Chat JavaScript file—spa/js/spa.chat.js About those event handler names

We know some of you out there are thinking “Is there a reason why the method name onSetchatee isn’t onSetChatee?” Well, there is.

Our naming convention for event handlers is on<Event>[<Modifier>], where the Modifier is an option. This usually works great, as most events are single syllables.

Examples include onTap or onTapAvatar. This convention is handy so we can trace the handler precisely to the event that it’s handling.

Like all conventions, there are edge cases that can get confusing. In the case of on- Listchange, for example, we’ve followed our convention: the event name is listchange, not listChange. Thus onListchange is correct, whereas onListChange is not. The same holds true for onSetchatee and onUpdatechat.

Removed getComputedStyle from the global symbol list. This was used by getEmSize, which has been moved to our browser utilities module.

Add the use strict pragma.

Update the slider template to include the people list and other refinements.

+ '<input type="text"/>'

+ '<input type="submit" style="display:none"/>' + '<div class="spa-chat-msg-send">'

+ 'send' + '</div>' + '</form>' + '</div>'

+ '</div>' + '</div>' + '</div>', ...

slider_closed_em : 2,

slider_opened_title : 'Tap to close', slider_closed_title : 'Tap to open', slider_opened_min_em : 10,

...

}, ...

setJqueryMap, setPxSizes, scrollChat, writeChat, writeAlert, clearChat, setSliderPosition,

onTapToggle, onSubmitMsg, onTapList, onSetchatee, onUpdatechat, onListchange, onLogin, onLogout,

configModule, initModule, removeSlider, handleResize;

//--- END MODULE SCOPE VARIABLES --- //--- BEGIN UTILITY METHODS --- //--- END UTILITY METHODS --- //--- BEGIN DOM METHODS --- // Begin DOM method /setJqueryMap/

setJqueryMap = function () { var

$append_target = stateMap.$append_target,

$slider = $append_target.find( '.spa-chat' );

jqueryMap = {

$slider : $slider,

$head : $slider.find( '.spa-chat-head' ),

$toggle : $slider.find( '.spa-chat-head-toggle' ),

$title : $slider.find( '.spa-chat-head-title' ),

$sizer : $slider.find( '.spa-chat-sizer' ),

$list_box : $slider.find( '.spa-chat-list-box' ),

$msg_log : $slider.find( '.spa-chat-msg-log' ),

$msg_in : $slider.find( '.spa-chat-msg-in' ),

$input : $slider.find( '.spa-chat-msg-in input[type=text]'),

$send : $slider.find( '.spa-chat-msg-send' ),

$form : $slider.find( '.spa-chat-msg-form' ),

$window : $(window) };

};

// End DOM method /setJqueryMap/

// Begin DOM method /setPxSizes/

setPxSizes = function () {

Change the click to tap so that those with touch devices understand.

Declare the new methods to handle user and Model events.

Remove the getEmSize method, as it’s now available from our browser utilities (spa.util_b.js)

Update the jQuery collection cache for the modified chat slider.

205 Complete the Chat feature module

var px_per_em, window_height_em, opened_height_em;

px_per_em = spa.util_b.getEmSize( jqueryMap.$slider.get(0) );

window_height_em = Math.floor(

( jqueryMap.$window.height() / px_per_em ) + 0.5 );

...

} ...

// Begin public method /setSliderPosition/

...

setSliderPosition = function ( position_type, callback ) { var

height_px, animate_time, slider_title, toggle_text;

// position type of 'opened' is not allowed for anon user;

// therefore we simply return false; the shell will fix the // uri and try again.

if ( position_type === 'opened'

&& configMap.people_model.get_user().get_is_anon() ){ return false; }

// return true if slider already in requested position if ( stateMap.position_type === position_type ){

if ( position_type === 'opened' ) { jqueryMap.$input.focus();

}

return true;

}

// prepare animate parameters switch ( position_type ){

case 'opened' : ...

jqueryMap.$input.focus();

break;

...

} ...

};

// End public DOM method /setSliderPosition/

// Begin private DOM methods to manage chat message scrollChat = function() {

var $msg_log = jqueryMap.$msg_log;

$msg_log.animate(

{ scrollTop : $msg_log.prop( 'scrollHeight' ) - $msg_log.height()

}, 150 );

};

writeChat = function ( person_name, text, is_user ) { var msg_class = is_user

? 'spa-chat-msg-log-me' : 'spa-chat-msg-log-msg';

jqueryMap.$msg_log.append(

Use getEmSize from the browser utilities.

Get the jQuery collection for the window from the jqueryMap cache.

Add code to refuse to open the slider if user is anonymous.

The Shell callback will adjust the URI accordingly.

Add code to focus on the input box when the slider is opened.

Begin the section for all DOM methods used to manipulate the message log.

Create the scrollChat method to provide smooth scrolling of the message log as text appears.

Create the writeChat method to append to the message log. If the originator is the user, use a different style. Be sure to encode the HTML output.

'<div class="' + msg_class + '">'

+ spa.util_b.encodeHtml(person_name) + ': ' + spa.util_b.encodeHtml(text) + '</div>' );

scrollChat();

};

writeAlert = function ( alert_text ) { jqueryMap.$msg_log.append(

'<div class="spa-chat-msg-log-alert">' + spa.util_b.encodeHtml(alert_text) + '</div>'

);

scrollChat();

};

clearChat = function () { jqueryMap.$msg_log.empty(); };

// End private DOM methods to manage chat message

//--- END DOM METHODS --- //--- BEGIN EVENT HANDLERS --- onTapToggle = function ( event ) {

...

};

onSubmitMsg = function ( event ) { var msg_text = jqueryMap.$input.val();

if ( msg_text.trim() === '' ) { return false; } configMap.chat_model.send_msg( msg_text );

jqueryMap.$input.focus();

jqueryMap.$send.addClass( 'spa-x-select' );

setTimeout(

function () { jqueryMap.$send.removeClass( 'spa-x-select' ); }, 250

);

return false;

};

onTapList = function ( event ) {

var $tapped = $( event.elem_target ), chatee_id;

if ( ! $tapped.hasClass('spa-chat-list-name') ) { return false; } chatee_id = $tapped.attr( 'data-id' );

if ( ! chatee_id ) { return false; }

configMap.chat_model.set_chatee( chatee_id );

return false;

};

onSetchatee = function ( event, arg_map ) { var

new_chatee = arg_map.new_chatee, old_chatee = arg_map.old_chatee;

jqueryMap.$input.focus();

if ( ! new_chatee ) { if ( old_chatee ) {

Create the

writeAlert method to append system alerts to the message log. Be sure to encode the HTML output.

Create the clearChat method to clear the message log.

End the section for all DOM methods used to manipulate the message log.

Place user event handlers at the top of this section and place the Model event handlers at the bottom.

Rename onClickToggle event handler to

onTapToggle.

Create the onSubmitMsg event handler for a user- generated event when submitting a message to send. Use the model.chat.send_msg method to send the message.

Create the onTapList handler for a user- generated event when they click or tap on a person name. Use the model.chat.set _chatee method to set the chatee.

Create the onSetchatee event handler for the Model- published event spa- setchatee. This handler selects the new chatee and deselects the old one. It also changes the chat slider title and notifies the user that the chatee has changed.

207 Complete the Chat feature module

writeAlert( old_chatee.name + ' has left the chat' );

} else {

writeAlert( 'Your friend has left the chat' );

}

jqueryMap.$title.text( 'Chat' );

return false;

}

jqueryMap.$list_box

.find( '.spa-chat-list-name' ) .removeClass( 'spa-x-select' ) .end()

.find( '[data-id=' + arg_map.new_chatee.id + ']' ) .addClass( 'spa-x-select' );

writeAlert( 'Now chatting with ' + arg_map.new_chatee.name );

jqueryMap.$title.text( 'Chat with ' + arg_map.new_chatee.name );

return true;

};

onListchange = function ( event ) { var

vlist_html = String(),

people_db = configMap.people_model.get_db(), chatee = configMap.chat_model.get_chatee();

people_db().each( function ( person, idx ) { var select_class = '';

if ( person.get_is_anon() || person.get_is_user() ) { return true;}

if ( chatee && chatee.id === person.id ) { select_class=' spa-x-select';

}

list_html

+= '<div class="spa-chat-list-name'

+ select_class + '" data-id="' + person.id + '">' + spa.util_b.encodeHtml( person.name ) + '</div>';

});

if ( ! list_html ) { list_html = String()

+ '<div class="spa-chat-list-note">'

+ 'To chat alone is the fate of all great souls...<br><br>' + 'No one is online'

+ '</div>';

clearChat();

}

// jqueryMap.$list_box.html( list_html );

jqueryMap.$list_box.html( list_html );

};

onUpdatechat = function ( event, msg_map ) { var

is_user,

sender_id = msg_map.sender_id,

Create the onListchange event handler for the Model-published event spa-listchange. This handler gets the current people collection and renders the people list, making sure the chatee is highlighted if defined.

Create the onUpdatechat event handler for the Model- published event spa- updatechat. This handler updates the display of the message log. If the originator of the message is the user, it clears the input area and refocus. It also sets the chatee to the sender of the message.

msg_text = msg_map.msg_text,

chatee = configMap.chat_model.get_chatee() || {}, sender = configMap.people_model.get_by_cid( sender_id );

if ( ! sender ) {

writeAlert( msg_text );

return false;

}

is_user = sender.get_is_user();

if ( ! ( is_user || sender_id === chatee.id ) ) { configMap.chat_model.set_chatee( sender_id );

}

writeChat( sender.name, msg_text, is_user );

if ( is_user ) {

jqueryMap.$input.val( '' );

jqueryMap.$input.focus();

} };

onLogin = function ( event, login_user ) { configMap.set_chat_anchor( 'opened' );

};

onLogout = function ( event, logout_user ) { configMap.set_chat_anchor( 'closed' );

jqueryMap.$title.text( 'Chat' );

clearChat();

};

//--- END EVENT HANDLERS --- ...

initModule = function ( $append_target ) { var $list_box;

// load chat slider html and jquery cache stateMap.$append_target = $append_target;

$append_target.append( configMap.main_html );

setJqueryMap();

setPxSizes();

// initialize chat slider to default title and state

jqueryMap.$toggle.prop( 'title', configMap.slider_closed_title );

stateMap.position_type = 'closed';

// Have $list_box subscribe to jQuery global events $list_box = jqueryMap.$list_box;

$.gevent.subscribe( $list_box, 'spa-listchange', onListchange );

$.gevent.subscribe( $list_box, 'spa-setchatee', onSetchatee );

$.gevent.subscribe( $list_box, 'spa-updatechat', onUpdatechat );

$.gevent.subscribe( $list_box, 'spa-login', onLogin );

$.gevent.subscribe( $list_box, 'spa-logout', onLogout );

// bind user input events

jqueryMap.$head.bind( 'utap', onTapToggle );

jqueryMap.$list_box.bind( 'utap', onTapList );

jqueryMap.$send.bind( 'utap', onSubmitMsg );

Create the onLogin event handler for the Model-published event spa-login. This handler opens the chat slider.

Create the onLogout event handler for the Model- published event spa-logout. This handler clears the chat slider message log, resets the chat slider title, and closes the chat slider.

Modify initModule to append the updated slider template to the container specified by the caller.

Subscribe to all Model-published events first.

Bind all user input events next.

Binding before subscribing could result in a race condition

209 Complete the Chat feature module

jqueryMap.$form.bind( 'submit', onSubmitMsg );

};

// End public method /initModule/

...

Now that we have the JavaScript in place, let’s revise the stylesheets to match.

6.4.2 Update the stylesheets

We now will update the stylesheets for our enhanced interface. First we wish to update our root stylesheet to prevent selection of text on most elements. This removes an annoying user experience that’s especially noticeable on touch devices. The update is shown in listing 6.13. Changes are shown in bold:

...

/** Begin reset */

...

h1,h2,h3,h4,h5,h6,p { margin-bottom : 6pt; } ol,ul,dl { list-style-position : inside;}

* {

-webkit-user-select : none;

-khtml-user-select : none;

-moz-user-select : -moz-none;

Listing 6.13 Update the root stylesheet—spa/css/spa.css Template systems and you

Our SPA uses simple string concatenation to generate HTML, which is perfectly ac- ceptable for our purposes. But there comes a time when we require more sophisti- cated HTML generation. That’s when it’s time to consider a template system.

Template systems convert data into display elements. We can divide template sys- tems broadly by the language the developer uses to direct element generation. The Embedded style allows us to embed the host language—in our case, JavaScript—

directly in the template. The Toolkit style provides a domain-specific template lan- guage (DSL) independent of the host language.

We don’t recommend use of any Embedded style systems because they make it far too easy to intermingle business logic with display logic. The most popular JavaScript Embedded style system is probably provided by underscore.js’s template method, but there are many others.

We’ve noticed that Toolkit style systems in other languages have tended to become preferred over time. This is probably because these systems tend to encourage clean segregation of display and business logic. Many good Toolkit style template systems are available for SPAs. At the time of this writing, popular and well-tested Toolkit style template systems include Handlebars, Dust, and Mustache. We feel they’re all worthy of your consideration.

Add a selector that prevents text selection for all elements. We really look forward to the day when we can drop all these vendor prefixes like -moz or -ms or -webkit. This change would be one-sixth the size if we could!

-o-user-select : none;

-ms-user-select : none;

user-select : none;

-webkit-user-drag : none;

-moz-user-drag : none;

user-drag : none;

-webkit-tap-highlight-color : transparent;

-webkit-touch-callout : none;

}

input, textarea, .spa-x-user-select { -webkit-user-select : text;

-khtml-user-select : text;

-moz-user-select : text;

-o-user-select : text;

-ms-user-select : text;

user-select : text;

}

/** End reset */

...

We now need to update our Chat stylesheet. The primary changes include:

■ Style an online people list to be shown on the left side of the slider.

■ Make the slider wider to accommodate the people list.

■ Style the message window.

■ Remove all spa-chat-box* and spa-chat-msgs* selectors.

■ Add styles for messages received from the user, the chatee, and the system.

These updates are shown in listing 6.14. Changes are shown in bold:

...

.spa-chat { ...

right : 0;

width : 32em;

height : 2em;

...

} ...

.spa-chat-sizer { position : absolute;

top : 2em;

left : 0;

right : 0;

}

.spa-chat-list { position : absolute;

top : 0;

left : 0;

bottom : 0;

Listing 6.14 Update the Chat stylesheet—spa/css/spa.chat.css

Add a selector that makes an exception for input fields, text areas, or any element that has an spa-x-user-select class.

Make the chat slider class 10em wider to accommodate the people list.

Create a class to style the people list container on the left one-third of the chat slider.

211 Complete the Chat feature module

width : 10em;

}

.spa-chat-msg {

position : absolute;

top : 0;

left : 10em;

bottom : 0;

right : 0;

}

.spa-chat-msg-log, .spa-chat-list-box {

position : absolute;

top : 1em;

overflow-x : hidden;

}

.spa-chat-msg-log { left : 0em;

right : 1em;

bottom : 4em;

padding : 0.5em;

border : thin solid #888;

overflow-y : scroll;

}

.spa-chat-msg-log-msg { background-color : #eee;

}

.spa-chat-msg-log-me { font-weight : 800;

color : #484;

}

.spa-chat-msg-log-alert { font-style : italic;

background : #a88;

color : #fff;

}

.spa-chat-list-box { left : 1em;

right : 1em;

bottom : 1em;

overflow-y : auto;

border-width : thin 0 thin thin;

border-style : solid;

border-color : #888;

background-color : #888;

color : #ddd;

border-radius : 0.5em 0 0 0;

}

.spa-chat-list-name, .spa-chat-list-note { width : 100%;

padding : 0.1em 0.5em;

Create a class to style the message container on the right two-thirds of the chat slider.

Create common rules to style both the message log container and the people list container.

Add rules to style the message log container.

Create a class to style normal messages.

Create a class to style messages sent by the user.

Create a class to style system-alert messages.

Add rules to the style- people-list container.

Create common rules to style both a person name and a single notification shown in the people list.

Một phần của tài liệu single page web applications (Trang 226 - 239)

Tải bản đầy đủ (PDF)

(433 trang)