单页应用因为是用javascript来生成HTML,所以我们把所有的外部脚本放在head区块中,以便改进组织结构和易读性。
一般在一个单独的html文件中开发功能容器的HTML和CSS。只有把容器调整为我们喜欢的那样之后,才把代码移至shell的css和javascript文件。
1.当发生历史事件的时候,更改锚,同时更改程序状态。
spa.shell=(function(){
//------BEGIN MUDULE SCOPE VARIABLES------
//静态配置值放在configMap变量中
var
configMap={
//缩紧html字符,这样有利于理解和维护
main_html:String()
//嵌入logo,账户设置和head容器中的搜索框
+'<div class="spa-shell-head">'
+'<div class="spa-shell-head-logo"></div>'
+'<div class="spa-shell-head-acct"></div>'
+'<div class="spa-shell-head-search"></div>'
+'</div>'
//将导航(nav)和content容器放在主容器里面
+'<div class="spa-shell-main">'
+'<div class="spa-shell-main-nav"></div>'
+'<div class="spa-shell-main-content"></div>'
+'</div>'
//创建footer容器
+'<div class="spa-shell-foot"></div>'
//将chat容器固定在外部容器的右下角
+'<div class="spa-shell-chat"></div>'
//创建modal容器,漂浮在其它内容的上面
+'<div class="spa-shell-modal"></div>',
chat_extend_time:1000,
chat_retract_time:300,
chat_extend_height:450,
chat_retract_height:15,
chat_extended_title:'click to retract',
chat_retracted_title:'click to extend'
},
//将在整个模块中共享的动态信息放在stateMap变量中
stateMap={
$container:null,
is_chat_retracted:true
},
//将jQuery集合缓存在jqueryMap中
jqueryMap={},
//声明所有模块作用域的变量,很多都是在之后赋值
setJqueryMap,initModule,toggleChat,onClickChat;
//------END MODULE SCOPE VARIABLES
//------BEGIN UTILITY METHODS
//unility Method 保留区块,这些函数不和页面元素交互
//------END UTILITY METHODS------
//------BEGIN DOM METHODS------
//begin DOM method /setJqueryMap/
//将创建和操作页面元素的函数放在“DOM Method”区块中
//使用setJqueryMap来缓存jQuery集合。几乎编写的每个shell和功能模块都应该有这个函数
//用途是可以大大地减少jQuery对文档的遍历次数,能够提高性能.
setJqueryMap=function(){
var $container=stateMap.$container;
jqueryMap={
$container:$container,
$chat:$container.find('.spa-shell-chat')
};
};
//end DOM mothod /setJqueryMap/
//begin DOM method /toggleChat/
//Purpose :Extends or retracts chat slider
//Arguments:
// *do_extend - if true,extends slider;if false retracts
// *callback - optional function to execute at end of animation
//Settings
// *chat_extend_time,chat_retract_time
// *chat_extend_height,chat_retract_height
//Returns : boolean
// *true - slider animation activated
// *false - slider animation not activated
//State : sets stateMap.is_chat_retracted
// *true - slider is retracted
// *false - slider is extended
toggleChat=function(do_extend,callback){
var
px_chat_ht=jqueryMap.$chat.height(),
is_open=px_chat_ht===configMap.chat_extend_height,
is_closed=px_chat_ht===configMap.chat_retract_height;
is_sliding=!is_open&&!is_closed;
//avoid race condition
if(is_sliding){return false;}
//begin extend chat slider
if(do_extend){
jqueryMap.$chat.animate(
{height:configMap.chat_extend_height},
configMap.chat_extend_time,
function(){
jqueryMap.$chat.attr(
'title',configMap.chat_extended_title
);
stateMap.is_chat_retracted=false;
if(callback){callback(jqueryMap.$chat);}
}
);
return true;
}
//End extend chat slider
//begin restract chat slider
jqueryMap.$chat.animate(
{height:configMap.chat_retract_height},
configMap.chat_retract_time,
function(){
jqueryMap.$chat.attr(
'title',configMap.chat_retracted_title
);
stateMap.is_chat_retracted=true;
if(callback){callback(jqueryMap.$chat);}
}
);
return true
//end retract chat slider
}
//End Dom mothod /toggleChat/
//------END DOM METHODS------
//------BEGIN EVENT HANDLERS------
//为jquery事件处理函数保留的“Event Handlers”区块
onClickChat=function(event){
if(toggleChat(stateMap.is_chat_retracted)){
$.uriAnchor.setAnchor({
chat:(stateMap.is_chat_retracted?'open':'closed')
});
}
toggleChat(stateMap.is_chat_retracted);
return false;
}
//------END EVENT HANDLERS------
//------BEGIN PUBLIC METHODS------
//将公开的方法放在public method 区块中
//begin public method /initModule/
//创建initModule公开方法,用于初始化模块
initModule=function($container){
//load HTML and map jQuery collections
stateMap.$container=$container;
$container.html(configMap.main_html);
setJqueryMap();
//initialize chat slider and bind click handler
stateMap.is_chat_retracted=true;
jqueryMap.$chat
.attr('title',configMap.chat_retracted_title)
.click(onClickChat);
}
//end public method /initModule/
//显式地导出公开方法,以映射(map)的形式返回。目前可用的只有initModule
return {initModule:initModule};
//------END PUBLIC METHODS------
}());
我们需要确保,当锚变化的时候,只改变应用需要改变的地方。这会使应用快很多,当部分页面内容没必要清除和重新渲染时,避免了发生烦人的“闪烁”现象。
2.使用锚来驱动应用状态
我们希望的是始终让锚组件来驱动应用状态:
1.当发生历史事件时,更改URI的锚组件,以便体现更改的状态
--接收事件的处理程序调用shell的工具方法来改变锚
--然后事件处理程序退出
2.Shell的hashchange事件处理程序注意到了URI的变化并按它行事。
--将当前状态和新的锚表示的状态做比较
--根据比较确定的结果,尝试更改需要更改的应用部分
--如果不能处理请求的变化,则保持当前的状态,并恢复锚,以便和状态匹配。
spa.shell=(function($){
//------BEGIN MODULE SCOPE VARIABLE------
var
configMap={
anchor_schema_map:{
chat:{open:true,closed:true}
},
//缩紧html字符,这样有利于理解和维护
main_html:String()
//嵌入logo,账户设置和head容器中的搜索框
+'<div class="spa-shell-head">'
+'<div class="spa-shell-head-logo"></div>'
+'<div class="spa-shell-head-acct"></div>'
+'<div class="spa-shell-head-search"></div>'
+'</div>'
//将导航(nav)和content容器放在主容器里面
+'<div class="spa-shell-main">'
+'<div class="spa-shell-main-nav"></div>'
+'<div class="spa-shell-main-content"></div>'
+'</div>'
//创建footer容器
+'<div class="spa-shell-foot"></div>'
//将chat容器固定在外部容器的右下角
+'<div class="spa-shell-chat"></div>'
//创建modal容器,漂浮在其它内容的上面
+'<div class="spa-shell-modal"></div>',
chat_extend_time:1000,
chat_retract_time:300,
chat_extend_height:450,
chat_retract_height:15,
chat_extended_title:'click to retract',
chat_retracted_title:'click to extend'
},
//将在整个模块中共享的动态信息放在stateMap变量中
stateMap={
$container:null,
anchor_map:{},
is_chat_retracted:true
},
//将jQuery集合缓存在jqueryMap中
jqueryMap={},
copyAnchorMap,setJqueryMap,toggleChat,
changeAnchorPart,onHashchange,
onClickChat,initModule;
//------END MODULE SCOPE VARIABLES------
//------BEGIN UTILITY METHODS------
//Returns copy of stored anchor map;minimizes overhead(降低难度)
copyAnchorMap=function(){
return $.extend(true,{},stateMap.anchor_map);
}
//------END UTILITY METHODS------
//------BEGIN DOM METHODS------
//begin DOM method /setJqueryMap/
//将创建和操作页面元素的函数放在“DOM Method”区块中
//使用setJqueryMap来缓存jQuery集合。几乎编写的每个shell和功能模块都应该有这个函数
//用途是可以大大地减少jQuery对文档的遍历次数,能够提高性能.
setJqueryMap=function(){
var $container=stateMap.$container;
jqueryMap={
$container:$container,
$chat:$container.find('.spa-shell-chat')
};
};
//end DOM mothod /setJqueryMap/
//begin DOM method /toggleChat/
//Purpose :Extends or retracts chat slider
//Arguments:
// *do_extend - if true,extends slider;if false retracts
// *callback - optional function to execute at end of animation
//Settings
// *chat_extend_time,chat_retract_time
// *chat_extend_height,chat_retract_height
//Returns : boolean
// *true - slider animation activated
// *false - slider animation not activated
//State : sets stateMap.is_chat_retracted
// *true - slider is retracted
// *false - slider is extended
toggleChat=function(do_extend,callback){
var
px_chat_ht=jqueryMap.$chat.height(),
is_open=px_chat_ht===configMap.chat_extend_height,
is_closed=px_chat_ht===configMap.chat_retract_height;
is_sliding=!is_open&&!is_closed;
//avoid race condition
if(is_sliding){return false;}
//begin extend chat slider
if(do_extend){
jqueryMap.$chat.animate(
{height:configMap.chat_extend_height},
configMap.chat_extend_time,
function(){
jqueryMap.$chat.attr(
'title',configMap.chat_extended_title
);
stateMap.is_chat_retracted=false;
if(callback){callback(jqueryMap.$chat);}
}
);
return true;
}
//End extend chat slider
//begin restract chat slider
jqueryMap.$chat.animate(
{height:configMap.chat_retract_height},
configMap.chat_retract_time,
function(){
jqueryMap.$chat.attr(
'title',configMap.chat_retracted_title
);
stateMap.is_chat_retracted=true;
if(callback){callback(jqueryMap.$chat);}
}
);
return true
//end retract chat slider
}
//End Dom mothod /toggleChat/
//Begin DOM method /changeAnchorPart/
//purpose : Changes part of the URI anchor component
//Arguments:
// *arg_map - The map describing what part of the URI anchor we want changed
//Returns : boolean
// *true - the Anchor portion of the URI was update
// *false - the Anchor portion of the URI could not be updated
//Action :
// The current anchor rep stored in stateMap.anchor_map.
// See uriAnchor for a discussion of encoding.
// This method
// *Creates a copy of this map using copyAnchorMap();
// *Modifies the key-values using arg_map.
// *Manages the distinction between independent and dependent values in the encoding
// *Attempts to change the URI using uriAnchor
// *Returns true on success , and false on failure.
//
changeAnchorPart=function(arg_map){
var
anchor_map_revise=copyAnchorMap(),
bool_return=true,
key_name,key_name_dep;
//Begin merge changes into anchor map
KEYVAL:
for(key_name in arg_map){
if(arg_map.hasOwnProperty(key_name)){
//skip dependent keys during iteration
if(key_name.indexOf('_')===0){
continue KEYVAL;
}
// update independent key value
anchor_map_revise[key_name] = arg_map[key_name];
//update independent key values
key_name_dep="_"+key_name;
if(arg_map[key_name_dep]){
anchor_map_revise[key_name_dep] = arg_map[key_name_dep]
}else{
delete anchor_map_revise[key_name_dep];
}
delete anchor_map_revise['_s'+key_name_dep]
}
}
//End merge changes into anchor map
//Begin attempt to update URI ; revert if not successful
try{
$.uriAnchor.setAnchor(anchor_map_revise);
}
catch(error){
//replace URI with existing state
$.uriAnchor.setAnchor(stateMap.anchor_map,null,true);
bool_return=false;
}
//End attempt to update URI
return bool_return;
};
// End DOM method /changeAnchorPart/
//------END DOM METHODS------
//------BEGIN EVENT HANDLERS------
//Begin Event handlers /onHashchange/
//Arguments:
// *event - jQuery event object.
//Settings:none
//Returns :false
//Action:
// *Parses the URI anchor component
// *Compares proposed application state with current
// *Adjust the application only where proposed state differs from existing
//
onHashchange=function(event){
var
anchor_map_previous=copyAnchorMap(),
anchor_map_proposed,
_s_chat_previous,_s_chat_proposed,
s_chat_proposed;
//attempt to parse anchor,$.uriAnchor.makeAnchorMap() always produce a addition
//key '_s_'+key ,such as _s_chat
try{anchor_map_proposed=$.uriAnchor.makeAnchorMap();}
catch(error){
$.uriAnchor.setAnchor(anchor_map_previous,null,true);
return false;
}
stateMap.anchor_map=anchor_map_proposed;
//convenience vars
_s_chat_previous=anchor_map_previous._s_chat;
_s_chat_proposed=anchor_map_proposed._s_chat;
//Begin adjust chat component if changed
if(!anchor_map_previous||_s_chat_previous!==_s_chat_proposed){
s_chat_proposed=anchor_map_proposed.chat;
switch(s_chat_proposed){
case 'open' :
toggleChat(true);
break;
case 'closed' :
toggleChat(false);
break;
default:
toggleChat(false);
delete anchor_map_proposed.chat;
$.uriAnchor.setAnchor(anchor_map_proposed,null,true);
}
}
// End adjust chat component if changed
return false;
};
//End Event handler /onHashchange/
//Begin Event handler /onHashchange/
onClickChat=function(event){
changeAnchorPart({
chat:(stateMap.is_chat_retracted?'open':'closed')
});
return false;
}
//End Event handler /onClickChat/
//------END EVENT HANDLERS------
//------BEGIN PUBLIC METHODS
//Begin Public method /initModule/
initModule=function($container){
//load HTML and map jQuery collections
stateMap.$container=$container;
$container.html(configMap.main_html);
setJqueryMap();
//initialize chat slider and bind click handler
stateMap.is_chat_retracted=true;
jqueryMap.$chat
.attr('title',configMap.chat_retracted_title)
.click(onClickChat);
// 配置uriAnchor插件,用于检测模式(schema)
//configure uriAnchor to use our schema
$.uriAnchor.configModule({
schema_map:configMap.anchor_schema_map
});
//Handler URI anchor change events
//this is done /after/ all feature modules are configured
//and initialized ,otherwise they will not be ready to handle
//the trigger event ,which is used to ensure the anchor
//is considered on-load
//
$(window)
.bind('hashchange',onHashchange)
.trigger('hashchange');
};
//End Public method /initModule/
return {initModule:initModule};
//------END PUBLIC METHODS------
}(jQuery));
网友评论