论坛首页 Web前端技术论坛

javascript总结(六)页面元素的创建调整与关联

浏览 3403 次
精华帖 (1) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2010-03-31   最后修改:2010-04-01

目录
(一)有关框架
(二)文件组织与代码组织
(三)JS与FLASH交互
(四)ajax局部刷新与RPC
  (五)获取设置元素样式与监听元素事件
  (六)页面元素的创建调整与关联
(七)浏览器兼容性问题
(八)WEB软件的前端架构实践

文章中的代码只为表达文章意义,非真正能执行的代码。
动态创建、调整element
一、你考虑清楚要直接操作element吗?

用这个疑问来表示对这种行为的慎重,操作element有哪些地方你可能需要考虑呢:
  • 美工对设计作了更改后,引起HTML结构变化时和样式变化时,需要变更代码(而不是模板)
  • 用代码来组织HTML结构,一点也不形象,维护起来也麻烦
  • 数据变化后,要在JS逻辑中维护页面变化,逻辑复杂,同样设计更改后,这部分代码也得改
  • 部分元素在不同浏览器下的操作方法不一致,导致你可能要针对不同浏览器来写代码
  • 通过标准DOM接口直接操作element与使用innerHTML整块设置HTML来比较,速度稍慢

即使有以上考虑,有以下情形我还是选择直接操作element
      
  • 如果业务逻辑足够简单,HTML结构也相对简单。比如更新用户名后更新页面上其他地方的用户名。
  • 如果HTML比较复杂,但数据或者状态变化后,只需要变化一个地方。
  • 在结构稳定的UI widget中,比如对话框。(以之相反的是grid,我会选择使用模板)
  • HTML中输入控件比较多 ,大部分时候需要保持控件的当前值和光标聚焦位置
  •     

另外,刷新整个部分与只更新变化的地方,用户体验会更好,不会闪的那么厉害,而且要做动画表示这个变化也方便。
二、应用模板进行局部更新
应用整体模板
        应用模板编程的优势及示例,我就不再重复。再次请大家参见金大为的雏凤清音 -- 面向数据的前端编程方法

应用局部模板
var groupManagerCmp = {
      body: "#userGroup",
      viewTemplete: new Template( "<tr><td>${name}</td><input type='button' value='编辑' /><td></td><td><input type='button' value='删除' /></td></tr>"),
      editTemplate:   new Template( "<tr><td><input type='text' value='${name}'></td><td><input type='button' value='提交' /></td><td><input type='button' value='取消' /></td></tr>"),
      renderViewRow: function(id, name){
         var tr = this.viewTemplete.bind(id,name);
         $("input[0]",tr).click(function(){ //编辑
             groupManagerCmp.edit(tr, id, name);
         })
        $("input[1]",tr).click(function(){ //删除
             action.do("delete", {id: id}, function(){
                 groupManagerCmp.remove(tr);
             })
         })
         return tr;
      },
      renderEditRow: function(id, name){
           var tr =  this.editTemplate.bind(id,name);
           $("input[0]",tr).click(function(){ //提交
              action.do("put", {id: id, name: name}, function(group){
                  groupManagerCmp.update(tr, group.id, group.name);
             })
           });
          $("input[1]",tr).click(function(){ //取消
             groupManagerCmp.update(tr, id, name);
         })
        return tr;
      },
      append: function(id, name){
          $(this.body).appendChild(this.renderViewRow(id, name));
      },
      remove: function(trNode){
         $(this.body).removeChild(trNode);
      },
      edit: function(trNode, id, name){
          $(this.body).replaceNode(trNode, this.renderEditRow(id, name));
      },
      update: function(trNode, id, name){
         $(this.body).replaceNode(trNode, this.renderViewRow(id,name));
      }
}

var action = {
    url: {
      post: "group/new",
      put: "group/update",
      delete: "group/destroy"
    }
    do: function(action, params, callback){
       ajax(this.url[action], params, function(data){
          callback(decode(data))
      });
     }
}

三、应用状态模式应对复杂的界面变化
         项目中有一个随机聊天的东西,系统从到达此页面的人中随机两两配对,进行聊天。
         业务过程:

用户点击开始配对-----》系统开始找人与你聊天     <-------------------------------
                                                            |                                                         |
                                        ----------------------------                                            |
                                       |                                  |                                           |
                         找到人,开始聊天       没找到,提示可重新配对                 |
                                       |                                  |                                           |
                                  聊天中                     点击重新配对-----------------------------
                                       |                                                                  |
                                       |                                                                  |
                  对方退出或中断,提示可重新配对                                  |
                                       |                                                                  |
                         ---------------------------                                                 |
                        |                                |                                                |
                 关闭页面                 点击重新配对--------------------------------


    //某种状态变化后,都调用updateView更新界面
  function updateView(state, args){
			if(state == "connectSuccess"){//连接成功
                           chatWin.show(args);
                           jQuery("connectWin").hide();
			}else{
                               chatWin.hide();
				if(state == "ready"){ //未连接,等待用户点击连接
				        //code
				}else if(state == "connecting"){ //正在连接
					 //code
				}else if(state == "connectFail"){ //连接失败
					jQuery("#status").text("连接失败");
                                        jQuery("#btnConnect").show();
				}else if(state == "strangerLeft"){ //对方离开
				          //code
				}else if(state == "connectInterrupt"){ //连接中断
				           //code
				}else if(state == "timeout"){//连接超时
					 //code
				}
			}
		}
 
   function connect(currentUser){
       chatProxy.connect(currentUser.userId, function(state, data){
           if(state == "success"){
               updateView("connectSuccess", data.stranger);
           }else{
               updateView("connectFail");
           }
       })
   }

