1. #1
    Sencha Premium Member tintin's Avatar
    Join Date
    Apr 2008
    Posts
    3
    Vote Rating
    0
    tintin is on a distinguished road

      0  

    Post Ext.ux.BoxSelect (like the Facebook's one)

    Ext.ux.BoxSelect (like the Facebook's one)


    After having seen this, read this, and this, I wrote this extension.



    Demo

    Usage:
    • Items can be deleted individually
    • Managed keys: backspace, delete, left/right arrows

    Todo list:
    • Manage some combo config options like allowBlank=false

    Tested in IE6/7, FF2/3, Opera, Chrome.

    All advices are welcome

    04.30.2008 : Corrected bug on delete key pressed

    09.15.2008 : NEW VERSION
    • File renamed (initially it was intended to be a plugin but not anymore)
    • Duplicate entries avoided (it's not an option, otherwise it wouldn't work correctly)
    • Right rendering in different browsers (rounded corners only in Gecko)
    • New methods like setValue, enable, disable
    • Initial value handled in different ways (collection of record, array of IDs, string of IDs)
    Attached Files
    Usually named efattal
    ExtJS Contributions:
    Ext.ux.ToastWindow | Ext.ux.BoxSelect | Ext.ux.VirtualKeyboard

  2. #2
    Sencha - Community Support Team VinylFox's Avatar
    Join Date
    Mar 2007
    Location
    Baltimore, MD
    Posts
    1,501
    Vote Rating
    8
    VinylFox will become famous soon enough VinylFox will become famous soon enough

      0  

    Default


    Love it...you come up with some sweet stuff. Love toast as well.

    Keep up the good work.

  3. #3
    Sencha User chalu's Avatar
    Join Date
    Feb 2008
    Location
    Benin City, Nigeria
    Posts
    480
    Vote Rating
    1
    chalu is on a distinguished road

      0  

    Wink Wow

    Wow


    This is awesome, you've handed me the base for my next extension . Keep it up

  4. #4
    Sencha User krycek's Avatar
    Join Date
    Jun 2007
    Posts
    96
    Vote Rating
    0
    krycek is on a distinguished road

      0  

    Default


    tintin, just see your extension.

    Great great work.

    Congratulations!


  5. #5
    Ext JS Premium Member stever's Avatar
    Join Date
    Mar 2007
    Posts
    1,407
    Vote Rating
    6
    stever will become famous soon enough stever will become famous soon enough

      0  

    Default


    Very nice!

  6. #6
    Ext Premium Member
    Join Date
    Mar 2007
    Posts
    175
    Vote Rating
    0
    SteveEisner is on a distinguished road

      0  

    Default


    This rocks. Great job.

    I've noticed from the demo that there are some selection issues:
    * when you start to type, and then back up erasing the entire word, it selects the previous "resolved" item rather than leaving you with a caret on empty text
    * cursor left and right gets confused sometimes (again, probably around empty text)

    I haven't used the component yet but if when do I'll try to figure out what's breaking those
    Steve

  7. #7
    Ext User
    Join Date
    Mar 2007
    Posts
    8
    Vote Rating
    0
    pibos is on a distinguished road

      0  

    Default Ext.ux.BoxSelect mod

    Ext.ux.BoxSelect mod


    I added initialValue functionality to Ext.ux.BoxSelect so that BoxSelect can be shown with intitial selection. Just add the value field to BoxSelect constructor config; it's value must be an array of objects having at minimum the fields specifyied in displayField and valueField

    here is the code


    Ext.ux.BoxSelect
    Code:
    Ext.namespace('Ext.ux.plugins');
    
    Ext.ux.Box = Ext.extend(Ext.Component, {
    	initComponent : function(){
    		Ext.ux.Box.superclass.initComponent.call(this);
    	},
    
    	onRender: function(ct, position){
    		Ext.ux.Box.superclass.onRender.call(this, ct, this.maininput);
    		
    		this.addEvents('remove');
    		
    		this.addClass('bit-box');
    		
    		this.el = ct.createChild({ tag: "li" }, this.maininput);
    		this.el.addClassOnOver('bit-hover');
    		
    		Ext.apply(this.el, {
    			
    			'focus': function(){
    				this.down('a.closebutton').focus();
    			},
    			
    			'dispose': function(){
    				this.dispose()
    			}.createDelegate(this)
    			
    		});
    
    		this.el.on('click', function(e){
    			this.focus()
    		}, this, {stopEvent:true});
    
    		this.el.update(this.caption);
    
    		this.lnk = this.el.createChild({
    			'tag': 'a',
    			'class': 'closebutton',
    			'href':'#'
    		});
    				
    		this.lnk.on({
    			'click': function(e){
    				e.stopEvent();
    				this.fireEvent('remove', this);
    				this.dispose();
    			},
    			'focus': function(){
    				this.el.addClass("bit-box-focus");
    			},
    			'blur': function(){
    				this.el.removeClass("bit-box-focus");
    			},
    			scope: this
    		});
    		
    		new Ext.KeyMap(this.lnk, [
    			{
    				key: [Ext.EventObject.BACKSPACE, Ext.EventObject.DELETE],
    				fn: function(){
    					this.dispose();
    				}.createDelegate(this)
    			},
    			{
    				key: Ext.EventObject.RIGHT,
    				fn: function(){
    					this.move('right');
    				}.createDelegate(this)
    			},
    			{
    				key: Ext.EventObject.LEFT,
    				fn: function(){
    					this.move('left');
    				}.createDelegate(this)
    			},
    			{
    				key: Ext.EventObject.TAB,
    				fn: function(){
    				}.createDelegate(this)
    			}
    		]).stopEvent = true;
    
    	},
    	
    	move: function(direction) {
    		if(direction == 'left')
    			el = this.el.prev();
    		else
    			el = this.el.next();
    		if(el)
    			el.focus();
    	},
    		
    	dispose: function() {
    		//if(el.prev() && this.retrieveData(el.prev(), 'small') ) el.prev().remove();
    		//if(this.current == el) this.focus(el.next());
    		//if(el.data['type'] == 'box') el.onBoxDispose(this);
    		
    		Ext.fly(this.hidden).remove();
    		this.el.hide({
    			duration: .1,
    			callback: function(){
    				this.move('right');
    				this.destroy()
    			}.createDelegate(this)
    		});
    		
    	
    		return this;
    	}
    });
    
    Ext.ux.BoxSelect = Ext.extend(Ext.form.ComboBox, {
    	initComponent:function() {
    		Ext.apply(this, {
    			selectedValues: {},
    			boxElements: {},
    			current: false,
    			options: {
    				className: 'bit',
    				separator: ','
    			},
    			hideTrigger: true,
    			grow: false
    		});
    			
    		Ext.ux.BoxSelect.superclass.initComponent.call(this);
    	},
    	
    	onRender:function(ct, position) {
    		Ext.ux.BoxSelect.superclass.onRender.call(this, ct, position);
    
    		this.el.removeClass('x-form-text');
    		this.el.className = 'maininput';
    		this.el.dom.name = '';
    		this.el.setWidth(20);
            this.el.dom.value = ''
    
    
    		this.holder = this.el.wrap({
    			'tag': 'ul',
    			'class':'holder x-form-text'
    		});
    				
    		this.holder.on('click', function(e){
    			e.stopEvent();
    			if(this.maininput != this.current) this.focus(this.maininput);		 
    		}, this);
    
    		this.maininput = this.el.wrap({
    			'tag': 'li', 'class':'bit-input'
    		});
    		
    		Ext.apply(this.maininput, {
    			'focus': function(){
    				this.focus();
    			}.createDelegate(this)
    		});
    
    		if(typeof this.displayFieldTpl  === 'string')
    		    this.displayFieldTpl = new Ext.XTemplate(this.displayFieldTpl)
    
            if(this.value){
                if(typeof this.value === 'string')
                    this.value = [this.value]
            }
            
            Ext.each(this.value, function(item){
        
                if(this.displayFieldTpl)
                    caption = this.displayFieldTpl.apply(item);
                else
                    caption = item[this.displayField]
                
                this.addBox(item[this.valueField], caption);
            }, this);
    
    		
    	},
    	
    	onResize : function( w, h, rw, rh ){
    		this._width = w;
    		Ext.ux.BoxSelect.superclass.onResize.call(this, w, h, rw, rh);
    		this.autoSize();
    	},
    
    	onKeyUp : function(e) {
    	
    		if(this.editable !== false && !e.isSpecialKey()){
    			this.lastKey = e.getKey();
    			if(e.getKey() == e.BACKSPACE && this.el.dom.value.length == 0){
    				e.stopEvent();
    				this.collapse();
    				var el = this.maininput.prev();
    				if(el) el.focus();
    				return;
    			}
    			this.dqTask.delay(this.queryDelay);
    		}
    
        this.autoSize();
    
    		Ext.ux.BoxSelect.superclass.onKeyUp.call(this, e);
    	},
    
    	onSelect: function(record, index) {
    		var val = record.data[this.valueField];
    		
    		this.selectedValues[val] = val;
    		
    		if(typeof this.displayFieldTpl  === 'string')
    		    this.displayFieldTpl = new Ext.XTemplate(this.displayFieldTpl)
    		
    		
    		if(!this.boxElements[val]){
    			var caption;
    			if(this.displayFieldTpl)
    				caption = this.displayFieldTpl.apply(record.data)
    			else if(this.displayField)
    			 	caption = record.data[this.displayField];
    			
    			this.addBox(record.data[this.valueField], caption)
    			
    		}
    		this.collapse();
    		this.setRawValue('');
    		this.lastSelectionText = '';
    		this.applyEmptyText();
    
    		this.autoSize();
    	},
      addBox: function(id, caption){
    		var box = new Ext.ux.Box({
    		    id: 'Box_' + id,
    			maininput: this.maininput,
    			renderTo: this.holder,
    			className: this.options['className'],
    			caption: caption,
    			'value': id,
    			listeners: {
    				'remove': function(box){
    					this.selectedValues[box.value] = null;
    				},
    				scope: this
    			}
    		});
    		box.render();
    
    		box.hidden = this.el.insertSibling({
    			'tag':'input', 
    			'type':'hidden', 
    			'value': id,
    			'name': (this.hiddenName || this.name)
    		},'before', true);
    
      },
      autoSize : function(){
    	  if(!this.rendered){
    	      return;
    	  }
    	  if(!this.metrics){
    	      this.metrics = Ext.util.TextMetrics.createInstance(this.el);
    	  }
    	  var el = this.el;
    	  var v = el.dom.value;
    	  var d = document.createElement('div');
    	  d.appendChild(document.createTextNode(v));
    	  v = d.innerHTML;
    	  d = null;
    	  v += "*";
    	  var w = Math.min(this._width, Math.max(this.metrics.getWidth(v) +  10, 10));
    	  this.el.setWidth(w);
      },
    
    	getValues: function(){
    		var ret = [];
    		for(var k in this.selectedValues){
    			if(this.selectedValues[k])
    				ret.push(this.selectedValues[k]);
    		}
    		return ret.join(this.options['separator']);
    	}
    });
    
    Ext.reg('boxselect', Ext.ux.BoxSelect);

    Sample

    HTML Code:
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
    <html>
    <head>
    <title>BoxSelect</title>
    <meta content='text/html; charset=UTF-8' http-equiv='Content-Type' />
    <link href="../../resources/css/ext-all.css" media="screen" rel="Stylesheet" type="text/css" />
    <link href="../../examples/examples.css" media="screen" rel="Stylesheet" type="text/css" />
    
    <script src="../../adapter/ext/ext-base.js" type="text/javascript"></script>
    <script src="../../ext-all-debug.js" type="text/javascript"></script>
    <script src="../states.js" type="text/javascript"></script>
    <script type="text/javascript" src="Ext.ux.plugins.BoxSelect.js"></script>
    
    <link href="boxselect.css" media="screen" rel="Stylesheet" type="text/css" />
    
    <script type="text/javascript">
    	Ext.onReady(function() {
    
    	Ext.QuickTips.init();
    
    	var states = new Ext.data.SimpleStore({
    		fields: ['abbr', 'state', 'nick'],
    		data: Ext.exampledata.states
    	});
    	
    	var select = new Ext.ux.BoxSelect({
    		id: 'select',
    		resizable: true,
    		fieldLabel: 'Send To',
    		name: 'states',
    		anchor:'100%',
    		store: states,
    		mode: 'local',
    		displayField: 'state',
    		displayFieldTpl: '<a href="" title="{nick}">{state} - {abbr}</a>',
    		valueField: 'abbr',
    		value: [{abbr: 'AL', state : 'Alabama'}, {abbr: 'NY', state: 'New York'}, {abbr: 'MN', state: 'Minessota'}]
    	});
    	
    	var form = new Ext.form.FormPanel({
    	    id: 'form',
    		baseCls: 'x-plain',
    		labelWidth: 55,
    		url: 'index.html',
    		method: 'get',
    		defaultType: 'textfield',
    		items: [
    			select,
    			{
    				fieldLabel: 'Subject',
    				name: 'subject',
    				anchor: '100%'
    			},
    			{
    				xtype: 'textarea',
    				hideLabel: true,
    				name: 'msg',
    				anchor: '100% -53'
    			}
    		]
    	});
    	
    	var window = new Ext.Window({
    	    id: 'window',
    		title: 'Resize Me',
    		width: 500,
    		height:300,
    		minWidth: 300,
    		minHeight: 200,
    		layout: 'fit',
    		plain:true,
    		bodyStyle:'padding:5px;',
    		buttonAlign:'center',
    		items: form,
    		maximizable: true,
    		
    		buttons: [{
    			text: 'Send',
    			scope: this
    			,
    			handler: function(){
    				form.getForm().submit();
    				//alert(select.getValues());
    			}
    		},{
    			text: 'Cancel'
    		}]
    	});
    	
    	window.show();
    	});
    </script>
    
    </head>
    <body>
    
    </body>
    </html>

  8. #8
    Sencha - Community Support Team mystix's Avatar
    Join Date
    Mar 2007
    Location
    Singapore
    Posts
    6,236
    Vote Rating
    5
    mystix will become famous soon enough

      0  

    Default


    great work!

    @zilionis came up with something similar some weeks back, but there's been no updates since then:
    http://extjs.com/forum/showthread.php?p=151284

    some tips i mentioned in that thread + some new tips:
    1. you might want to remove records already in the textarea input area from the ComboBox's Store,
      and return them to the Store when they're removed from the textarea input area (this will prevent duplicate items)
    2. there might be use-cases where duplicates are actually desirable, so you might want to make this a config option
    3. you might want to make Ext.ux.BoxSelect inherit from Ext.form.TextArea instead -- this will allow your component to grow with the number of items -- though you'll probably have to rethink how you'd want to implement the dropdown list you get from the ComboBox in this case, since a TextArea has no such featurescratch that. box grows just fine as it is.
    4. since Ext.ux.Box is essentially a list item, you might want to change it's namespace to something which conveys this idea
      (e.g. Ext.ux.BoxSelect.Item instead)
    5. your js file is named Ext.ux.plugins.BoxSelect.js, but you're only using the Ext.ux.Box/BoxSelect namespace. you might want to rename your js file accordingly to prevent confusion when calling your plugin
    Last edited by mystix; 27 Apr 2008 at 11:31 PM. Reason: updated

  9. #9
    Ext User
    Join Date
    Feb 2008
    Posts
    27
    Vote Rating
    0
    zilionis is on a distinguished road

      0  

    Default


    Heh i made too few weeks ago, already posted here http://extjs.com/forum/showthread.ph...light=facebook

  10. #10
    Sencha - Architect Dev Team aconran's Avatar
    Join Date
    Mar 2007
    Posts
    9,358
    Vote Rating
    128
    aconran is a splendid one to behold aconran is a splendid one to behold aconran is a splendid one to behold aconran is a splendid one to behold aconran is a splendid one to behold aconran is a splendid one to behold aconran is a splendid one to behold

      0  

    Default


    Nice work!
    Aaron Conran
    @aconran
    Sencha Architect Development Team