微博@输入功能的ExtJS实现

微博的@辅助输入功能具有方便快捷的特点,基础功能描述为:

  • 1.在TextArea内输入“@”时,在光标处弹出选项列表。
  • 2.通过键盘或者鼠标选择选项,文本插入到textArea。

 

人肉相关文章之后才知道:在非ie浏览器下,获得TextArea的光标位置不是一件容易的事情!

目前大家常用的实现思路是:采用一个屏幕外的div模拟TextArea,通用地插入内容,通过div内光标位置来获得textarea内的光标位置。

这篇文章有详细的叙述:

http://yiminghe.iteye.com/blog/1482102

 

具体技术实现,jquery的找到一些,比如这篇文章:

http://lecoding.com/articles/316.html。

 

extjs下的实现则几乎没找到,只找到这一篇:

http://www.rahulsingla.com/blog/2010/12/extjs-re-usable-dropdownlist-for-any-form-field

作者是在ext3.1下实现的,对于这个实现我不甚满意:

  • 1.用DataView不是好选择

  简单的选项可以用Menu,复杂的用ListView,没必要去折腾DataView+Layer组合。

  即这个自定义派生类是不必要的。

  

  • 2.该实现并没有完善对有无弹出菜单两种情形下,对键盘事件的处理,连回车键录入都不能支持。

 

没办法,自己造轮子吧!

综合上述文章,实现思路如下:

  • 1.派生ExtJS的TextArea,实现支持@输入功能的增强版TextArea。
  • 2.采用ListView(GridPanel)作为弹出菜单,因为它已经支持showAt方法,可以在指定坐标显示。
  • 3.采用离屏div获得textarea内的光标坐标。
  • 4.为了处理菜单可见/隐藏 两种状态下的键盘事件,结合了extjs的三种键盘处理机制:

  listener: "specialkey" "keyup",

  以及Ext.KeyNav。

  原因是:前两种无法接管“回车”键,而第三种在接管之后,我没有找到何时的方法,将键盘消息传递回textarea,获得相同的效果

  (试了fireEvent,没成功。)

  

  • 5.关键点:通过程序语句选择菜单项时 XXX.getSelectionModel().select(0,true,true);

  后两个参数必须加上——阻止触发相关event,否则存在性能问题——响应严重延迟。

  

  • 6.代码采用ExtJS4.2,在chromefirefox下测试通过。

完整代码如下:

 

