Skip to main content
Dan Rodney
Community Expert
Community Expert
December 16, 2011
Answered

[JS] Menu Added via Scripting Moves

  • December 16, 2011
  • 3 replies
  • 23178 views

I've been able to sucessfully add items to the InDesign menu thanks in part to Marijan Tompa's (tomaxxi) blog post http://indisnip.wordpress.com/2010/08/08/create-customized-menu/

My test code (below) creates a new menu, and sucessfully adds two menu items plus a submenu. The submenu is causing me problems. When you first launch InDesign it's created in the proper place (in the middle of the menu). But when you relaunch InDesign, the submenu moves to the top of the menu and never goes back to it's proper position farther down in the menu where it was the first time InDesign was launched.

I've searched high and low in this forum, the web and InDesign's documentation and can't figure out how to keep it from moving (I want the menu to be farther down the menu, not at the top). I hope one of you kind souls will help me to control the position of the submenu (and have it stay there across launches).

Thanks in advance!

Dan

Here's the code I'm working with. This is saved as testMenu.jsx in the Scripts > startup scripts folder.

P.S. I'm testing this in CS5.5 currently, but ideally this solution should work in CS3 and later.

#targetengine "myTestMenu"

var myFolder = Folder(app.activeScript.path);

myFolder = myFolder.parent + '/Scripts Panel/';

var menuItem1Handler = function( /*onInvoke*/ ){

  app.doScript(File(myFolder + 'MyTest1.jsx'));

};

var menuItem2Handler = function( /*onInvoke*/ ){

  app.doScript(File(myFolder + 'MyTest2.jsx'));

};

menuInstaller()

function menuInstaller() {

  var menuItem1T = "My Menu Item 1",

       menuItem2T = "My Menu Item 2",

       menuT = "MyTestMenu",

             subT = "Sub Menu",

       subs = app.menus.item("$ID/Main").submenus, sma, mnu;

  var refItem = app.menus.item("$ID/Main").submenus.item("$ID/&Layout");

  subMenu1 = app.scriptMenuActions.item(menuItem1T);

  if( subMenu1 == null ) {

          subMenu2 = app.scriptMenuActions.add(menuItem1T);

  }

  subMenu2 = app.scriptMenuActions.item(menuItem2T);

  if( subMenu2 == null ) {

          subMenu2 = app.scriptMenuActions.add(menuItem2T);

  }

  subMenu2.eventListeners.add("onInvoke", menuItem2Handler);

  mnu = subs.item(menuT);

  if( mnu == null ) {

            mnu = subs.add(menuT, LocationOptions.after, refItem);

  }

  mnu.menuItems.add(subMenu1);

  mnu.menuSeparators.add();

  subsSubs = app.menus.item( '$ID/Main' ).submenus.item( menuT ).submenus;

  mnuSubMenu = subsSubs.item( subT );

  if( mnuSubMenu == null ) {

          mnuSubMenu = subsSubs.add( subT);

  }

  mnu.menuItems.add(subMenu2);

};

This topic has been closed for replies.
Correct answer Dan Rodney

Note to self: Don't respond to the forum before your brain is engaged...

Ignore what I wrote above. It made no sense on a lot of levels...

The issue at hand is caused by the following:

Submenus do not go away until you remove them. When restart InDesign, the submenu still exists, so you are creating the meni tems below it.

You need to create the submneu first and specify LocationOptions. Something like this:

function menuInstaller() {

  var menuItem1T = "My Menu Item 1",

       menuItem2T = "My Menu Item 2",

       menuT = "MyTestMenu",

             subT = "Sub Menu",

       subs = app.menus.item("$ID/Main").submenus, sma, mnu;

  var refItem = app.menus.item("$ID/Main").submenus.item("$ID/&Layout");

  subMenu1 = app.scriptMenuActions.item(menuItem1T);

  if( subMenu1 == null ) {

          subMenu1 = app.scriptMenuActions.add(menuItem1T);

  }

  subMenu1.eventListeners.add("onInvoke", menuItem1Handler);

  subMenu2 = app.scriptMenuActions.item(menuItem2T);

  if( subMenu2 == null ) {

          subMenu2 = app.scriptMenuActions.add(menuItem2T);

  }

  subMenu2.eventListeners.add("onInvoke", menuItem2Handler);

  mnu = subs.item(menuT);

  if( mnu == null ) {

            mnu = subs.add(menuT, LocationOptions.after, refItem);

  }

  subsSubs = app.menus.item( '$ID/Main' ).submenus.item( menuT ).submenus;

  mnuSubMenu = subsSubs.item( subT );

  if( mnuSubMenu == null ) {

          mnuSubMenu = subsSubs.add( subT);

  }

  mnu.menuItems.add(subMenu1,LocationOptions.BEFORE,mnuSubMenu);

  mnu.menuSeparators.add(LocationOptions.BEFORE,mnuSubMenu);

  mnu.menuItems.add(subMenu2,LocationOptions.AFTER,mnuSubMenu);

};

Harbs


Thank you all so much for your replies, and thank you Harbs for making it finally work!

Just to make sure this can serve as a resource for others in the future, below is a final (working) menu based on Harb's code. I also added 2 submenu items just to be complete.

Thanks again everyone. I'm so glad you could help get this working!

Dan

#targetengine "HarbsTestMenu"

menuInstaller();

function menuInstaller() {

    // SET THE FILES THAT ARE TRIGGERED BY MENU ITEMS

    menuItem1Handler = function( /*onInvoke*/ ){

        app.doScript( File(myFolder + 'File-One.jsx') );

    };

    menuItem2Handler = function( /*onInvoke*/ ){

        app.doScript( File(myFolder + 'File-Two.jsx') );

    };

    subMenuItem1Handler = function( /*onInvoke*/ ){

        app.doScript( File(myFolder + 'File-Three.jsx') );

    }; 

    subMenuItem2Handler = function( /*onInvoke*/ ){

        app.doScript( File(myFolder + 'File-Four.jsx') );

    }; 

 

    // SET GENERAL VARIABLES

    try {

        myFolder = app.activeScript;

    }

    catch(e) {

        myFolder = File(e.fileName);  // we are running from the ESTK

    }

    myFolder = myFolder.parent.parent  + '/' ;  //this file is in the "startup scripts" subfolder

 

    var menuItem1T = "My Menu Item 1",

       menuItem2T = "My Menu Item 2",

       subMenuItem1T = "My SubMenu Item 1",

       subMenuItem2T = "My SubMenu Item 2",

       menuT = "HarbsTestMenu",

       subT = "Sub Menu",

       subs = app.menus.item("$ID/Main").submenus, mnu;

    var refItem = app.menus.item("$ID/Main").submenus.item("$ID/&Layout");

    // CREATE MENU ITEMS

    subMenu1 = app.scriptMenuActions.item(menuItem1T);

    if( subMenu1 == null ) {

        subMenu1 = app.scriptMenuActions.add(menuItem1T);

    }

    subMenu1.eventListeners.add("onInvoke", menuItem1Handler);

    subMenu2 = app.scriptMenuActions.item(menuItem2T);

    if( subMenu2 == null ) {

        subMenu2 = app.scriptMenuActions.add(menuItem2T);

    }

    subMenu2.eventListeners.add("onInvoke", menuItem2Handler);

    subSubMenu1 = app.scriptMenuActions.item(subMenuItem1T);

    if( subSubMenu1 == null ) {

        subSubMenu1 = app.scriptMenuActions.add(subMenuItem1T);

    }

    subSubMenu1.eventListeners.add("onInvoke", subMenuItem1Handler);

    subSubMenu2 = app.scriptMenuActions.item(subMenuItem2T);

    if( subSubMenu2 == null ) {

        subSubMenu2 = app.scriptMenuActions.add(subMenuItem2T);

    }

    subSubMenu2.eventListeners.add("onInvoke", subMenuItem2Handler);

    mnu = subs.item(menuT);

    if( mnu == null ) {

        mnu = subs.add(menuT, LocationOptions.after, refItem);

    }

    subsSubs = app.menus.item( '$ID/Main' ).submenus.item( menuT ).submenus;

    mnuSubMenu = subsSubs.item( subT );

    if( mnuSubMenu == null ) {

        mnuSubMenu = subsSubs.add( subT);

    }

    mnu.menuItems.add(subMenu1,LocationOptions.BEFORE,mnuSubMenu);

    mnu.menuSeparators.add(LocationOptions.BEFORE,mnuSubMenu);

    mnu.menuItems.add(subMenu2,LocationOptions.AFTER,mnuSubMenu);

    mnuSubMenu.menuItems.add(subSubMenu1,LocationOptions.AFTER,mnuSubMenu);

    mnuSubMenu.menuItems.add(subSubMenu2,LocationOptions.AFTER,mnuSubMenu);

};

3 replies

Harbs.
Legend
December 18, 2011

That's odd. John's post seems to exist, but doesn't show up...

Harbs.
Legend
December 18, 2011

That seemed to do it... Weird...

Marc Autret
Legend
December 17, 2011

Hi Dan,

There would be much to say about your code (variable scope, etc.) but the two most important, I think, are:

— Custom menus and submenus are application-persistent while script menu actions are (usually) session-persistent

In the specific case where an action directly relies on a script file, you don't need to deal w/ session-persistence and you can directly address the script file (no need of app.doScript() stuff and so on).

Here is a possible approach (provided that the featured script files are located in the parent folder of the Startup scripts folder):

#targetengine "DanRodneyMenu"

// =====================================

// This script is a 'startup script', it runs each time ID starts

// and shouldn't be rerun during the session. We don't really need

// it to be session-persistent. However we use a #targetengine to

// save a 'done' flag which prevents the script from being rerun.

// =====================================

(function(/*obj|undefined*/HOST)

// -------------------------------------

// Install and/or update the menu/submenu

// and connect the menu actions

{

    // ---

    // HOST can be your framework container, if needed (default: $.global)

    // ---

    HOST || (HOST=$.global);

    // ---

    // Prevent the current (startup) script from being uselessly rerun

    // ---

    if( HOST[$.engineName] )

        {

        alert( "This script is automatically executed at startup. You shouldn't run it manually." );

        return;

        }

        // ---

    HOST[$.engineName] = true;

    // ---

    // Settings and constants

    // ---

    var MENU_NAME = "My Test Menu",

        FEATURES = [

            { caption: "My Menu Item 1", fileName: 'File-One.jsx', subName: "" },

            { caption: "My Menu Item 2", fileName: 'File-Two.jsx', subName: "My Sub Menu" }

            ],

        LO_END = LocationOptions.atEnd,

        INDESIGN_ROOT_MENU = app.menus.item( '$ID/Main' ),

        FEATURE_LOCATION_PATH = (function()

            {

            var f;

            try{ f=app.activeScript; }

            catch(_){ f=File(_.fileName); }

            return f.parent.parent + '/';

            })();

    // ---

    // (Re)set the actions

    // Note: checks also whether script files are available

    // ---

    var    t, f, i = FEATURES.length;

    while( i-- )

        {

        t = FEATURES;

        if( (f=File(FEATURE_LOCATION_PATH + t.fileName)).exists )

            {

            // The script file exists => create the corresponding action

            // and directly attach the event listener to the file

            // (no need to use app.doScript(...) here)

            // ---

            (t.action = app.scriptMenuActions.add( t.caption )).

                addEventListener('onInvoke', f);

            }

        else

            {

            // The script file does not exist => remove that feature

            // ---

            FEATURES.splice(i,1);

            }

        }

    // ---

    // Create/reset the custom menu container *if necessary*

    // Note:  menus/submenus are application-persistent

    // ---

    var    mnu = INDESIGN_ROOT_MENU.submenus.itemByName( MENU_NAME );

    if( !mnu.isValid )

        {

        // Our custom menu hasn't been created yet

        // ---

        if( !FEATURES.length ) return;

        mnu = INDESIGN_ROOT_MENU.submenus.add(

            MENU_NAME,

            LocationOptions.after,

            INDESIGN_ROOT_MENU.submenus.item( '$ID/&Window' )

            );

        }

    else

        {

        // Our custom menu already exists, but we must clear

        // any sub element in order to rebuild a fresh structure

        // ---

        mnu.menuElements.everyItem().remove();

        // If FEATURES is empty, remove the menu itself

        // ---

        if( !FEATURES.length ){ mnu.remove(); return; }

        }

    // ---

    // Now, let's fill mnu with respect to FEATURES' order

    // (Possible submenus are specified in .subName and created on the fly)

    // ---

    var s,

        n = FEATURES.length,

        subs = {},

        sub = null;

    for( i=0 ; i < n ; ++i )

        {

        t = FEATURES;

        // Target the desired submenu

        // ---

        sub = (s=t.subName) ?

            ( subs || (subs=mnu.submenus.add( s, LO_END )) ) :

            mnu;

        // Connect the related action

        // ---

        sub.menuItems.add( t.action );

        }

})();

@+

Marc

John Hawkinson
Inspiring
December 16, 2011

Dan, I haven't gone and tested this in your example, so this is speculative but:

  subMenu1 = app.scriptMenuActions.item(menuItem1T);
  if( subMenu1 == null ) {
          subMenu2 = app.scriptMenuActions.add(menuItem1T);
  }

This isn't really advisable. In CS5 and later, if you don't check .isValid, you can have problems (I think).

But also, in JavaScript, in general, you should not write

if (varname == null)

because it is usually sufficient to simply write

if (varname)

or

if (!varname)

and you should also never use the == operator when you can use the === operator, because it coerces to truthiness. So you should rather instead use

if (varname===null)

if you truly needed to check for null as opposed to undefined or zero.

Perhaps more significnatly, shouldn't you be setting submenu1 inside that if, rather than submenu2?

Dan Rodney
Community Expert
Community Expert
December 16, 2011
  1. .isValid doesn't seem to fix the issue of the submenu moving. It also doesn't work back in CS3. I'd like one set of code (if possible) that will work in CS3 and later. Although if I need two sets of code I can deal with that, as long as I know what would make it work in each version. The submenu is still moving and that's the real issue.
  2. Switching from == to === doesn't fix the issue of the submenu moving (although the == was working even if the error detection isn't as thruthful).
  3. Thanks for the eagle eye on the typo of submenu1 instead of 2, but that also doesn't fix it.

Taking into account your suggestions though, here's a slight update on the code, but the submenu still moves on relaunch. Otherwise it works, it's just that the submenu moves. Any more thoughts?

#targetengine "myTestMenu"

var myFolder = Folder(app.activeScript.path);

myFolder = myFolder.parent + '/Scripts Panel/';

var menuItem1Handler = function( /*onInvoke*/ ){

  app.doScript(File(myFolder + 'MyTest1.jsx'));

};

var menuItem2Handler = function( /*onInvoke*/ ){

  app.doScript(File(myFolder + 'MyTest2.jsx'));

};

menuInstaller()

function menuInstaller() {

  var menuItem1T = "My Menu Item 1",

       menuItem2T = "My Menu Item 2",

       menuT = "MyTestMenu",

             subT = "Sub Menu",

       subs = app.menus.item("$ID/Main").submenus, sma, mnu;

  var refItem = app.menus.item("$ID/Main").submenus.item("$ID/&Layout");

  subMenu1 = app.scriptMenuActions.item(menuItem1T);

  if( subMenu1 === null ) {

          subMenu1 = app.scriptMenuActions.add(menuItem1T);

  }

  subMenu2 = app.scriptMenuActions.item(menuItem2T);

  if( subMenu2 === null) {

          subMenu2 = app.scriptMenuActions.add(menuItem2T);

  }

  subMenu2.eventListeners.add("onInvoke", menuItem2Handler);

  mnu = subs.item(menuT);

  if( mnu === null ) {

            mnu = subs.add(menuT, LocationOptions.after, refItem);

  }

  mnu.menuItems.add(subMenu1);

  mnu.menuSeparators.add();

  subsSubs = app.menus.item( '$ID/Main' ).submenus.item( menuT ).submenus;

  mnuSubMenu = subsSubs.item( subT );

  if( mnuSubMenu === null) {

          mnuSubMenu = subsSubs.add( subT);

  }

  mnu.menuItems.add(subMenu2);

};

— Adobe Certified Expert & Instructor at Noble Desktop | Web Developer, Designer, InDesign Scriptor
praveenkumare90474043
Participant
October 12, 2016

Thank you all so much for your replies, and thank you Harbs for making it finally work!

Just to make sure this can serve as a resource for others in the future, below is a final (working) menu based on Harb's code. I also added 2 submenu items just to be complete.

Thanks again everyone. I'm so glad you could help get this working!

Dan

#targetengine "HarbsTestMenu"

menuInstaller();

function menuInstaller() {

    // SET THE FILES THAT ARE TRIGGERED BY MENU ITEMS

    menuItem1Handler = function( /*onInvoke*/ ){

        app.doScript( File(myFolder + 'File-One.jsx') );

    };

    menuItem2Handler = function( /*onInvoke*/ ){

        app.doScript( File(myFolder + 'File-Two.jsx') );

    };

    subMenuItem1Handler = function( /*onInvoke*/ ){

        app.doScript( File(myFolder + 'File-Three.jsx') );

    }; 

    subMenuItem2Handler = function( /*onInvoke*/ ){

        app.doScript( File(myFolder + 'File-Four.jsx') );

    }; 

 

    // SET GENERAL VARIABLES

    try {

        myFolder = app.activeScript;

    }

    catch(e) {

        myFolder = File(e.fileName);  // we are running from the ESTK

    }

    myFolder = myFolder.parent.parent  + '/' ;  //this file is in the "startup scripts" subfolder

 

    var menuItem1T = "My Menu Item 1",

       menuItem2T = "My Menu Item 2",

       subMenuItem1T = "My SubMenu Item 1",

       subMenuItem2T = "My SubMenu Item 2",

       menuT = "HarbsTestMenu",

       subT = "Sub Menu",

       subs = app.menus.item("$ID/Main").submenus, mnu;

    var refItem = app.menus.item("$ID/Main").submenus.item("$ID/&Layout");

    // CREATE MENU ITEMS

    subMenu1 = app.scriptMenuActions.item(menuItem1T);

    if( subMenu1 == null ) {

        subMenu1 = app.scriptMenuActions.add(menuItem1T);

    }

    subMenu1.eventListeners.add("onInvoke", menuItem1Handler);

    subMenu2 = app.scriptMenuActions.item(menuItem2T);

    if( subMenu2 == null ) {

        subMenu2 = app.scriptMenuActions.add(menuItem2T);

    }

    subMenu2.eventListeners.add("onInvoke", menuItem2Handler);

    subSubMenu1 = app.scriptMenuActions.item(subMenuItem1T);

    if( subSubMenu1 == null ) {

        subSubMenu1 = app.scriptMenuActions.add(subMenuItem1T);

    }

    subSubMenu1.eventListeners.add("onInvoke", subMenuItem1Handler);

    subSubMenu2 = app.scriptMenuActions.item(subMenuItem2T);

    if( subSubMenu2 == null ) {

        subSubMenu2 = app.scriptMenuActions.add(subMenuItem2T);

    }

    subSubMenu2.eventListeners.add("onInvoke", subMenuItem2Handler);

    mnu = subs.item(menuT);

    if( mnu == null ) {

        mnu = subs.add(menuT, LocationOptions.after, refItem);

    }

    subsSubs = app.menus.item( '$ID/Main' ).submenus.item( menuT ).submenus;

    mnuSubMenu = subsSubs.item( subT );

    if( mnuSubMenu == null ) {

        mnuSubMenu = subsSubs.add( subT);

    }

    mnu.menuItems.add(subMenu1,LocationOptions.BEFORE,mnuSubMenu);

    mnu.menuSeparators.add(LocationOptions.BEFORE,mnuSubMenu);

    mnu.menuItems.add(subMenu2,LocationOptions.AFTER,mnuSubMenu);

    mnuSubMenu.menuItems.add(subSubMenu1,LocationOptions.AFTER,mnuSubMenu);

    mnuSubMenu.menuItems.add(subSubMenu2,LocationOptions.AFTER,mnuSubMenu);

};


how to add menus and submenus in looping condition and how to invoke the process in looping. how to separate the menu with submenu in looping..