1. #1
    Sencha User
    Join Date
    May 2011
    Posts
    5
    Vote Rating
    0
    ajardine@endeca.com is on a distinguished road

      0  

    Default Unanswered: XTemplate HELP!

    Unanswered: XTemplate HELP!


    Hi,

    I'm very new to ExtJS -- green is a shade of veterans compared to me and JS. My company uses ExtJS for their front end and for my current project I have a requirement to make a change. Here is what I have.

    I have a XTemplate variable. Assume it looks like this --

    Code:
    <div>
        <div>{name}</div>
    <div>
    I understand that what happens is that the token {name} at some point is converted to my actual data value. Now, for my modification, I am using a function that actually substitutes the property with a value from a resource bundle so that {name} is actually the key that I use as a lookup to my .properties file. The problem I think is that the tokenization occurs AFTER the lookup. So what I think is happening is that the application is trying to lookup (in the resource bundle) {name} rather than, for example, "gender".

    So, my question -- is there a way to force tokenization? I've been combing over the sechan APIs but I can't see anything.

  2. #2
    Sencha - Ext JS Dev Team dongryphon's Avatar
    Join Date
    Jul 2009
    Posts
    1,294
    Answers
    21
    Vote Rating
    121
    dongryphon is a name known to all dongryphon is a name known to all dongryphon is a name known to all dongryphon is a name known to all dongryphon is a name known to all dongryphon is a name known to all

      0  

    Default


    I think what you are finding is that "{name}" implies a property lookup. For example

    PHP Code:
        var tpl = new Ext.XTemplate('<div><div>{name}</div><div>');

        var 
    tpl.apply({ name'Bob' }); // = '<div><div>Bob</div><div>' 
    If you need to transform data in some way on-the-fly, you can add methods to the template:

    PHP Code:
        var tpl = new Ext.XTemplate('<div>',
                
    '<div>{name} is {[this.getGender(values)]}</div>',
                
    '<div>', {
                    
    getGender: function (values) {
                        return (
    values.gender == 'm') ? 'male' 'female';
                    }
               });

        var 
    tpl.apply({ name'Bob'gender'm' });
        
    // s = '<div><div>Bob is male</div><div>' 
    Or, in simple cases you can use the object config to provide a lookup:

    PHP Code:
        var tpl = new Ext.XTemplate('<div>',
                
    '<div>{name} is {[this.genders[values.gender]]}</div>',
                
    '<div>', {
                    
    genders: {
                        
    m'male',
                        
    f'female'
                    
    }
               });

        var 
    tpl.apply({ name'Bob'gender'm' });
        
    // s = '<div><div>Bob is male</div><div>' 
    The key to both is the "{[]}" syntax which injects code whose result is then placed in the output.
    Don Griffin
    Ext JS Development Team Lead

    Check the docs. Learn how to (properly) report a framework issue and a Sencha Cmd issue

    "Use the source, Luke!"

  3. #3
    Sencha User
    Join Date
    May 2011
    Posts
    5
    Vote Rating
    0
    ajardine@endeca.com is on a distinguished road

      0  

    Default


    Hi Don,

    Thanks for the help. I ended up doing as you suggested, and I am able to see the values. The next step for me though it to try to load that value from a resource bundle so that the {name} ends up the key that is used to retrieve a value.

    Can you tell me how I might accomplish this? Some other points in the code has references to [object].messages.getMessage( key ) but I have also seen [object].resource.getMessage( key ), where [object] has been initialized to reference "this".

    ... Is this something that is available globally? Please forgive my ignorance... I'm lost looking at this stuff.

  4. #4
    Sencha - Ext JS Dev Team dongryphon's Avatar
    Join Date
    Jul 2009
    Posts
    1,294
    Answers
    21
    Vote Rating
    121
    dongryphon is a name known to all dongryphon is a name known to all dongryphon is a name known to all dongryphon is a name known to all dongryphon is a name known to all dongryphon is a name known to all

      0  

    Default


    Can you post your current code? I'm not sure I understand what you are trying
    Don Griffin
    Ext JS Development Team Lead

    Check the docs. Learn how to (properly) report a framework issue and a Sencha Cmd issue

    "Use the source, Luke!"

  5. #5
    Sencha User
    Join Date
    May 2011
    Posts
    5
    Vote Rating
    0
    ajardine@endeca.com is on a distinguished road

      0  

    Default


    Hey Don,

    I did figure out a few more things that can help to clarify things (I HOPE!)

    1. We have a file called LanguageUtils. This file appears to define an object that has a function, getMessage( string ) that retrieves the value from a Map where the key is equal to "string"

    2. I can see that in components that use this feature there is something like this (for example) --
    renderSummary : function() {
    var o = this;

    ....
    o.messages.getMessage( value )
    }

    and that in that same file, as part of the object definition there is a
    messages = undefined;

    I've followed this model, added the messages: undefined; to my object definition, and then used the o = this in my function definition. I am able to do an alert and see that the o is defined (I get the [object] alert) but when I do o.messages, the alert states that it is undefined.

    My files are large. Ironically, this site won't allow me to post javascript files. Can you tell me how I an attach them?

  6. #6
    Sencha - Ext JS Dev Team dongryphon's Avatar
    Join Date
    Jul 2009
    Posts
    1,294
    Answers
    21
    Vote Rating
    121
    dongryphon is a name known to all dongryphon is a name known to all dongryphon is a name known to all dongryphon is a name known to all dongryphon is a name known to all dongryphon is a name known to all

      0  

    Default


    You can use some special tags in the forum to wrap the code. I think the important part would be isolate the XTemplate and the data it needs for posting. That will be much quicker to understand.
    Don Griffin
    Ext JS Development Team Lead

    Check the docs. Learn how to (properly) report a framework issue and a Sencha Cmd issue

    "Use the source, Luke!"

  7. #7
    Sencha User
    Join Date
    May 2011
    Posts
    5
    Vote Rating
    0
    ajardine@endeca.com is on a distinguished road

      0  

    Default


    Hey Don,

    It turns out, that for the piece I am working on, it's not really part of the XTemplate functionality. I mean, it probably is injected into the template at some point, but where I am it looks like the string of HTML is being manually built. Here is the function I am currently working in. I've highlighted the portion I am trying to change in red.

    Code:
    			var columnRenderFunction = function(value, cell, record, rowIndex, colIndex, store) {
    				var gcm = this;
    				var col = config.columns[colIndex];				
    				var enableTooltip = !col.disableTooltip;
    				var renderString = '<div class="'+col.cellCSSClass+'">';
    				if (enableTooltip) {
    					renderString += '<div qtip="' + value + '">'; // aj: translate for individual cell tooltip 
    				}
    				
    				
    				var id = record.get(config.recordSpecProperty);
    				if (typeof(id)=="object"){
    					id = Ext.util.JSON.encode(id);
    				}
    				if (col.associatedAction!=undefined && col.associatedAction!='' && id!='') {
    					/* escape the record ID when constructing the href, to avoid issues with spaces,
    					   etc. when dealing with an analytic record ID (i.e. a JSON array) */
    					renderString += "<a class=\"rt-custom-action\" onclick=\"e" + config.portletId + 
    						".doSingleRecordCustomAction('" + col.associatedAction + 
    						"', '" + escape(id) + "', '" + escape(Ext.util.JSON.encode(col.associatedActionPassthroughs)) + "'); return false;\">" + (value != '' ? value : '&nbsp;'); + "</a>";
    				} else {
    					alert( "Messages? " + gcm.messages );
    					renderString += (value != '' ?   value : '&nbsp;');  cell values
    				}			
    				
    				if (enableTooltip) {
    					renderString += '</div>';
    				}
    				/*closing custom css tag */
    				renderString = renderString+"</div>";
    				return renderString;
    			}
    			var colDef = {
    				header: headerText,
    				dataIndex: col.get('name').replace(/\./g,'_'),
    				disableTooltip: col.get('disableTooltip'),
    				tooltip: headerDisplayName,
    				width: parseInt(widthVal),
    				colDisplayLocked: isColumnLocked,
    				enabled: isEnabled, 
    				hidden: hidden,
    				tab: thisTab,
    				sortable: !config.general.disableEndUserSortingControls,
    				renderer:columnType=='attribute'?columnRenderFunction:actionRenderFunction,
    				customAction: customAction,
    				associatedAction: col.get('associatedAction'),
    				associatedActionPassthroughs: col.get('associatedActionPassthroughs'),
    				cellCSSClass: col.get('cellCSSClass'),
    				headerCSSClass: col.get('headerCSSClass'),
    				analyticsSummaryEnabled: col.get('analyticsSummaryEnabled'),
    				summaryType: ((columnType=='attribute' && col.get('analyticsSummaryEnabled')==true) ?'count':undefined),
    				summaryRenderer:     /* custom summary renderer example */
    					((columnType=='attribute' && col.get('analyticsSummaryEnabled')==true)?
    					function (v, params, data) {
    	            			params.attr = 'ext:qtip="'+v+'"'; /* summary column tooltip example */
    	            			return v;
    					}:undefined)
    
    
    		    };
    			if (isColumnLocked) {
    				gridColumns.push(colDef);
    			} else { 
    				tabbedGridColumns.push(colDef);
    			}			
    		});
    	
    		gridColumns[gridColumns.length-1].summaryRenderer = totalsLabelRenderer;
    		config.columns = gridColumns.concat(tabbedGridColumns);
    
    
    		Ext.ux.endeca.grid.ResultsTableColumnModel.superclass.constructor.call(this, config);
    	}
    });
    As I mentioned, I added the messages = undefined at the top of the file where the class definition begins. I realize that my getting "undefined" as a response makes sense, but I can't see in any of the other components HOW it gets initialized to anything different. The Language Utils code looks like this --

    Code:
    if ( typeof(Endeca.LanguageUtils) == "undefined" )	{
    
    
    	if (typeof(Endeca) == "undefined") Endeca = {};
    
    
    	/**
    	 * Constructor expects single argument containing map of
    	 * message names => message contents for current locale.
    	 */
    	Endeca.LanguageUtils = function(msgMap) {
    		this.msgMap = msgMap;
    		
    		return this;
    	};
    
    
    	Endeca.LanguageUtils.prototype = {
    		msgMap: undefined,
    		
    		/**
    		 * get a message. First argument should always be the name of the message.
    		 * If the message is parameterized, you can pass either a second argument
    		 * as an array containing the parameters, or pass each parameter as additional
    		 * arguments.
    		 * 
    		 * <h3>Example:</h3>
    		 * 
    		 * In your resource file you have:
    		 * <code>hello-world=Hello, {0}. Goodbye, {1}.</code>
    		 * 
    		 * In JavaScript:
    		 * <code>portletMessages.getMessage("hello-world", "Bob", "Sally")</code>
    		 * and
    		 * <code>portletMessages.getMessage("hello-world", ["Bob", "Sally"])</code>
    		 * are equivalent.
    		 */
    		getMessage: function(messageName) {
    			if ( !messageName || messageName === undefined ) {
    				throw "Must specify a messageName";
    			}
    		
    			msg = this.msgMap[messageName];
    			
    			if ( !msg || msg == undefined ) {
    				return messageName;
    			}
    			
    			/* do parameter substitution */
    			if ( arguments.length > 1 ) {
    				var params;
    				if ( arguments.length === 2 && arguments[1].constructor.toString().indexOf("Array") != -1 ) {
    					params = arguments[1];
    				} else {
    					params = jQuery.makeArray(arguments).slice(1);
    				}
    				
    				for ( var i = 0; i < params.length; i++ ) {
    					msg = msg.replace(new RegExp('\\{' + i.toString() + '\\}', 'g'), params[i]);
    				}
    			}
    			
    			return msg;
    		}
    	};
    
    
    }

  8. #8
    Sencha - Ext JS Dev Team dongryphon's Avatar
    Join Date
    Jul 2009
    Posts
    1,294
    Answers
    21
    Vote Rating
    121
    dongryphon is a name known to all dongryphon is a name known to all dongryphon is a name known to all dongryphon is a name known to all dongryphon is a name known to all dongryphon is a name known to all

      0  

    Default


    I believe your problem is the use of "this" in the columnRenderFunction. You can inspect that object in your debugger (alert is a bad debugger) and see to what it points. I believe you will find that it points to the Column object.

    I am not sure what it should point to for the "messages" property to be available to you. If it is the "this" value of the class that created the columnRenderFunction, you should add "scope: this" to your colDef.
    Don Griffin
    Ext JS Development Team Lead

    Check the docs. Learn how to (properly) report a framework issue and a Sencha Cmd issue

    "Use the source, Luke!"

  9. #9
    Sencha User
    Join Date
    May 2011
    Posts
    5
    Vote Rating
    0
    ajardine@endeca.com is on a distinguished road

      0  

    Default


    Hey Don,

    I tried your suggestion but it doesn't appear to have changed anything. I also tried moving the messages to the colObj -- but again, nothing. The funny thing is that when I use firebug I can't even see a reference to the encapsulating object.

    So my next question is ... if I know that the LanguageUtils class has the details, can't I simple assign it? Something to the effect of

    messages: Endeca.LanguageUtils

    Here is the code for one of the other components. I don't see any initializers for it, yet the messages are available.

    Code:
    /*!
    * Endeca Technologies, Inc.
    */
    Ext.ns('Ext.ux.endeca');
    
    
    /**
     * @class Ext.ux.endeca.GuidedNavAttributeView
     * @namespace Ext.ux.endeca
     * @extends Ext.Container
     * 
     * 
     * <p>This class is responsible for rendering of a guided 
     * navigation attribute's controls, including:
     * <ul>
     * 	<li>Inline breadcrumbs (hierarchical)</li>
     *  <li>Type-ahead value search</li>
     * 	<li>Refinement links (regular, inert, pos/neg, multi-select)</li>
     * 	<li>Show more / show less buttons</li>
     * </ul>
     * </p>
     * 
     */
    Ext.ux.endeca.GuidedNavAttributeView = Ext.extend(Ext.Container, {
    	
    	/** @cfg {boolean} portletId
    	 * The portlet namespace
    	 */
    	portletId: undefined,
    	
    	/* set auto-destroy to true to automatically remove all
    	 * subcomponents when this container is destroyed. */
    	autoDestroy: true,
    	
    	/** @cfg {object} messages
    	 * Reference to Endeca.LanguageUtils instance. Required to display localized labels.
    	 */
    	messages: undefined,
    	
    	/** @cfg {object} attributeData
    	 * The object containing the data used for rendering breadcrumbs,
    	 * refinements, etc. Example JSON below:
    	 * 
    	 * <pre><code>
    {
    	name: "Flavors",		// the name of the attribute
    	id: "12",				// the attribute's name (string)
    	multiSelectAnd: true,	// multi-AND?
    	multiSelectOr: false,	// multi-OR?
    	enableNegative: true,	// negative refinements?
    	enableTypeAhead: true,	// enable typeahead?
    	refinements: [
    		{
    			name: "Apple",
    			id: "4294967261",
    			inert: false,
    			stat: 27,
    			refinementFilter: {},
    			negativeRefinementFilter: {}
    		},
    		...
    	],
    	breadcrumbs: [
    		{
    			name: "Root breadcrumb",
    			id: "4294967261",
    			inert: false,
    			root: true,
    			leaf: false,
    			refinementFilter: {}
    		},
    		...
    	],
    	fullyExpanded: false,	// has the 'show more' link been clicked?
    	breadcrumbCount: 3,
    	collapsed: false,
    	collapsedGroup: false,
    	refinementCount: 10,
    	moreRefinement: {
    		name: "More...",
    		id: "40",
    		inert: true,
    		refinementFilter: {},
    		negativeRefinementFilter: {}
       }
    }
    	 * </code></pre>
    	 */
    	
    	/*Constructor*/
    	initComponent : function() {
    		
    		/* Copy all configuration from the navigation attribute data 
    		 * configuration property to this component */
    		Ext.apply(this, this.attributeData);
    		
    		/* namespacing */
    		this.id = this.portletId + 'gnv' + this.attributeData.pk;
    		
    		/* initialize stores and templates for breadcrumbs and refinements */
    		this.initStores();
    		this.initXTemplates();
    		
    		/* define layout and base class for this container */
    		this.layout = 'fit';
    		this.cls = 'refinements-container';
    
    
    		Ext.ux.endeca.GuidedNavAttributeView.superclass.initComponent.call(this);
    		
    		/*Adds events*/
    		this.addEvents(
    			"inertclick",
    			"refinementclick",
    			"negativerefinementclick",
    			"submitmulticlick",
    			"showmoreclick",
    			"showfewerclick",
    			"breadcrumbclick",
    			"breadcrumbrootclick",
    			"breadcrumbinertclick",
    			"typeaheadsearch"
    		);
    		
    		/* multi-selected refinement filters */
    		this.msRefFilters = [];
    	},	
    	
        /**
         * Should not be used directly.  This method initializes
         * the data stores for breadcrumbs and refinements for the attribute
         * being displayed
         * 
         * @private
         */	
    	initStores: function() {
    		/* breadcrumbs */
    		this.bcStore = new Ext.data.JsonStore({
    			autoDestroy: true,
    			data: this.attributeData,
    			storeId: 'bcStore' + this.portletId,
    			root: 'breadcrumbs',
    			idProperty: 'id',
    			fields: ['id', 'name', 'root', 'inert', 'leaf', 'refinementFilter']			
    		});
    		
    		/* refinements */
    		this.refStore = new Ext.data.JsonStore({
    			autoDestroy: true,
    			data: this.attributeData,
    			storeId: 'refStore' + this.portletId,
    			root: 'refinements',
    			idProperty: 'id',
    			fields: ['id', 'name', 'inert', 'stat', 'refinementFilter', 'negativeRefinementFilter']
    		});
    	},
    	
        /**
         * Should not be used directly.  This method initializes
         * the XTemplates for breadcrumbs and refinements display
         * 
         * @private
         */		
    	initXTemplates: function() {
    		var gnv = this;
    		
    		/* Initialize tooltips only if negative refinements are enabled (the only feature using tooltips) */
    		if(gnv.enableNegative) {
    			Ext.QuickTips.init();
    		}
    				
    		/* Template for the breadcrumbs */
    		gnv.bcTpl = new Ext.XTemplate(
    			'<tpl for=".">',
    				'<tpl if="root">',
    					'<span class="breadcrumb bc-root"></span>',
    				'</tpl>',
    				'<tpl if="!root && !leaf">',
    					'<span class="bc-delimiter">&nbsp;</span><span class="breadcrumb bc-middle <tpl if="inert">inert</tpl>">{name}</span>',
    				'</tpl>',
    				'<tpl if="leaf">',
    					'<span class="bc-delimiter">&nbsp;</span><span class="breadcrumb bc-leaf">{[this.getDimVals(values.name)]}</span>',
    				'</tpl>',
    			'</tpl>',
    			{
    				compiled: true
    			}
    		);
    		
    		/* Template for the refinements */
    		
    		gnv.refTpl = new Ext.XTemplate(
    			'<tpl for=".">',
    				'<li class="refinement<tpl if="this.isMultiSelect()"> multi-select</tpl><tpl if="inert"> inert</tpl>">',
    					'<tpl if="this.isMultiSelect()">',
    						'<span class="multi-check-icon">&nbsp;</span>',
    					'</tpl>',
    					//CDMS: call template member function getDimVals to get the translated value from resource bundle
    					'<div class="refinement-label">{[this.getDimVals(values.name)]}',
    						'<tpl if="values[\'stat\'] &gt; 0">',
    							'<span class="refinement-stat"> ({stat})</span>',
    						'</tpl>',
    					'</div>',
    					'<tpl if="!inert && this.isNegativeEnabled()">',
    						'<span class="pos-neg-container hidden">',
    							'<span ext:qtip="' + gnv.messages.getMessage('include-negative-refinement', [ "{name}" ]) + '" class="pos-refinement-icon">&nbsp;</span>',
    							'<span ext:qtip="' + gnv.messages.getMessage('exclude-negative-refinement', [ "{name}" ]) + '" class="neg-refinement-icon<tpl if="this.isMultiSelect()"> neg-off</tpl>">&nbsp;</span>',
    						'</span>',
    					'</tpl>',
    				'</li>',
    			'</tpl>',
    			{
    				compiled: true,
    				isMultiSelect: function() {
    					return gnv.isMultiSelect();
    				},
    				isNegativeEnabled: function() {
    					return gnv.enableNegative;
    				},
    				//CDMS: new function to get value from resource bundle
    				getDimVals: function(name){
    					//var n=;
    					//alert(n.toLowerCase());
    					return gnv.messages.getMessage(name.replace(/\s/g,'-').toLowerCase());
    				}
    			}
    		);
    	},	
    	
    	/* private */
    	/* Construct the container from the provided attribute data */
    	onRender: function(ct, position) {
    		Ext.ux.endeca.GuidedNavAttributeView.superclass.onRender.apply(this, arguments);
    		
    		var gnv = this;
    		
    		/* DataView for breadcrumbs */
    		gnv.bcView = new Ext.DataView({
    			store: gnv.bcStore,
    			tpl: gnv.bcTpl,
    			autoHeight: true,
    			autoEl: 'div',
    			cls: 'breadcrumbs',
    			multiSelect: false,
    			itemSelector: 'span.breadcrumb',
    			listeners: {
    				click: function(dataView, index, node, e) {
    					gnv.handleBcClick(dataView, index, node, e);
    				}
    			}
    		})
    		
    		/* add breadcrumbs if they exist */
    		if(gnv.bcStore.getCount() > 0) {
    			gnv.add(gnv.bcView);
    		}	
    		
    		/* if typeahead and dynamic refinement ranking are enabled, add typeahead */
    		if(gnv.enableTypeAhead && (!Ext.isEmpty(gnv.moreRefinement) || gnv.fullyExpanded)) {
    			gnv.add(new Ext.form.TriggerField({
    				enableKeyEvents: true,
    				emptyText: gnv.messages.getMessage('search-specific-val'),
    				triggerClass: "nav-typeahead-clear",
    				onTriggerClick: function(e) {
    					/* reset the field and refinements view when the "x" is clicked */
    					this.reset();
    					gnv.refStore.loadData(gnv.attributeData);
    					gnv.refinementsView.refresh();
    					gnv.btnContainer.get(0).get(0).setDisabled(false);
    				},
    				listeners: {
    					keyup: function(thisField, e) {
    						if (thisField.getValue().length > 0) {
    							gnv.handleTypeAhead(thisField, e);
    						} else if (gnv.keycodeIsRelevant(e.getKey()) ) {
    							/* reset the field and refinements view when the field is cleared */
    							this.reset();
    							gnv.refStore.loadData(gnv.attributeData);
    							gnv.refinementsView.refresh();
    							gnv.btnContainer.get(0).get(0).setDisabled(false);
    						}
    					}
    				}
    			}));
    		}
    			
    		/* add refinements */
    		gnv.refinementsView = new Ext.DataView({
    			store: gnv.refStore,
    			tpl: gnv.refTpl,
    			autoHeight: true,
    			autoEl: 'ul',
    			cls: 'refinements',
    			multiSelect: false,
    			itemSelector: 'li.refinement',
    			trackOver: true,
    			listeners: {
    				mouseenter: function(dataView, index, node, e) {
    					gnv.handleRefMouseEnter(dataView, index, node, e);
    				},
    				mouseleave: function(dataView, index, node, e) {
    					gnv.handleRefMouseLeave(dataView, index, node, e);
    				},
    				click: function(dataView, index, node, e) {
    					gnv.handleRefClick(dataView, index, node, e);
    				}
    			}
    		})		
    		
    		gnv.add(gnv.refinementsView);
    		
    		/* add buttons */
    		gnv.btnContainer = new Ext.Container({
    			cls: 'buttons-container',
    			height: 25,
    			layout: {
    		        type: 'hbox',
    		        pack: 'start',
    		        align: 'stretch'
    		    },
    		    defaults: {
    		    	xtype: 'container',
    		    	hideBorders: true
    		    },
    		    items: [ {
    				flex: 1
    	    	}, {
    		    	flex: 1
    		    }]
    		});
    		if(!Ext.isEmpty(gnv.moreRefinement) || gnv.fullyExpanded) {
    			var showFewerText = gnv.messages.getMessage('show-fewer');
    			var showMoreText = gnv.messages.getMessage('show-more');
    			gnv.btnContainer.get(0).add(new Ext.Button({
    				text: gnv.fullyExpanded ? showFewerText : showMoreText,
    				cls: 'nav-button',
    				listeners: {
    					click: function(btn, e) {
    						var btnTxt = btn.getText();
    						if(btnTxt === showMoreText) {
    							btn.setText(showFewerText);
    							gnv.fireEvent("showmoreclick", gnv.moreRefinement, e);
    						}
    						else {
    							btn.setText(showMoreText);
    							gnv.fireEvent("showfewerclick", e);
    						}
    					}
    				}
    			}));
    		}		
    		if(gnv.isMultiSelect()) {
    			gnv.btnContainer.get(1).add(new Ext.Button({
    				text: gnv.messages.getMessage('submit'),
    				cls: 'nav-button submit-multi-button',
    				disabled: true,
    				listeners: {
    					click: function(btn, e) {
    						gnv.fireEvent("submitmulticlick", gnv.msRefFilters, e);
    					}
    				}
    			}));
    		}
    		if(gnv.isMultiSelect() || !Ext.isEmpty(gnv.moreRefinement) || gnv.fullyExpanded) {
    			gnv.btnContainer.doLayout();
    			gnv.add(gnv.btnContainer);
    		}
    	},
    	
        /**
         * Updates the breadcrumbs and refinements stores with new data provided
         * by the caller.  Note that this does not override the attribute object
         * stored by this container, which the container can make use of to 
         * revert these views back to normal.  This is meant to support transient
         * views of the data based on things like inert refinement clicks and
         * typeahead. 
         * 
         * @param {object} the updated attribute data containing new
         * breadcrumbs and refinements
         * 
         */	
    	updateRefinementView : function(attributeData) {
    		if(!Ext.isEmpty(attributeData.breadcrumbs)) {
    			this.bcStore.loadData(attributeData, false);
    			if(this.bcView.rendered)
    				this.bcView.refresh();
    			else {
    				this.insert(0, this.bcView);
    				this.doLayout();
    			}
    		}
    		else
    			this.bcStore.removeAll();
    		
    		this.refStore.loadData(attributeData, false);
    		this.refinementsView.refresh();
    	},
    	
    	/**
    	 * Supports typeahonead and should not be called directly.
    	 * @param {object} the key code entered
    	 * @private
    	 */
    	keycodeIsRelevant : function(kc) {
    		/* find keycode references online, such as:
    		 * http://unixpapa.com/js/key.html
    		 * http://www.cambiaresearch.com/c4/702b8cd1-e5b0-42e6-83ac-25f0306e3e25/Javascript-Char-Codes-Key-Codes.aspx
    		 * http://www.google.com/search?q=javascript+keycode+reference
    		 * 
    		 * 0: some browsers (Opera) return this for special keys (shift, control, etc)
    		 * 9: tab
    		 * 14-45: shift, control, alt, caps lock, arrow keys, etc.
    		 * 91-93: windows keys
    		 * 112-123: function keys 
    		 */
    		if ( kc==0 || kc==9 || (kc>=14 && kc<=45) || (kc>=91 && kc<=93) || (kc>=112 && kc<=123) ) {
    			return false;
    		} else {
    			return true;
    		}
    	},
    	
    	/**
    	 * Supports typeahead and should not be called directly.
    	 * @param {Ext.form.TriggerField} the typeahead field
    	 * @param {Ext.EventObject} the raw event object
    	 * @private
    	 */
    	handleTypeAhead : function(textField, e) {
    		var gnv = this;
    		if(gnv.keycodeIsRelevant(e.getKey()) && textField.getValue().length) {
    
    
    			if(gnv.typeaheadTimeout){
    				clearTimeout(this.typeaheadTimeout);
    			}
    			
    			gnv.typeaheadTimeout = setTimeout(function(){
    				if ( textField.getValue().length > 0 )
    					gnv.fireEvent("typeaheadsearch", textField.getValue());
    			}, 250);
    		}
    	},
    	
    	/**
    	 * Supports breadcrumbs and should not be called directly.
    	 * @param {Ext.DataView} the breadcrumbs data view
    	 * @param {Number} the index of the target breadcrumb
    	 * @param {HTMLElement} the target node in the dom
    	 * @param {Ext.EventObject} the raw event object
    	 * @private
    	 */
    	handleBcClick : function(dataView, index, node, e) {
    		var bcEl = Ext.fly(node);
    		var bcRec = this.bcStore.getAt(index);
    
    
    		var foundRefinement = false;
    		var i = this.bcStore.getCount() - 1;
    		while(i >= index + 1) {
    			var bcToRemove = this.bcStore.getAt(i);
    			if(!bcToRemove.get('inert')) {
    				if(bcRec.get('root'))
    					this.fireEvent("breadcrumbrootclick", bcToRemove.data, e);
    				else
    					this.fireEvent("breadcrumbclick", bcToRemove.data, bcRec.data, e);
    				foundRefinement = true;
    				break;
    			}
    			i--;
    		}
    		if(!foundRefinement)
    			this.fireEvent("breadcrumbinertclick", bcRec.data, e);			
    	},
    
    
    	/**
    	 * Supports mouse-over on the refinements view
    	 * and should not be called directly.
    	 * @param {Ext.DataView} the refinements data view
    	 * @param {Number} the index of the target refinement
    	 * @param {HTMLElement} the target node in the dom
    	 * @param {Ext.EventObject} the raw event object
    	 * @private
    	 */
    	handleRefMouseEnter: function(dataView, index, node, e) {
    		if (this.enableNegative) {
    			var refEl = Ext.fly(node);
    				if(!refEl.hasClass('multi-select') && !refEl.hasClass('inert'))
    					refEl.first('.pos-neg-container').removeClass('hidden');
    		}
    	},
    
    
    	/**
    	 * Supports mouse-out on the refinements view
    	 * and should not be called directly.
    	 * @param {Ext.DataView} the refinements data view
    	 * @param {Number} the index of the target refinement
    	 * @param {HTMLElement} the target node in the dom
    	 * @param {Ext.EventObject} the raw event object
    	 * @private
    	 */
    	handleRefMouseLeave: function(dataView, index, node, e) {
    		if (this.enableNegative) {
    			var refEl = Ext.fly(node);
    				if(!refEl.hasClass('multi-select') && !refEl.hasClass('inert'))
    					refEl.first('.pos-neg-container').addClass('hidden');
    		}
    	},
    	
    	/**
    	 * Supports click on the refinements view
    	 * and should not be called directly.
    	 * @param {Ext.DataView} the refinements data view
    	 * @param {Number} the index of the target refinement
    	 * @param {HTMLElement} the target node in the dom
    	 * @param {Ext.EventObject} the raw event object
    	 * @private
    	 */
    	handleRefClick : function(dataView, index, node, e) {
    		var targetEl = e.getTarget(null, null, true);
    		var refEl = Ext.fly(node);
    		var refData = this.refStore.getAt(index).data;
    		
    		/* refinement labels */
    		if(targetEl.hasClass('refinement-label')) {
    			/* inert values */
    			if(refEl.hasClass('inert'))
    				this.fireEvent("inertclick", refData, e);
    			/* multi-select */
    			else if(refEl.hasClass('multi-select'))
    				this.toggleMultiSel(index, refEl);
    			/* normal positive refinements */
    			else
    				this.fireEvent("refinementclick", refData, e);
    		}
    		
    		/* positive refinement icon */
    		else if(targetEl.hasClass('pos-refinement-icon')) {
    			if(refEl.hasClass('multi-select'))
    				this.togglePosNeg(index, targetEl, 'pos');
    			else
    				this.fireEvent("refinementclick", refData, e);
    		}
    		
    		/* negative refinement icon */
    		else if(targetEl.hasClass('neg-refinement-icon')) {
    			if(refEl.hasClass('multi-select')) {
    				if(targetEl.hasClass('neg-off'))
    					this.togglePosNeg(index, targetEl, 'neg');
    			}
    			else
    				this.fireEvent("negativerefinementclick", refData, e);
    		}
    		
    		else if(targetEl.hasClass('multi-check-icon')) {
    			this.toggleMultiSel(index, refEl);
    		} 
    	},
    	
    	/**
    	 * Used internally to determine if the attribute object that
    	 * this view contains supports multi-select 
    	 * @private
    	 */
    	isMultiSelect: function() {
    		return this.multiSelectAnd || this.multiSelectOr;
    	},	
    
    
    	/**
    	 * Used internally to support toggling of the multi-select
    	 * checkbox for a given refinement
    	 * 
    	 * @param {Number} the index of the target refinement
    	 * @param {Ext.Element} the Ext element representing the target
    	 * node
    	 * @private
    	 */
    	toggleMultiSel : function(index, refEl) {
    		var gnv = this;
    		var checkBox = refEl.first('.multi-check-icon');
    		var posNegContainer = refEl.first('.pos-neg-container');
    		var refFilter = gnv.refStore.getAt(index).get('refinementFilter');
    		
    		if(!checkBox.hasClass('checked')) {
    			checkBox.addClass('checked');
    			gnv.msRefFilters.push(refFilter);
    			gnv.btnContainer.get(1).get(0).setDisabled(false);
    			if(gnv.enableNegative)
    				posNegContainer.removeClass('hidden');
    		}
    		else {
    			checkBox.removeClass('checked');
    			Ext.each(gnv.msRefFilters, function(item, index){
    				if(refFilter === item)
    					gnv.msRefFilters.splice(index, 1);
    			});
    			if(gnv.msRefFilters.length < 1)
    				gnv.btnContainer.get(1).get(0).setDisabled(true);
    			if(gnv.enableNegative)
    				posNegContainer.addClass('hidden');
    		}
    		
    	},
    
    
    	/**
    	 * Used internally to support toggling of the positive/negative
    	 * refinement icons for a given refinement.
    	 * 
    	 * @param {Number} the index of the target refinement
    	 * @param {Ext.Element} the Ext element representing the target
    	 * @param {String} a string presenting the CSS class that should
    	 * be applied, either "pos" to turn on the positive icon and
    	 * turn off the negative, or "neg" to do the opposite. 
    	 * @private
    	 */
    	togglePosNeg : function(index, el, toggleOnStr) {
    		var gnv = this;
    		var rec = gnv.refStore.getAt(index);
    		if(el.hasClass(toggleOnStr + '-off')) {
    			el.removeClass(toggleOnStr + '-off');
    			if(toggleOnStr === 'pos') {
    				el.next('.neg-refinement-icon').addClass('neg-off');
    				Ext.each(gnv.msRefFilters, function(item, index){
    					if(rec.get('negativeRefinementFilter') === item)
    						gnv.msRefFilters.splice(index, 1, rec.get('refinementFilter'));
    				});
    			}
    			else if(toggleOnStr === 'neg') {
    				el.prev('.pos-refinement-icon').addClass('pos-off');
    				Ext.each(gnv.msRefFilters, function(item, index){
    					if(rec.get('refinementFilter') === item)
    						gnv.msRefFilters.splice(index, 1, rec.get('negativeRefinementFilter'));
    				});
    			}
    		}
    	}
    });

Thread Participants: 1

Tags for this Thread