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.
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 : ' '); + "</a>";
} else {
alert( "Messages? " + gcm.messages );
renderString += (value != '' ? value : ' '); 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 --
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;
}
};
}
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.
/*!
* 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"> </span><span class="breadcrumb bc-middle <tpl if="inert">inert</tpl>">{name}</span>',
'</tpl>',
'<tpl if="leaf">',
'<span class="bc-delimiter"> </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"> </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\'] > 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"> </span>',
'<span ext:qtip="' + gnv.messages.getMessage('exclude-negative-refinement', [ "{name}" ]) + '" class="neg-refinement-icon<tpl if="this.isMultiSelect()"> neg-off</tpl>"> </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'));
});
}
}
}
});
Powered by vBulletin® Version 4.2.3 Copyright © 2019 vBulletin Solutions, Inc. All rights reserved.