d3 selector for immediate children - d3.js

I can obviously do this:
d3.selectAll('div#some-div>ul')
But what if I'm using a DOM node or existing D3 selection:
d3.select(this).selectAll('ul')
will get me all descendent ULs. So, if
var div = d3.select('div')
got me this node:
<div>
<ul>
<li>foo
<ul><li>bar</li></ul>
</li>
<ul>
</div>
Then
var uls = div.selectAll('ul')
will get me two ULs. I guess I could distinguish a top level one like:
uls.filter(function() { return this.parentNode === div.node() }
So, I've answered my own question. Maybe it will be useful to someone. Or maybe someone can recommend a less ugly solution.
Even better, Alain Dumesny, whose answer below is belatedly selected as correct, posted this as an issue to D3 and got the problem fixed, kludge-free, at the source! (I would copy it in here for convenience, but then people might not scroll down and cast greatly deserved upvotes for his heroic feat.)

I wouldn't have expected this to work, but it looks like D3 will sub-select any element that is a child of the selection and matches the selector - so this works:
d3.select(this).selectAll('div > ul');
See http://jsfiddle.net/g3aay/2/

If anyone is still interested, d3.select(this.childNodes) was helping me to solve my problem for picking all immediate children. Alternatively, you can use
selection.select(function(){
return this.childNodes;
})

d3 selection v2.0 should now have this built in with new selection.selectChildren() / selection.selectChild() methods - see https://github.com/d3/d3-selection/issues/243

#nrabinowitz's solution doesn't work all the time.
In my case, I was trying to do d3.select(this).selectAll(".childNode > *").
So I was trying to get all the immediate children of .childNode. The problem is that this was a nested stack, so .childNode could also appear in the children, which was causing problems.
The best way I found is:
var child = d3.select(this).select(".childNode");
var sel = d3.select(this).selectAll(".childNode > *").filter(function() {
return this.parentNode == child.node();
});

The selectAll method relies on the querySelectorAll native method (in v4 at least).
It means you can use the :scope pseudo selector :
var uls = div.selectAll(':scope > ul')
the :scope pseudo selector is currently a draft specification and is not supported in all browsers yet. More information on :scope pseudo selector available on MDN

Based on the solution by Sigfrid, here is something I added to the prototype, in a project I work on.
/**
* Helper that allows to select direct children.
* See https://stackoverflow.com/questions/20569670/d3-selector-for-immediate-children
*
* #param {string} selector
* #returns {Selection}
*/
d3.selectAll('__nonexisting__').__proto__.MYPREFIX_selectChildren = function (selector) {
var expectedParent = this.node();
return this.selectAll(selector).filter(
function() {
return this.parentNode === expectedParent;
}
);
};
The way that I grab the prototype object looks a bit clumsy. Perhaps there is a better way.
The "MYPREFIX_" is meant to prevent name clashes.
The jsdoc #returns {Selection} is ambiguous, unfortunately this type is declared within a closure and has no global name referenceable by jsdoc (afaik).
Once this file is included, you can then do this:
d3.select('#some_id').MYPREFIX_selectChildren('ul')

Looks like d3 used to have some functions built to address this exact problem- but for one reason or another they were removed.
By pasting this code into your program, you can add them back in again:
function childMatcher(selector) {
return function(node) {
return node.matches(selector);
};
}
function children() {
return this.children;
}
function childrenFilter(match) {
return function() {
return Array.prototype.filter.call(this.children, match);
};
}
/**
* Runs the css selector only on the immediate children.
* See: https://stackoverflow.com/questions/20569670/d3-selector-for-immediate-children
* Use: https://github.com/d3/d3-selection/commit/04e9e758c80161ed6b7b951081a5d5785229a8e6
*
* Example Input: selectChildren("form")
*/
d3.selection.prototype.selectChildren = function(match) {
return this.selectAll(match == null ? children
: childrenFilter(typeof match === "function" ? match : childMatcher(match)));
}
function childFind(match) {
return function() {
return Array.prototype.find.call(this.children, match);
};
}
function childFirst() {
return this.firstElementChild;
}
/**
* Runs the css selector only on the immediate children and returns only the first match.
* See: https://stackoverflow.com/questions/20569670/d3-selector-for-immediate-children
* Use: https://github.com/d3/d3-selection/commit/04e9e758c80161ed6b7b951081a5d5785229a8e6
*
* Example Input: selectChild("form")
*/
d3.selection.prototype.selectChild = function(match) {
return this.select(match == null ? childFirst
: childFind(typeof match === "function" ? match : childMatcher(match)));
}
If you are using typescript, then here is the function declaration you can include in the same file:
declare module "d3" {
interface Selection<GElement extends d3.BaseType, Datum, PElement extends d3.BaseType, PDatum> {
selectChild(match: string | null | Function): Selection<GElement, Datum, PElement, PDatum>;
selectChildren(match: string | null | Function): Selection<GElement, Datum, PElement, PDatum>;
}
}
Here's a fiddle that implements this: https://jsfiddle.net/Kade333/drw3k49j/12/

For whatever it's worth after four years, ​d3.selectAll('#id > *') can be used, e.g. in ​d3.selectAll('#id > *').remove() to remove all children of an element with id=id

Related

Cypress should not.exist or not.be.visible

Because of - imo - poor page design, I've found myself having problems verify the visibility or non-existance of one or more elements on a page.
The problem is that some of the elements does not exist, while some of them have CSS property display:none. But the existing test code checks for not.exist, which makes the test fail. But I cannot change to not.be.visible, since then it will fail on the other elements.
So: Is it possible to do an OR in an assertion? Somthing like
cy.get('blabla').should('not.be.visible').or.cy.get('blabla').should('not.exist');
The above line compiles, but yields an undefined on the second part, so it doesn't work.
Here's the code:
(I don't consider the code architecture important - the question is basically the OR thing.)
page.sjekkAtDellaanFelterVises(2, 2, [
DellaanFelter.formaal,
DellaanFelter.opprinneligLaanebelop,
DellaanFelter.utbetalingsdato,
DellaanFelter.restlaanInnfridd,
]);
public sjekkAtDellaanFelterVisesALT(sakRad: number, delLanRad: number, felter: DellaanFelter[]) {
this.sjekkFelter(felter, DellaanFelter, (felt: string) => this.delLanAccordionBody(sakRad, delLanRad).get(this.e2e(felt)));
}
#ts-ignore
public sjekkFelterALT<T, E extends Node = HTMLElement>(felter: T[], enumType, lookupFn: (felt: string) => Chainable<JQuery<E>>) {
this.valuesOfEnum(enumType).forEach(felt => {
this.sjekkFelt(felt, felter, enumType, lookupFn);
});
}
// #ts-ignore enumType fungerer fint i praksis ...
public sjekkFeltALT<T, E extends Node = HTMLElement>(felt: string, felter: T[], enumType, lookupFn: (felt: string) => Chainable<JQuery<E>>) {
if (felter.some(feltSomSkalVises => enumType[feltSomSkalVises] == felt)) {
lookupFn(felt).should('be.visible');
} else {
lookupFn(felt).should('not.exist');
}
}
Or is the solution to try and check if the elements exists first, then if they do, check the visibility?
The tl;dr is that there isn't going to be a simple solution here -- Cypress' get command has assertions, so you can't easily catch or eat those exceptions. If you try to get an element that doesn't exist, Cypress will have a failed assertion. Unfortunately, the best case would be to have deterministic behavior for each assertion.
More info on why Cypress behaves this way here.
I think your best case for doing this would be to write a custom Chai assertion, but I don't have any experience in doing anything like that. Here is Chai's documentation on doing so.
If you wanted to simplify your code, but knew which elements should not exist and which elements should not be visible, you could write a custom command to handle that.
Cypress.Commands.add('notExistOrNotVisible', (selector, isNotExist) => {
cy.get(selector).should(isNotExist ? 'not.exist' : 'not.be.visible');
});
cy.notExistOrNotVisible('foo', true); // asserts that `foo` does not exist
cy.notExistOrNotVisible('bar', false); // asserts that `bar` is not visible
I arbitrarily made not exist the positive case, but you could switch that and the logic in the should.
I was facing the same problem, with some modals being destroyed (i.e. removed from the DOM) on close and others being just hidden.
I found a way to kinda emulate an or by adding the visibility check as a filter to the selection, then asserting non-existence:
cy.get('my-selector').filter(':visible').should('not.exist')
The error messages in case of failure are not as self-explanatory ("expected :visible to not exist") and you have to read the log a bit further to understand. If you don't need the separation between selector and filter you can combine the both to make get a nicer error message ("expected my-selector:visible to not exist"):
cy.get('my-selector:visible').should('not.exist')
Hopefully this will help some of you. I've been working with Cypress for a while now and found these particular custom commands to be pretty useful. I hope they help you too. ( Check for visibility utilizes the checkExistence command as well. You can just use the cy.isVisible() command and it will automatically check if it's at least in the DOM before continuing ).
P.S. These commands are still being tweaked - be nice :)
Command for checking existence
/**
* #param {String} errorMessage The error message you want to throw for the function if no arg is used
*/
const isRequired = (errorMessage = '--- Parameter is required! ---') => { throw new Error(errorMessage); };
/**
* #description Check if an element, found through xpath selector, exists or not in the DOM
* #param {String} xpath Required. Xpath to pass into the function to check if it exists in the DOM
* #param {Object} ifFound Message to pass to cy.log() if found. Default message provided
* #param {String} ifNotFound Message to pass to cy.log() if not found. Default message provided
* #returns Boolean. True if found, False if not found
* #example cy.checkExistence("xpath here", "Found it!", "Did not find it!").then(result=>{if(result==true){ // do something } else { // do something else }})
*/
Cypress.Commands.add('checkExistence',(xpath=isRequired(), ifFound, ifNotFound )=>{
return cy.window().then((win) => {
return (function(){
let result = win.document.evaluate(xpath, win.document.body, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue
if(result!=null && result!=undefined){
if ( ifFound != undefined ) {
cy.log( `_** ${ifFound} **_` );
}
return cy.wrap( true );
} else {
if ( ifNotFound != undefined ) {
cy.log( `_** ${ifNotFound} **_` );
}
return cy.wrap( false );
}
})();
});
})
Command for checking visibility
/**
* #param {String} errorMessage The error message you want to throw for the function if no arg is used
*/
const isRequired = (errorMessage = '--- Parameter is required! ---') => { throw new Error(errorMessage); };
/**
* #description Check to see if an element is visible or not, using xpath selector or JQuery element
* #param {String} Xpath Xpath string
* #param {Boolean} waitForVisibility Optional. False by default. Set to true to wait for element to become visible.
* #param {Number} timeout Optional. Timeout for waitForVisibility param.
* #returns Boolean. True if visible, False if not visible
* #example cy.isVisible("//div").then(result=>{})
*/
Cypress.Commands.add('isVisible', (Xpath=isRequired(), waitForVisibility=false, timeout)=>{
cy.checkExistence(Xpath).then(result=>{
if(result==true){
cy.xpath(Xpath).then($element => {
if ($element.is(':visible')){
return cy.wrap(true)
} else {
if(waitForVisibility===true){
if(!timeout){
cy.logSpecial('wave',3, `Must provide value for timeout. Recieved ${timeout}`, true)
} else {
cy.log(`Waiting for element to become visible within ${timeout / 1000} seconds...`, true).then(()=>{
let accrued;
let interval = 250;
(function retry(){
if ($element.is(':visible')){
return cy.wrap(true)
} else {
accrued = accrued + interval;
if(accrued>=timeout){
cy.log(`Timeout waiting for element to become visible. Waited ${timeout / 1000} seconds.`)
return cy.wrap(false)
} else {
cy.wait(interval)
cy.wrap(retry())
}
}
})();
})
}
} else {
return cy.wrap(false)
}
}
})
} else {
cy.log(`Element does not exist in the DOM. Skipping visibility check...`).then(()=>{
if(throwError==true){
throw new Error (`Element of xpath ${Xpath} was not visible.`)
}
return cy.wrap(false)
})
}
})
})

check if d3.select or d3.selectAll

I have a method on a reusable chart that can be passed a selection and return a value if it is passed a d3.select('#id') selection or an array of values if it is passed a d3.selectAll('.class') selection. I'm currently interrogating the passed argument with context._groups[0] instanceof NodeList, but it feels a little fragile using an undocumented property, as that may change in future versions. Is there a more built in way of determining if a selection comes from select or selectAll?
selection.size() will not help here, as it only tells us the result of the selection, not how it was called.
EDIT:
Here's the context of the use. I'm using Mike Bostock's reusable chart pattern and this instance includes a method for getting/setting a label for a donut.
To me, this API usage follows the principle of least astonishment, as it's how I would expect the result to be returned.
var donut = APP.rotatingDonut();
// set label for one element
d3.select('#donut1.donut')
.call(donut.label, 'Donut 1')
d3.select('#donut2.donut')
.call(donut.label, 'Donut 2')
// set label for multiple elements
d3.selectAll('.donut.group-1')
.call(donut.label, 'Group 1 Donuts')
// get label for one donut
var donutOneLabel = d3.select('#donut1').call(donut.label)
// donutOnelabel === 'Donut 1'
// get label for multiple donuts
var donutLables = d3.selectAll('.donut').call(donut.label)
// donutLabels === ['Donut 1', 'Donut 2', 'Group 1 Donuts', 'Group 1 Donuts']
and the internal method definition:
App.rotatingDonut = function() {
var label = d3.local();
function donut() {}
donut.label = function(context, value) {
var returnArray;
var isList = context._groups[0] instanceof NodeList;
if (typeof value === 'undefined' ) {
// getter
returnArray = context.nodes()
.map(function (node) {return label.get(node);});
return isList ? returnArray : returnArray[0];
}
// settter
context.each(function() {label.set(this, value);});
// allows method chaining
return donut;
};
return donut
}
Well, sometimes a question here at S.O. simply doesn't have an answer (it has happened before).
That seems to be the case of this question of yours: "Is there a more built in way of determining if a selection comes from select or selectAll?". Probably no.
To prove that, let's see the source code for d3.select and d3.selectAll (important: those are not selection.select and selection.selectAll, which are very different from each other).
First, d3.select:
export default function(selector) {
return typeof selector === "string"
? new Selection([[document.querySelector(selector)]], [document.documentElement])
: new Selection([[selector]], root);
}
Now, d3.selectAll:
export default function(selector) {
return typeof selector === "string"
? new Selection([document.querySelectorAll(selector)], [document.documentElement])
: new Selection([selector == null ? [] : selector], root);
}
As you can see, we have only two differences here:
d3.selectAll accepts null. That will not help you.
d3.selectAll uses querySelectorAll, while d3.select uses querySelector.
That second difference is the only one that suits you, as you know by now, since querySelectorAll:
Returns a list of the elements within the document (using depth-first pre-order traversal of the document's nodes) that match the specified group of selectors. The object returned is a NodeList. (emphasis mine)
And querySelector only...:
Returns the first Element within the document that matches the specified selector, or group of selectors.
Therefore, the undocumented (and hacky, since you are using _groups, which is not a good idea) selection._groups[0] instanceof NodeList you are using right now seems to be the only way to tell a selection created by d3.select from a selection created by d3.selectAll.

ExtJs 6 doSort method

I didn't find doSort function available in EXT 6 with respect to the grid columns and also didnt find it in any upgrade notes. may be because it is a private function, can anyone please tell me what is the alternative to do the same thing what doSort was doing in Ext 4 ?
I tried to use sorters instead,
{
text: 'columnText',
dataIndex: 'columnIndex',
sorter: me.sort
}
sort: function(v1,v2) {
...
}
but i didn't found smth like dataIndex or columnName in v1, v2 parameters to do sort. (it's just a model)
I need empty cell be from below after Sort Ascending, empty cell be from above after Sort Descending
Thanks.
What is the problem here? You can use the model object to retrieve your column data to sort. From the docs:
sorter: function(record1, record2) {
var name1 = record1.data.columnIndex;
var name2 = record2.data.columnIndex;
return name1 > name2 ? 1 : (name1 === name2) ? 0 : -1;
}
EDIT: If you dont want to rewrite this for every column, then you can do a trick like this:
sorter: (function(columnIndex){ return function(v1, v2){ me.sort(v1, v2, columnIndex);} })("column1")
Now, you can get the column name as 3rd argument in your sort function.
You want to sort the store, not a column. Have a look at the doSort function in ExtJS 4 for a moment:
doSort: function(state) {
var tablePanel = this.up('tablepanel'),
store = tablePanel.store;
// If the owning Panel's store is a NodeStore, this means that we are the unlocked side
// of a locked TreeGrid. We must use the TreeStore's sort method because we cannot
// reorder the NodeStore - that would break the tree.
if (tablePanel.ownerLockable && store.isNodeStore) {
store = tablePanel.ownerLockable.lockedGrid.store;
}
store.sort({
property: this.getSortParam(),
direction: state
});
},
/**
* Returns the parameter to sort upon when sorting this header. By default this returns the dataIndex and will not
* need to be overriden in most cases.
* #return {String}
*/
getSortParam: function() {
return this.dataIndex;
},
Now, this code is still working in ExtJS 6, just that it is no longer part of the framework. You can "put it back into the framework" (e.g. as an override) and it should work again. Or you can use the relevant parts directly from the column event, e.g.
columns:[{
dataIndex:'ABC',
listeners:{
headercontextmenu:function(ct, column) {
column.mySortState = column.mySortState=='ASC'?'DESC':'ASC';
ct.up('grid').getStore().sort({
property:column.dataIndex;
direction:column.mySortState
});
}
}
}]
Maybe you need to define it in the model like this:
fields: [{
name: "textField",
type: 'string',
//sortType: 'asInt'
}]

Looking for selectAll-like operation that will include calling object in the selection (whenever it matches the selector)

The d3.js expression
d3.select(foo).selectAll(some_selector)
will return a selection comprising all the strict descendants of foo that satisfy some_selector.
But suppose that foo itself satisfies some_selector. How can I get it included in the resulting selection when this is the case?
The following naive solution to this problem
d3.select(foo.parentNode).selectAll(some_selector)
is incorrect, because, in general, the selection resulting from it will include any siblings of foo that satisfy some_selector!
IOW, I'm looking for a solution that is clearer, more concise, and less of a dirty hack than this (for example):
// temporarily tag all candidate elements, namely, foo and all its descendants
d3.select(foo).classed('yuk', true)
.selectAll('*').classed('yuk', true);
var parent = d3.select(foo.parentNode),
wanted = parent.selectAll(some_selector)
.filter('.yuk');
// undo the tagging
parent.selectAll('.yuk').classed('yuk', false);
Your question addresses the same issue as the other one you posted yesterday, although not being an exact duplicate. My answer to that one will work for this problem as well. I have adjusted my JSFiddle to allow for some filtering on the node and its descendants:
var selector = ".foo",
x = d3.select("#x"); // your parent node
// Get all children of node x regarding selector as NodeList and convert to Array.
var xAndDescendants = Array.prototype.slice.call(
x.node().querySelectorAll(selector)
);
// Add node x to the beginning if selector is true.
if (!(x = x.filter(selector)).empty())
xAndDescendants.unshift(x.node());
// Select resulting array via d3.js
var selection = d3.selectAll(xAndDescendants);
It's possible to avoid the class but its a bit complicated.
Here is one solution (I'm sure there are simpler ways!)..
function init() {
d3.select(foo)
.selectAll('*')
.call(function () {
immediateFamily(this, function (selection) {
return selection
.style('padding', '3px')
.style('border', '1px solid green')
})
})
}
;(function () {
$(document).ready(init)
})()
function immediateFamily(selection, styling) {
styling(selection)
styling(d3.select(selection[0].parentNode))
}
The idea is to avoid repeating the styling clauses by putting them in an anonymous function in the selection chain and pass this, along with the this context, to a function that applies said styling to the the selection and the parent node of the first group in the selection.
To make it slightly more terse - and even less comprehensible...
function immediateFamily2(selection, styling) {
styling(d3.select(styling(selection)[0].parentNode))
}
To take it to it's ultimate and possibly most idiomatically correct conclusion...
;(function () {
$(document).ready(init2)
function init2() {
var foo = d3.select('tr').filter(':first-child')[0][0]
d3.select(foo)
.selectAll('*')
.call(immediateFamily, bar)
function bar(selection) {
selection
.style('margin', '10px 20px 10px 20px')
.style('outline', '1px solid green')
}
}
function immediateFamily(selection, styling) {
styling(this)
styling(d3.select(this[0].parentNode))
return this
}
})()
Of course it could be further generalised but you get the idea.
(This code runs fine, but feel free to insert your own semicolons!)

Passing parameters to i18n model within XML view

How can we pass parameters to the i18n model from within a XML view?
Without parameters
<Label text="{i18n>myKey}"/>
works but how can we pass a parameter in that expression?
The only piece of information I've found so far is http://scn.sap.com/thread/3586754. I really hope that this is not the proper way to do it since this looks more like a (ugly) hack to me.
The trick is to use the formatter jQuery.sap.formatMessage like this
<Label text="{parts:['i18n>myKey', 'someModel>/someProperty'],
formatter: 'jQuery.sap.formatMessage'}"/>
This will take the value /someProperty in the model someModel and just stick it in myKey of your i18n resource bundle.
Edit 2020-05-19:
jQuery.sap.formatMessage is deprecated as of UI5 version 1.58. Please use sap/base/strings/formatMessage. See this answer on usage instructions.
At the moment this is not possible. But you can use this simple workaround, that works for me.
Preparations
First of all we create a general i18n handler in our Component.js. We also create a JSONModel with a simple modification, so that immediatly the requested path is returned.
sap.ui.define([
"sap/ui/core/UIComponent",
"sap/ui/model/json/JSONModel"
], function(UIComponent, JSONModel) {
"use strict";
return UIComponent.extend("your namespace", {
/**
* Add a simple "StringReturnModel" to the components' models
*/
init: function() {
// [...] your other code in the init method
// String Return Model
var stringModel = new JSONModel({});
stringModel.getProperty = function(sPath) {
return sPath;
};
this.setModel(stringModel, "string");
},
/**
* Reads out a string from our text domain.
* The model i18n is defined in your manifest.json
*
* #param param text parameter
* #param arr array for parameters
* #return string
*/
i18n: function(param, arr) {
var oBundle = this.getModel("i18n").getResourceBundle();
return oBundle.getText(param, arr);
},
});
});
Now, a model with the context {string>} exists. To use the i18n function in the XML view, we create a formatter function. This function parses the parts of the binding and returns the localized string.
sap.ui.define([
], function() {
"use strict";
var formatter = {
/**
* First argument must be the property key. The other
* one are the parameters. If there are no parameters, the
* function returns an empty string.
*
* #return string The localized text
*/
i18n: function() {
var args = [].slice.call(arguments);
if (args.length > 1) {
var key = args.shift();
// Get the component and execute the i18n function
return this.getOwnerComponent().i18n(key, args);
}
return "";
}
};
return formatter;
});
How To Use:
Together with the string-model you can use the formatter to pass paramaters to your i18n:
<Text text="{ parts: ['string>yourProperty', 'string/yourFirstParamter', 'anotherModel/yourSecondParamter'], formatter: '.model.formatter.i18n' }" />
You can pass how many paramaters as you want, but be sure that the first "part" is the property key.
What is written at the link is correct for complex formatting case.
But if you want to combine two strings you can just write
<Label text="{i18n>myKey} Whatever"/>
or
<Label text="{i18n>myKey1} {i18n>myKey2}"/>
create file formatter.js
sap.ui.define([
"sap/base/strings/formatMessage"
], function (formatMessage) {
"use strict";
return {
formatMessage: formatMessage
};
});
View
<headerContent>
<m:MessageStrip
text="{
parts: [
'i18n>systemSettingsLastLoginTitle',
'view>/currentUser',
'view>/lastLogin'
],
formatter: '.formatter.formatMessage'
}"
type="Information"
showIcon="true">
</m:MessageStrip>
</headerContent>
Controller
var oBundle = this.getModel("i18n").getResourceBundle();
MessageToast.show(this.formatter.formatMessage(oBundle.getText("systemSettingsLastLoginTitle"), "sInfo1", "sInfo2"));
i18n
systemSettingsLastLoginTitle=You are logged in as: {0}\nLast Login: {1}
As ugly as it may seem, the answer given in the link that you mentioned is the way to go. However it may seem complicated(read ugly), so let's break it down..
Hence, you can use the following for passing a single parameter,
<Label text="{path: 'someParameter', formatter: '.myOwnFormatter'}"/>
Here, the someParameter is a binding of a OData model attribute that has been bound to the whole page/control, as it is obvious that you wouldn't bind a "hardcoded" value in a productive scenario. However it does end with this, as you see there isn't a place for your i18n text. This is taken care in the controller.js
In your controller, add a controller method with the same formatter name,
myOwnFormatter : function(someParameter)
{
/* the 'someParameter' will be received in this function */
var i18n = this.i18nModel; /* However you can access the i18n model here*/
var sCompleteText = someParameter + " " + i18n.getText("myKey")
/* Concatenate the way you need */
}
For passing multiple parameters,
Use,
<Label text="{parts:[{path : 'parameter1'}, {path :'parameter2'}], formatter : '.myOwnFormatter'}" />
And in your controller, receive these parameters,
myOwnFormatter : function(parameter1, parameter2) { } /* and so on.. */
When all this is done, the label's text would be displayed with the parameter and your i18n text.
In principle it is exactly as described in the above mentioned SCN-Link. You need a binding to the key of the resource bundle, and additional bindings to the values which should go into the parameters of the corresponding text. Finally all values found by these bindings must be somehow combined, for which you need to specify a formatter.
It can be a bit shortened, by omitting the path-prefix inside the array of bindings. Using the example from SCN, it also works as follows:
<Text text="{parts: ['i18n>PEC_to',
'promoprocsteps>RetailPromotionSalesFromDate_E',
'promoprocsteps>RetailPromotionSalesToDate_E'}],
formatter: 'retail.promn.promotioncockpit.utils.Formatter.formatDatesString'}"/>
Under the assumption, that you are using {0},{1} etc. as placeholders, a formatting function could look like the following (without any error handling and without special handling of Dates, as may be necessary in the SCN example):
formatTextWithParams : function(textWithPlaceholders, any_placeholders /*Just as marker*/) {
var finalText = textWithPlaceholders;
for (var i = 1; i < arguments.length; i++) {
var argument = arguments[i];
var placeholder = '{' + (i - 1) + '}';
finalText = finalText.replace(placeholder, arguments[i]);
}
return finalText;
},

Resources