Ext.define('Ext.form.TA', {
   extend: 'Ext.form.TextArea',
   config: {
	enableKeyEvents:true,
  	listeners:{
  	 'specialkey': function(field, e){
  	 	this.onKey(e);
     },
	'keyup':function(ta,e,opt){
		var me=this;
		 switch(e.keyCode){
		 	case 50:
		 		me.showMenu();
			 break;
		 }
		}
	  }
	},
	
	showMenu:function(){
		var ta = this;
		var pos = ta.inputEl.dom.selectionStart;
		var txt = ta.getRawValue( ).substring(0,pos);
		var p0 = ta.getXY();
		var x = p0[0]+ta.getDivWidth(txt);
		var y = p0[1]+ta.getDivHeight(txt);
		//contactsDropDown.bindElement(ta.el);
		var mnu = this.mnu;
       	mnu.showAt([x,y]);
       	mnu.getSelectionModel().select(0,true,true);
       	this.focus();		
	},
	hideMenu:function(){
		this.mnu.hide();
	},
    constructor: function (config) {
    	config.width =config.width||400;
    	config.height =config.height||200;
        this.callParent(arguments); 
    },
	getDivHeight:function(text){
		this.heightDiv.setHTML(text.replace(/\n/g, '<br/>'));
		return this.heightDiv.getHeight() + 10;
	},
	getDivWidth:function(text){
       var h = this.getDivHeight('x');
       var olen = this.getDivHeight(text);
        if (h != olen) {
            for (var i = 0; i < text.length; ++i) {
                var s = text.substr(0, text.length - i);
                var len = this.getDivHeight(s);
                if (len != olen) {
                    text = text.substr(text.length - i);
                    break;
                }
            }
        }
        this.widthDiv.setHTML(text);
        return this.widthDiv.getWidth();
	},
	buildMenu:function(){
		var me=this;
		me.ds = Ext.create('Ext.data.Store', {
		    storeId:'simpsonsStore',
		    fields:['name', 'email', 'phone'],
		    data:{'items':[
		        { 'name': 'Lisa',  "email":"lisa@simpsons.com",  "phone":"555-111-1224"  },
		        { 'name': 'Bart',  "email":"bart@simpsons.com",  "phone":"555-222-1234" },
		        { 'name': 'Homer', "email":"home@simpsons.com",  "phone":"555-222-1244"  },
		        { 'name': 'Marge', "email":"marge@simpsons.com", "phone":"555-222-1254"  }
		    ]},
		    proxy: {
		        type: 'memory',
		        reader: {
		            type: 'json',
		            root: 'items'
		        }
		    }
		});
		
		me.mnu=Ext.create('Ext.grid.Panel', {
			floating:true,
		    //title: 'Simpsons',
			selModel: 'SINGLE',
		    store: Ext.data.StoreManager.lookup('simpsonsStore'),
		    columns: [
		        { text: 'Name',  dataIndex: 'name' },
		        { text: 'Email', dataIndex: 'email', flex: 1 },
		        { text: 'Phone', dataIndex: 'phone' }
		    ],
		    height: 200,
		    width: 400
		});	
		me.mnu.on("itemclick",function(gd, record, item, index, e, eOpts ){
			me.onSelOk();
		});
	},
	insertText:function(val){  
		var value=val=='\r\n'?val:val+' ';
        var ta = this.inputEl.dom;  
        var oriValue = this.getValue();  
        var pos_start = ta.selectionStart;
		this.setValue(oriValue.substring(0,ta.selectionStart) + value + oriValue.substring(ta.selectionEnd));  
        //ta.setValue(oriValue.toString() + value );  
        ta.selectionStart = pos_start+value.length;  
        ta.selectionEnd = ta.selectionStart;  
        //this.focus();
	}, 
	onSelOk:function(){
  	 	var sel = this.mnu.getSelectionModel();
  	 	var si = sel.getSelection();
  	 	if(!si || si.length==0)
  	 		return;
  	 	this.insertText(si[0].data['name']);
  	 	this.hideMenu();
	},
  	onKey: function(e){
  	 	if(!this.mnu.isVisible()){
  	 		if(e.getKey()==e.ENTER){
  	 			this.insertText('\r\n');
  	 		}
  	 		return;
  	 	}
  	 	e.stopEvent();
        // e.HOME, e.END, e.PAGE_UP, e.PAGE_DOWN,
        // e.TAB, e.ESC, arrow keys: e.LEFT, e.RIGHT, e.UP, e.DOWN
  	 	var sel = this.mnu.getSelectionModel();
  	 	var si = sel.getSelection();
  	 	var pos = 0;
  	 	if(si && si.length>0){
  	 		pos = si[0].index;
  	 	}
 		var mnu = this.mnu;
 	 	switch(e.getKey()){
        case e.DOWN:
	       	pos++
	       	if(pos>=this.ds.getCount())
	       		pos=0;
       		this.mnu.getSelectionModel().select(pos,true,true);
	       	break;
         case e.UP:
	       	pos--;
	       	if(pos<0)
	       		pos=this.ds.getCount()-1;
       		this.mnu.getSelectionModel().select(pos,true,true);
	       	break;
	     case e.ESC:
	     	this.hideMenu();
	       	break;
	     case e.ENTER:
	     	this.onSelOk();
	       	break;
       }
	   //this.focus();
    },
	
	
  buildKey:function(){
    this.keyNav = new Ext.KeyNav(this.inputEl, {
        "enter": function(e) {
        	console.log('enter');
        	this.onKey(e);
        },
        "esc": function(e) {
        	console.log('esc');
        	this.onKey(e);
        },
        "tab": function(e) {
        	console.log('tab');
        	this.onKey(e);
        },
        scope: this
    });
  },
    onRender: function () {
        this.callParent();
		this.heightDiv = Ext.DomHelper.append(document.body,{tag:'div',id:'height_div',style:'visibility:hidden;position:absolute;z-index:10000;top:0px;left:3000px;word-break:break-all;word-wrap:break-word'},true);
		this.heightDiv.setWidth(this.getWidth());
		this.heightDiv.setStyle('font-size',this.inputEl.getStyle('font-size'));
		this.widthDiv = Ext.DomHelper.append(document.body,{tag:'div',id:'width_div',style:'visibility:hidden;position:absolute;z-index:10000;top:0px;left:3000px;'},true);
		this.widthDiv.setHeight(this.getHeight());
		this.widthDiv.setStyle('font-size',this.inputEl.getStyle('font-size'));  	
		
		this.buildMenu();
		this.buildKey();
   }
});

 

附@弹出效果截图:

 

 

  

转载于:https://my.oschina.net/u/1440018/blog/543246

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值