Copy link to clipboard
Copied
Hello, there.
I am creating a script that will create a group with 3 columns: name (statictext), template (dropdownlist) and masterpages (dropdownlist).
If the name's array has 4 items, the group will be populated with 4 lines (each one with name, template and masterpages).
The dropdownlists on column 2 (template) I filled with files found in selected folder.
The dropdownlists on column 3 (masterpages) I want to fill with the masterpages in each document, based on the selection of previous dropdownlist.
Is it possible?
To create my window, I'm using this:
for (var i=0; i<myEditorias.length; i++) {
g1p1_c1.add("statictext" , undefined , myEditorias);
g2p1_c2.add("dropdownlist" , [0,0,180,20] , myTemplateFiles);
g3p1_c3.add("dropdownlist" , [0,0,180,20] , "" , {title: myEditorias});
}
for (var y=0; y<g2p1_c2.children.length; y++) {
g2p1_c2.children
.onChange = function () { alert(y);
for (var i=0; i<myMasters.length; i++) {
if (this.selection.text == myMasters[0]) {
//POPULATE g3p1_c3.children(y) BASED ON g2p1_c2 SELECTION
}
}
}
}
The alert on line 12 results as undefined, because the y is outside the function.
I tried to put y inside the function's parenthesis, but it didn't work.
Any help will be appreciated.
On afterthought i think this can also be solved by making a closure, something like the below
for (var y=0; y<g2p1_c2.children.length; y++)
{
g2p1_c2.children
.onChange = getCallBack(y) }
function getCallBack(y)
{
return function()
{
alert(y);
for (var i=0; i<myMasters.length; i++)
{
if (this.selection.text == myMasters[0])
{
//POPULATE g3p1_c3.children(y) BASED ON g2p1_c2 SELECTION
}
}
}
}
-M
...Copy link to clipboard
Copied
Hi,
Could you post the whole code that creates the UI and some instructions on how to set it up to see the problem first hand. Off hand i could say you could add a property to the children which will hold the value of y and that will be accessible inside the OnChange method. Something like
g2p1_c2.children
.myProperty = y
One thing to note is that you are using OnChange method, i think in this method you would not have "this" set to the object on which the event was fired, so you will either have to either use "AddEventListener" which many times does not work due to other issues or else will have to find a way to identify as to which object was the event called for and then access it property value that you added in the loop
If you could share your code i could try and make it work. Hope this did not get too confusing.
-Manan
Copy link to clipboard
Copied
On afterthought i think this can also be solved by making a closure, something like the below
for (var y=0; y<g2p1_c2.children.length; y++)
{
g2p1_c2.children
.onChange = getCallBack(y) }
function getCallBack(y)
{
return function()
{
alert(y);
for (var i=0; i<myMasters.length; i++)
{
if (this.selection.text == myMasters[0])
{
//POPULATE g3p1_c3.children(y) BASED ON g2p1_c2 SELECTION
}
}
}
}
-Manan
Copy link to clipboard
Copied
This last worked fine. The alerts show me the right index.
And the changing the code to populate the column 3 childrens... works fine!!!!!
Thank you so much.
Just to understand: Before to post here at the Forum, I tried an approach like this, but just calling a new function.
Could you help me to understand what the getCallBack function is doing?
I appreciate a LOT your help, thank you so much Manan Joshi​
Copy link to clipboard
Copied
Hi,
There is another possible approach, avoiding closure and multiple event listeners.
What is not clearly stated in your original question is that you have a 1-to-N map structure from file name to master templates. So you can build your dialog by just passing your titles (static texts) and that map.
I don't think it's a good design to build three independent columns managed separately, while the actual data structure is a set of rows, each having 3 interconnected fields. In this context, better is to use the scheme
+----------------------------------+
¦ +------+ +---------+ +---------+ ¦
¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦
¦ +------+ +---------+ +---------+ ¦
+----------------------------------+
+----------------------------------+
¦ +------+ +---------+ +---------+ ¦
¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦
¦ +------+ +---------+ +---------+ ¦
+----------------------------------+
+----------------------------------+
¦ +------+ +---------+ +---------+ ¦
¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦
¦ +------+ +---------+ +---------+ ¦
+----------------------------------+
Finally, a great mechanism in ScriptUI is the option to address various events from a single place, using a single event listener that properly identifies the target (child component) that is sending a command.
Here is a full implementation to illustrate these ideas:
const myDialog = function myDialog(/*str[]*/names,/*{file=>str[]}*/map, ff,k,w,p,i,g,f)
//----------------------------------
{
// Checkpoints.
// ---
if( !(names instanceof Array) ) throw "Invalid `names` arg. Should be an array.";
if( !(map===Object(map)) ) throw "Invalid `map` arg. Should be an object.";
// Extract the file array from `map` (for feeding the 2nd dropdown.)
// ---
ff = [];
for( k in map )
{
if( !map.hasOwnProperty(k) ) continue;
if( !(map
instanceof Array) ) throw "Invalid `map` key. Should refer to an array."; ff[ff.length] = k;
}
// Create the dialog and a convenient root container.
// ---
w = new Window('dialog',"My Dialog");
(p = w.add('panel')).orientation = 'column';
p.margins = 20;
// Create the rows (one for each name ; group of 3 columns.)
// ---
for( i=-1 ; ++i < names.length ; )
{
// Group container (row #i).
// ---
(g=p.add('group')).orientation = 'row';
g.spacing = 20;
g.alignChildren = ['left','center'];
// Row fields.
// ---
k = names;
g.add('statictext', void 0, k, { idx: i }).preferredSize = [150,-1];
g.add('dropdownlist', void 0, ff, { kind:'FILE' }).preferredSize = [180,-1];
g.add('dropdownlist', void 0, '', { name:'TPL', title:k }).preferredSize = [180,-1];
}
// The root container is the best place to manage the
// change 'event'. You then have a unique listener.
// ---
(f=callee.ON_CHANGE).Q = map; // Cache the map.
p.addEventListener('change', f); // Register the handler.
w.add('button', void 0, "Done", { name:'ok' });
return w;
};
myDialog.ON_CHANGE = function onChange(/*Event*/ev, src,dst,s,a,i)
//----------------------------------
// this :: 'panel' ; ev.type == 'change'
// [REM] callee.Q maps a source keystring to a dest array.
{
// Checkpoint and cleanup.
// ---
if( 'change' != ev.type ) return;
src = ev.target;
if( 'FILE' != (src.properties||0).kind ) return;
dst = src.parent['TPL'];
if( (!dst) || 'dropdownlist' != dst.type ) return;
dst.items.length && dst.removeAll();
// Get the mapped array from the current `src` selection.
// ---
if( !(s=src.selection) ) return;
if( !(a=(callee.Q||0)[s.text]) || (!a.length) ) return;
// Load a in `dst`.
// ---
for( i=-1 ; ++i < a.length ; dst.add('item',a) );
dst.selection = 0; // Maybe we can select the 1st item (?)
};
// =========================================================
// YOUR DATA
// =========================================================
const NAMES = ["aaaa", "bbb", "cccc", "dddddd"];
const MAP =
{
"File1": ["Master1-A", "HelpMaster", "Summary"],
"MyFileNo2": ["Master2-A", "SubMaster"],
"Other_File": ["Master3-A", "ChapterIntro", "IndexPages"],
"AnotherOne": ["Master4-A", "ThatsIt"],
};
var dlg = myDialog(NAMES, MAP);
dlg && dlg.show();
Best,
Marc
Copy link to clipboard
Copied
Awesome too. That's what I'm looking for in this code. An array with masterpages for each file.
As I can't deal with it, I found this way I did.
Your code is a little complex to me. I'll study it to figure out what each variable means, etc.
As I saw, a map is an object of arrays formed by two values.
What is the difference between a map and myArray.push([value1 , value2])?
How to set a Map in ESTK?
Thank you so much, Marc!!!
Copy link to clipboard
Copied
In Javascript a function has access to the variables in the parent scope even after the parent has finished its execution. In the getCallBack method what i am doing is binding the state of y with the function that is being returned. Each time getCallBack is called with a different value of y which is accessible to the inner function which is returned and whose reference is bound to the callback method. Now whenever this callback method is called, it will have access to the value of y and hence we use closure to our rescue. I hope this makes sense.
A simple start to closures can be read from the following link, then you can explore ahead as it suits you
Demystifying JavaScript Closures, Callbacks and IIFEs
-Manan
Copy link to clipboard
Copied
Great, thank you so much again, Manan!
Copy link to clipboard
Copied
Hello, Manan Joshi​.
I'm getting blind about a very similar situation of this original post.
But, this time, I am adding new groups to my window with + buttons, as in Peter Kahrel's UI manual (page 80).
plusBtn.onClick = function () {
g1 = g1g0p1.add("group");
g1.alignment = "left";
g1.orientation = "row";
g1.add("statictext" , undefined , "A página");
g1.add("dropdownlist" , undefined , myPares);
g1.add("statictext" , undefined , "deve casar com a página");
g1.add("statictext" , undefined , "[...]");
if (g1g0p1.children.length > 1) {
minusBtn.enabled = true;
}
if (g1g0p1.children.length == myPares.length) {
plusBtn.enabled = false;
}
g1.index = g1g0p1.children.length-1;
w.layout.layout(true);
}
In the new g1 element, I want to fill the children[3] based on children[1] selection.
So, I tried to use yout getCallBack suggestion.
for (var x=0; x<g1g0p1.children.length; x++) {
g1g0p1.children
.children[1].onChange = populate(x); }
function populate(x) {
return function () {
var myNumero = Number(this.selection.text)+1;
g1g0p1.children
.children[3].text = "\[" + String(myNumero) + "\]"; plusBtn.enabled = true;
}
}
But, even if I try an alert inside the return function () {} it's just working in the first g1 (the one created in the dialog main code).
Any suggestion?
Here is the complete fragment I am having problems.
if (!app.documents.length || (app.documents.length && !app.documents[0].visible)) {
var w = new Window("dialog");
w.orientation = "column";
var myQtdPages = 8;
var p1 = w.add("panel" , undefined , "Páginas duplas");
p1.preferredSize.width = 312;
p1.margins.top = 20;
p1.orientation = "column";
var g1g0p1 = p1.add("group");
g1g0p1.orientation = "column";
var g1 = g1g0p1.add("group");
g1.alignment = "left";
g1.orientation = "row";
var myText_1 = g1.add("statictext" , undefined , "A página");
var myPares = [];
var myPagePar = g1.add("dropdownlist" , undefined , myPares);
var myText_2 = g1.add("statictext" , undefined , "deve casar com a página");
var myPageImpar = g1.add("statictext" , undefined , "[...]");
if (myQtdPages > 0) {
for (var i=0; i<myQtdPages-1; i++) {
if (i<myQtdPages-1 && (i+1) % 2 == 0) {
myPares.push(i+1);
myPagePar.add("item" , i+1);
}
}
}
var myControls = p1.add("group");
myControls.margins.top = 10;
myControls.orientation = "row";
myControls.alignment = "right";
myControls.alignChildren = "right";
var plusBtn = myControls.add("button" , [0,0,24,24] , "+");
var minusBtn = myControls.add("button" , [0,0,24,24] , "-");
plusBtn.enabled = false;
minusBtn.enabled = false;
plusBtn.onClick = function () {
g1 = g1g0p1.add("group");
g1.alignment = "left";
g1.orientation = "row";
g1.add("statictext" , undefined , "A página");
g1.add("dropdownlist" , undefined , myPares);
g1.add("statictext" , undefined , "deve casar com a página");
g1.add("statictext" , undefined , "[...]");
if (g1g0p1.children.length > 1) {
minusBtn.enabled = true;
}
if (g1g0p1.children.length == myPares.length) {
plusBtn.enabled = false;
}
g1.index = g1g0p1.children.length-1;
w.layout.layout(true);
}
minusBtn.onClick = function () {
var del = g1g0p1.children.length-1;
if (del > 0) {
g1g0p1.remove(g1g0p1.children[del]);
if (g1g0p1.children.length == 1) {
minusBtn.enabled = false;
}
}
w.layout.layout(true);
}
for (var x=0; x<g1g0p1.children.length; x++) {
g1g0p1.children
.children[1].onChange = populate(x); }
function populate(x) {
return function () {
alert(x);
var myNumero = Number(this.selection.text)+1;
g1g0p1.children
.children[3].text = "\[" + String(myNumero) + "\]"; plusBtn.enabled = true;
}
}
w.show();
}
Thanks in advance.
Copy link to clipboard
Copied
Hi lf.corullon​,
Provided i understood the problem correctly, the issue is that you will need to add you event handler when you are adding control on click of the plus button. Initially the code runs from top to bottom and the event handler is attached on the first dropdown on the window, but after that whenever you click on the + sign the code only inside the onClick event handler is run and hence no event handlers are added to these new dropdowns and hence you see the issue. If you make the following change to your code it should work
plusBtn.onClick = function ()
{
g1 = g1g0p1.add("group");
g1.alignment = "left";
g1.orientation = "row";
g1.add("statictext" , undefined , "A página");
g1.add("dropdownlist" , undefined , myPares);
g1.add("statictext" , undefined , "deve casar com a página");
g1.add("statictext" , undefined , "[...]");
if (g1g0p1.children.length > 1)
{
minusBtn.enabled = true;
}
if (g1g0p1.children.length == myPares.length)
{
plusBtn.enabled = false;
}
g1.index = g1g0p1.children.length-1;
w.layout.layout(true);
for (var x=0; x<g1g0p1.children.length; x++)
{
g1g0p1.children
.children[1].onChange = populate(x); }
}
-Manan