三、应用观察者模式应对多处界面变化
        示例:当用户变更它的个人信息时,更新见面中显示用户个人信息的地方。用户的个人信息可能在多个页面的不同位置显示。
//用户类
User = function(uinfo){
    for(var p in uinfo){
      this[p]=uinfo[p];
    }
    this._observers = [];
};

User.prototype = {
            //更新属性
            update: function(property, value){
                    if(this.hasOwnProperty(property)){
                            if(this[property] != value){
                                    this[property] = value;
                                    this.notice(property);
                            }
                    }
            },
            //增加属性变更监听者(函数)
            addListener: function(property, observer){
                    if(!this._observers[property])this._observers[property] = [];
                    this._observers[property].push(observer);
            },
            //通知变更
            notice: function(property){
                    var observers = this._observers[property];
                    if(observers && observers.length){
                            var value = this[property];
                            $.each(observers, function(i, fn){
                                    fn && fn(value);
                            })
                    }
            }
    }

//在页面初始化代码中添加监听函数
jQuery(document).ready(function(){
currentUser.addListener("name", function(name){
    jQuery("#userName").text(name);
})
currentUser.addLIstener("icon", function(icon){
   jQuery("#userIcon").attr("src",icon);
})
})
//在业务代码中,当用户更新个人信息,用户对象自动调用监听函数更新页面。
currentUser.update("name","lucy");
currentUser.update("icon","url");

三、给元素操作取一个名字
给逻辑相关的元素操作封装在一个方法里面,并取一个与业务相关的名字是一个好的做法。特别是当这个操作的代码重复出现在多个地方的时候,更有这个必要。
function updateUserHometown(province, city){
   jQuery("#homeTown").html(provinece + "," + city);
}

在JS对象中关联element
一、直接关联还是通过查找引用
var testDiv = document.createElement("div");
testDiv.id = "testDiv";
document.body.appendChild(testDiv);
document.body.removeChild(testDiv);
alert(testDiv.nodeName); //DIV 
alert(testDiv.paretNode); //undefined
delete testDiv;  此时testDiv才被清除

通过以上代码可以看出,当你直接关联到元素时,就有可能造成元素被移除后,其对映DOM对象还在。基于这个原因,要小心因此可能造成内存泄露。
而通过ID或者其他选择器则不会有这个问题。但每次取不是很麻烦费时吗?其实你只要保证关联到元素的变量会被释放,还是应该直接引用的
function updateView(){
    var con = document.getElementById("container");
    //code ...
}
因为con是一个局部变量,所以它在函数调用后被释放了。
(function(){
var con = document.getElementById("container");
  updateView = function(){ 
   alert(con);
  }
})()
document.getElementById("container").parentNode.removeChild(document.getElementById("container"));
updateView(); //con还是存在。

二、避免引用散布到代码中
将元素CSS选择表达式散布的代码中,维护起来确实很麻烦,也使得我们的代码和页面结合得更加紧密,这是我们不希望看到的,考虑以下办法避免:
//方法一:通过参数传递元素选择表达式
function submitForm(){
 document.getElementById("userInfoForm").submit();
}
function  submitForm(formId){
        document.getElementById(formId).submit();
}
//<a href="javascript:submitForm("userInfoForm")" >提交</a>

//方法二:通过get方法把变化集中到一处
function getMainGrid(){
 return  document.getElementById("mainGrid");
}

//方法三:尽量把引用放到一处,然后通过传递参数引用元素。
function initSidebar(){
          var s1 = document.getElementById("s1");
          var s2 = document.getElementById("s2");
          new Sidebar(s1,"news")
          new SIdebar(s2, "videos");
}

三、考虑关联element被其他业务逻辑删除的情况
        在实际项目中,我做了一个文字滚动,做好之后,我发现滚动部分在多种情况下会被替换掉,而文字滚动我是通过setInterval来做的,虽然滚动动画的类里有停止滚动的接口,但由于刷新页面的逻辑不是集中于一个函数内,并且局部刷新页面的动作很大可能还会在未来的代码里面出现,因此我考虑还是由滚动类自己监测容器元素是否存在,由此来决定是否停止滚动,并释放自己。
论坛首页 Web前端技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics