Keyboard Shortcut to Toggle Un/Comment Out for Oracle APEX

Matt MulvaneyMatt Mulvaney
6 min read

For this Mod for Oracle APEX Page Designer, we are going to add a Ctrl+Shift+/ keyboard shortcut to APEX.

This shortcut does the equivalent of the Comment Out or Uncomment Out feature by toggling between the two.

It’s much faster to bulk select items with the mouse and then use the Ctrl+Shift+/ keyboard shortcut to toggle the build status than it is to popup the menu and locate the entry. For example:

Instructions

Here is what you need to do:

  1. Install Violentmonkey by clicking on the one of the Installation options. Alternatively, If you are unable to install Violentmonkey, this method also works absolutely fine in Tampermonkey.

  2. Open APEX Page Designer

  3. Click the Violentmonkey browser extension icon and click the ➕ icon

  4. Replace all the code with this code and click Save & Close

     // ==UserScript==
     // @name         Pretius Oracle APEX Page Designer Toggle Comment Out
     // @namespace    http://tampermonkey.net/
     // @version      24.2.1
     // @description  Adds a keyboard shortcut to toggle the "Comment Out" build option for selected components in Oracle APEX Page Designer.
     // @match        *://*/ords/*
     // @match        *://*/pls/*
     // @grant        none
     // @author       Matt Mulvaney - Pretius - @Matt_Mulvaney
     // ==/UserScript==
    
     (function() {
         'use strict';
    
         /**
          * DISCLAIMER:
          * This code is unofficial and is not supported by Oracle APEX.
          * It is provided "as is" without warranty of any kind, either express or implied.
          * Use of this code is at your own risk. The authors and distributors accept no responsibility
          * for any consequences arising from its use.
          */
    
         // Keyboard shortcut for toggling "Comment Out" (Ctrl+Shift+/)
         const kbCommentToggle = 'Ctrl+Shift+/';
    
         /**
          * Checks if the script is running in the correct Oracle APEX environment.
          * Specifically, it verifies that the global 'apex' object exists and that
          * the current app and page IDs match the Page Designer (App 4000, Page 4500).
          * @returns {boolean} True if the environment is valid, false otherwise.
          */
         function isApexEnvironmentValid() {
             return typeof apex !== 'undefined' &&
                 parseInt(apex.env.APP_ID, 10) == 4000 &&
                 parseInt(apex.env.APP_PAGE_ID, 10) == 4500;
         }
    
         /**
          * Waits for APEX shortcuts to stabilize before registering custom shortcuts.
          * This prevents the custom shortcut from being overwritten by APEX's initialization process.
          * @param {Function} callback - Function to call once shortcuts have stabilized
          * @param {number} maxAttempts - Maximum number of attempts to check stability (default: 20)
          */
         function waitForShortcutsToStabilize(callback, maxAttempts = 20) {
             let attempts = 0;
             let lastCount = 0;
    
             const checkStability = () => {
                 // Only proceed if we're in a valid APEX environment
                 if (!isApexEnvironmentValid()) {
                     return;
                 }
    
                 const currentCount = apex.actions.listShortcuts().length;
    
                 if (currentCount === lastCount && currentCount > 30) {
                     // Count has stabilized and reached expected number
                     callback();
                 } else if (attempts < maxAttempts) {
                     lastCount = currentCount;
                     attempts++;
                     setTimeout(checkStability, 500);
                 } else {
                     callback();
                 }
             };
    
             checkStability();
         }
    
         /**
          * Object containing initialization logic for the Page Designer shortcut.
          * Adds a custom action and keyboard shortcut to toggle the "Comment Out" build option.
          */
         const XYZPageDesignerShortcutsx = {
             /**
              * Initializes the shortcut and action once when the script loads.
              * Only activates if the environment is valid (Page Designer open).
              */
             initialize: function() {
                 if (!isApexEnvironmentValid()) {
                     return;
                 }
    
                 // Register the custom action to toggle the "Comment Out" build option
                 apex.actions.add({
                     name: "xyz-toggle-comment-out",
                     label: "Toggle Comment Out",
                     /**
                      * Toggles the "Comment Out" build option for all selected components in the visible tree.
                      * This function:
                      * 1. Identifies the visible Page Designer tree.
                      * 2. Retrieves all selected nodes.
                      * 3. Resolves the corresponding component objects.
                      * 4. Toggles their build option between normal and "Comment Out".
                      */
                     action: function xyzToggleCommentOut() {
                         // CSS selectors for all possible Page Designer trees
                         const treeSelectors = [
                             '#PDrenderingTree:visible',
                             '#PDdynamicActionTree:visible',
                             '#PDprocessingTree:visible',
                             '#PDsharedCompTree:visible'
                         ];
    
                         // Find the currently visible tree using a combined selector
                         const pTree$ = $(treeSelectors.join(', '));
                         if (!pTree$.length) return; // Exit if no tree is visible
    
                         // Get selected nodes in the tree
                         const selectedNodes = pTree$.treeView("getSelectedNodes");
                         if (!selectedNodes?.length) return; // Exit if nothing is selected
    
                         /**
                          * Helper to retrieve the component object for a given tree node.
                          * Handles both jQuery and plain node objects.
                          * @param {Object} node - The selected tree node.
                          * @param {jQuery} tree$ - The jQuery tree object.
                          * @returns {Object|null} The component object or null if not found.
                          */
                         const getComponent = (node, tree$) => {
                             try {
                                 const nodeData = node instanceof jQuery
                                     ? tree$.treeView("getNodes", node)[0]
                                     : node;
    
                                 if (!nodeData?.data?.typeId || !nodeData?.data?.componentId) {
                                     return null;
                                 }
    
                                 // Retrieve component(s) by type and ID
                                 const components = window.pe?.getComponents(
                                     nodeData.data.typeId,
                                     { id: nodeData.data.componentId }
                                 );
    
                                 // Return the component if exactly one is found
                                 return components?.length === 1 ? components[0] : null;
                             } catch (error) {
                                 console.warn('Error retrieving component:', error);
                                 return null;
                             }
                         };
    
                         /**
                          * Toggles the build option property for all valid components.
                          * If the component is already commented out, it is restored; otherwise, it is commented out.
                          * Uses a transaction for batch updates and command history for undo/redo.
                          * @param {Array} components - Array of component objects to update.
                          */
                         const toggleBuildOption = (components) => {
                             if (!components?.length) return;
    
                             // Filter components that support the BUILD_OPTION property
                             const validComponents = components.filter(comp =>
                                 comp?.getProperty?.(window.pe.PROP.BUILD_OPTION)
                             );
    
                             if (!validComponents.length) return;
    
                             try {
                                 // Create a human-readable message for the transaction
                                 let actionText;
    
                                 if (validComponents.length === 1) {
                                     // Single component: include the component name
                                     const componentName = validComponents[0].getDisplayTitle();
                                     actionText = `Toggle Comment Out for ${componentName}`;
                                 } else {
                                     // Multiple components: use generic description
                                     const componentType = validComponents[0].typeId;
                                     const componentTypeName = window.model?.getComponentType?.(componentType)?.title?.singular || 'Component';
                                     actionText = `Toggle Comment Out for ${validComponents.length} ${componentTypeName}s`;
                                 }
    
                                 // Start the transaction with human-readable message
                                 const lTransaction = window.pe.transaction.start("", actionText);
    
                                 // Toggle the build option for each component
                                 validComponents.forEach(component => {
                                     const property = component.getProperty(window.pe.PROP.BUILD_OPTION);
                                     const currentValue = property.getValue();
    
                                     // Switch between "Comment Out" and normal state
                                     const newValue = currentValue === pageDesigner.COMMENTED_OUT_ID
                                         ? ""
                                         : pageDesigner.COMMENTED_OUT_ID;
    
                                     property.setValue(newValue);
                                 });
    
                                 // Commit the transaction to support undo/redo
                                 apex.commandHistory.execute(lTransaction);
                             } catch (error) {
                                 console.error('Error toggling build options:', error);
                             }
                         };
    
                         // Collect all valid component objects from selected nodes
                         const components = selectedNodes
                             .map(node => getComponent(node, pTree$))
                             .filter(Boolean);
    
                         toggleBuildOption(components);
                     }
                 });
    
                 // Register the keyboard shortcut for the action
                 apex.actions.addShortcut(kbCommentToggle, "xyz-toggle-comment-out");
             }
         };
    
         // Initialize the shortcut after APEX is loaded and shortcuts have stabilized.
         if (document.querySelector('#apex') || window.apex) {
             waitForShortcutsToStabilize(() => {
                 XYZPageDesignerShortcutsx.initialize();
             });
         } else {
             document.addEventListener('theme42ready', () => {
                 waitForShortcutsToStabilize(() => {
                     XYZPageDesignerShortcutsx.initialize();
                 });
             });
         }
     })();
    

This is my 3rd Tampermonkey mod; I’ll get them all tied together soon.

ENJOY

What’s the picture? Its Trig Point TP6037 - Snape - UK trig points are historic surveying pillars, once vital for accurate mapping and now cherished landmarks for outdoor enthusiasts and a testament to the nation's cartographic heritage - Visit Yorkshire!

0
Subscribe to my newsletter

Read articles from Matt Mulvaney directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Matt Mulvaney
Matt Mulvaney

With around 20 years on the job, Matt is one of the most experienced software developers at Pretius. He likes meeting new people, traveling to conferences, and working on different projects. He’s also a big sports fan (regularly watches Leeds United, Formula 1, and boxing), and not just as a spectator – he often starts his days on a mountain bike, to tune his mind.