{"id":23547870,"url":"https://github.com/dani3lsun/apex-app-desktop","last_synced_at":"2025-08-09T21:18:30.915Z","repository":{"id":53066592,"uuid":"49778415","full_name":"Dani3lSun/apex-app-desktop","owner":"Dani3lSun","description":"APEX App as Desktop Application using Github Electron","archived":false,"fork":false,"pushed_at":"2021-04-08T08:49:57.000Z","size":456,"stargazers_count":10,"open_issues_count":3,"forks_count":7,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-04-24T06:13:14.312Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Dani3lSun.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-01-16T15:18:42.000Z","updated_at":"2021-11-14T17:34:37.000Z","dependencies_parsed_at":"2022-09-08T22:02:57.493Z","dependency_job_id":null,"html_url":"https://github.com/Dani3lSun/apex-app-desktop","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Dani3lSun%2Fapex-app-desktop","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Dani3lSun%2Fapex-app-desktop/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Dani3lSun%2Fapex-app-desktop/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Dani3lSun%2Fapex-app-desktop/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Dani3lSun","download_url":"https://codeload.github.com/Dani3lSun/apex-app-desktop/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250573356,"owners_count":21452352,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-12-26T09:19:39.386Z","updated_at":"2025-04-24T06:13:19.970Z","avatar_url":"https://github.com/Dani3lSun.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"**Table of Contents**\n\n- [APEX Desktop Application using Github Electron](#apex-desktop-application-using-github-electron)\n\t- [Description](#description)\n\t- [Helpful links](#helpful-links)\n\t- [Successful](#successful)\n\t- [Problems](#problems)\n\t- [Changelog](#changelog)\n\t- [Installation](#installation)\n\t\t- [Preparations](#preparations)\n\t\t\t- [Install Node.js](#install-nodejs)\n\t\t- [Electron App](#electron-app)\n\t\t\t- [package.json](#packagejson)\n\t\t\t- [Install Electron into app folder and globally](#install-electron-into-app-folder-and-globally)\n\t\t\t- [main.js](#mainjs)\n\t\t\t- [index.html](#indexhtml)\n\t\t\t- [apexutils.js](#apexutilsjs)\n\t\t\t- [electronapex.js](#electronapexjs)\n\t\t\t- [Starting the App](#starting-the-app)\n\t\t\t- [Bundle the app into a real Application](#bundle-the-app-into-a-real-application)\n\t- [Sample functions](#sample-functions)\n\t  - [Desktop notifications](#desktop-notifications)\n\t  - [File open](#file-open)\n\t  - [APEX Authorization Scheme](#apex-authorization-scheme)\n\t- [License](#license)\n\t- [Preview](#preview)\n\n# APEX Desktop Application using Github Electron\n## Description\nThis is not a ready to use software, but much more a showcase and tutorial how to build desktop APEX apps using Electron from Github...\n\nThis showcase describes the steps I´ve been done (maybe wrong ones included, too :) ), there I had success and there a had problems with.\n\n\n## Helpful Links\n- [Electron](http://electron.atom.io)\n- [Electron on Github](https://github.com/atom/electron)\n- [Node.js](https://nodejs.org) (required on your Machine to install all other things)\n- [Electron API docs](https://github.com/atom/electron/tree/master/docs/api)\n- [Electron Packager](https://github.com/maxogden/electron-packager) (Packaging Apps into container to rollout)\n- [Electron Tutorial](http://ryanfrench.co/2015/05/02/harmonic_tutorial_1.html) (Sample App tutorial)\n\n\n## Successful\n- Embed APEX App into electron using webview\n- Copy\u0026Paste support with App menu (menubar)\n- Open links (href / window.open) inside of electron app\n- Desktop Notifications\n- React on console.log events from APEX app and do things with this (for example opening a local file)\n- App style without border and titlebar\n- fullscreen mode\n\n## Problems\n- React on electron functions triggered from APEX app (nodeintegration and webview-preload functions in webviews does not work for APEX app (maybe because of the changed URL if page gets rendered))\n- Session state (on a hard refresh, app returns to login page (possible solution: on close get last webview URL and save it))\n- Closing to OS X dock: Reopening the app shows login page, instead of content from before (minimize works well)\n\n\n## Changelog\n\n#### First testings...\n\n\n## Installation\n### Preparations\n#### Install Node.js\nIt is required to have a up and running Node.js installation on your local development machine.\nEither install it using a package manager, or download the latest version from Node.js homepage...for example:\n- Ubuntu:\n```\napt-get install nodejs\napt-get install npm\n```\n\n- Mac OS X (Homebrew):\n```\nbrew install nodejs\n```\n\n- Windows:\nDownload and install it from Nodejs homepage\n\nnpm is the package manager for node applications. Thus electron is based on node, npm is working all the same...\n\n### Electron App\n#### package.json\n**not required if you decide to download this repository!**\n\nThe first step is to generate the package.json file\n```\nmkdir apex-app-desktop\ncd apex-app-desktop\nnpm init\n```\n\nThe resulting json file could look like this:\n```json\n{\n  \"name\": \"apex-app-desktop\",\n  \"version\": \"1.0.0\",\n  \"description\": \"APEX Desktop Application using Github Electron\",\n  \"main\": \"main.js\",\n  \"scripts\": {\n    \"test\": \"echo \\\"Error: no test specified\\\" \u0026\u0026 exit 1\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/Dani3lSun/apex-app-desktop.git\"\n  },\n  \"author\": \"Daniel Hochleitner\",\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/Dani3lSun/apex-app-desktop/issues\"\n  },\n  \"homepage\": \"https://github.com/Dani3lSun/apex-app-desktop#readme\",\n  \"devDependencies\": {\n    \"electron-prebuilt\": \"^1.1.0\"\n  }\n}\n```\n\nNow we have to add **\"start\": \"electron .\"** to the scripts tag of the json file.\n```json\n{\n  \"name\": \"apex-app-desktop\",\n  \"version\": \"1.0.0\",\n  \"description\": \"APEX Desktop Application using Github Electron\",\n  \"main\": \"main.js\",\n  \"scripts\": {\n    \"test\": \"echo \\\"Error: no test specified\\\" \u0026\u0026 exit 1\",\n    \"start\": \"electron .\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/Dani3lSun/apex-app-desktop.git\"\n  },\n  \"author\": \"Daniel Hochleitner\",\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/Dani3lSun/apex-app-desktop/issues\"\n  },\n  \"homepage\": \"https://github.com/Dani3lSun/apex-app-desktop#readme\",\n  \"devDependencies\": {\n    \"electron-prebuilt\": \"^1.1.0\"\n  }\n}\n```\n\n#### Install Electron into app folder and globally\n**not required if you decide to download this repository!**\n\n```\nnpm i electron-prebuilt --save-dev #for local app folder\nnpm i -g electron-prebuilt #globally for using commandline\n```\n\n#### main.js\n**not required if you decide to download this repository!**\n\nThis is the main javascript file for the app (also mentioned in the package.json file). It will initialize the whole application.\nHere you can open new windows, react on app events or create menus...If you decide to do all by yourself and not copying the whole repo, be sure to include the images (such as tray.png) or comment out that lines using referenced images.\n\n```javascript\n// Libraries used in the app\nvar app = require('electron').app;\nvar BrowserWindow = require('electron').BrowserWindow;\nvar appMenu = require('electron').Menu;\nvar appTray = require('electron').Tray;\nvar path = require('path');\nconst {\n    crashReporter\n} = require('electron');\napp.commandLine.appendSwitch('ignore-certificate-errors', 'true');\n// Crash Reporter (Optional)\ncrashReporter.start({\n    productName: 'APEX Plugins',\n    companyName: 'Daniel Hochleitner',\n    submitURL: 'https://github.com/Dani3lSun/apex-app-desktop/issues',\n    autoSubmit: false\n});\n// Init mainWindow and Tray\nvar mainWindow = null;\nvar appIcon = null;\n// Kill the app when all windows are closed\napp.on('window-all-closed', function() {\n    if (process.platform != 'darwin') {\n        app.quit();\n    }\n});\n// App is loaded\napp.on('ready', function() {\n    // Tray icon\n    // template\n    var trayTemplate = [{\n        label: \"About Application\",\n        selector: \"orderFrontStandardAboutPanel:\"\n    }, {\n        label: \"Quit\",\n        click: function() {\n            app.quit();\n        }\n    }];\n    // set tray icon with context menu\n    appIcon = new appTray(path.join(__dirname, 'img/tray.png'));\n    appIcon.setContextMenu(appMenu.buildFromTemplate(trayTemplate));\n    // Create the main window for the app\n    mainWindow = new BrowserWindow({\n        \"width\": 1280, // init width\n        \"height\": 800, // init height\n        \"minWidth\": 1024,\n        \"minHeight\": 800,\n        \"resizable\": true,\n        \"useContentSize\": true,\n        //\"transparent\": true, // better look in OSX\n        \"titleBarStyle\": \"hidden-inset\", // better look in OSX\n        \"icon\": path.join(__dirname, 'img/tray.png') // app icon (for linux build)\n    });\n    // Load in content with webview to APEX app\n    mainWindow.loadURL('file://' + __dirname + '/index.html');\n    // Ensure that garbage collection occurs when the window is closed\n    mainWindow.on('closed', function(e) {\n        mainWindow = null;\n    });\n    // Create the Application main menu (required for Copy\u0026Paste Support)\n    // Application menu\n    var menuTemplate = [{\n        label: \"Application\",\n        submenu: [{\n                label: \"About Application\",\n                selector: \"orderFrontStandardAboutPanel:\"\n            }, {\n                type: \"separator\"\n            }, {\n                label: \"Quit\",\n                accelerator: \"Command+Q\",\n                click: function() {\n                    app.quit();\n                }\n            }]\n            // Edit menu\n    }, {\n        label: \"Edit\",\n        submenu: [{\n            label: \"Undo\",\n            accelerator: \"CmdOrCtrl+Z\",\n            selector: \"undo:\"\n        }, {\n            label: \"Redo\",\n            accelerator: \"Shift+CmdOrCtrl+Z\",\n            selector: \"redo:\"\n        }, {\n            type: \"separator\"\n        }, {\n            label: \"Cut\",\n            accelerator: \"CmdOrCtrl+X\",\n            selector: \"cut:\"\n        }, {\n            label: \"Copy\",\n            accelerator: \"CmdOrCtrl+C\",\n            selector: \"copy:\"\n        }, {\n            label: \"Paste\",\n            accelerator: \"CmdOrCtrl+V\",\n            selector: \"paste:\"\n        }, {\n            label: \"Select All\",\n            accelerator: \"CmdOrCtrl+A\",\n            selector: \"selectAll:\"\n        }]\n    }, {\n        // View menu\n        label: 'View',\n        submenu: [{\n            label: 'Reload',\n            accelerator: 'CmdOrCtrl+R',\n            click: function(item, focusedWindow) {\n                if (focusedWindow)\n                    focusedWindow.reload();\n            }\n        }, {\n            label: 'Toggle Full Screen',\n            accelerator: (function() {\n                if (process.platform == 'darwin')\n                    return 'Ctrl+Command+F';\n                else\n                    return 'F11';\n            })(),\n            click: function(item, focusedWindow) {\n                if (focusedWindow)\n                    focusedWindow.setFullScreen(!focusedWindow.isFullScreen());\n            }\n        }, {\n            label: 'Toggle Developer Tools',\n            accelerator: (function() {\n                if (process.platform == 'darwin')\n                    return 'Alt+Command+I';\n                else\n                    return 'Ctrl+Shift+I';\n            })(),\n            click: function(item, focusedWindow) {\n                if (focusedWindow)\n                    focusedWindow.toggleDevTools();\n            }\n        }]\n    }];\n    // set menu with options from above\n    appMenu.setApplicationMenu(appMenu.buildFromTemplate(menuTemplate));\n});\n// Create the main window for the app when App is reopened from OSX Dock\napp.on('activate', function(e, hasVisibleWindows) {\n    if (mainWindow === null) {\n        mainWindow = new BrowserWindow({\n            \"width\": 1280, //init width\n            \"height\": 800, // init height\n            \"minWidth\": 1024,\n            \"minHeight\": 800,\n            \"resizable\": true,\n            \"useContentSize\": true,\n            \"transparent\": true, // better look in OSX\n            \"titleBarStyle\": \"hidden-inset\", // better look in OSX\n            \"icon\": path.join(__dirname, 'img/tray.png') // app icon (for linux build)\n        });\n        mainWindow.loadURL('file://' + __dirname + '/index.html');\n        mainWindow.on('closed', function() {\n            mainWindow = null;\n        });\n    }\n});\n```\n\n#### index.html\n**not required if you decide to download this repository!**\n\nThis file is opened from main.js. It includes the webview element which has as source the APEX URL.\nAlso this file has js functions that get triggered from webview events.\n\n```html\n\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n\n\u003chead\u003e\n  \u003ctitle\u003eAPEX Plugins\u003c/title\u003e\n\u003c/head\u003e\n\n\u003cbody style=\"overflow: hidden\"\u003e\n  \u003cstyle\u003e\n    html,\n    body {\n      width: 100%;\n      height: 100%;\n      margin: 0;\n      padding: 0;\n    }\n\n    #apex-plugin-app {\n      width: 100%;\n      height: 100%;\n    }\n  \u003c/style\u003e\n  \u003c!-- Webview\n  Parameter:\n  autosize=\"on\",\n  allowpopups (allow new windows created inside webview),\n  minwidth,\n  minheight,\n  useragent (UserAgent inside the webview - used for authorization scheme in APEX)\n  --\u003e\n  \u003cwebview id=\"apex-plugin-app\" src=\"https://apex.oracle.com/pls/apex/f?p=APEXPLUGIN\" autosize=\"on\" minwidth=\"1024\" minheight=\"800\" allowpopups plugins useragent=\"APEXDESKTOP\"\u003e\u003c/webview\u003e\n  \u003c!-- Webview JS --\u003e\n  \u003cscript\u003e\n    var webview = document.getElementById(\"apex-plugin-app\");\n    var apexutils = require('./apexutils');\n    // open href links in electron app\n    webview.addEventListener('new-window', function(e) {\n      window.open(e.url, e.frameName, \"resizable,scrollbars,status\");\n    });\n    // console message events (functions wrapped into console messages)\n    webview.addEventListener('console-message', function(e) {\n      var stringDevider = \"::\";\n      var position = e.message.indexOf(stringDevider);\n      var messageType = e.message.substr(0, position);\n      // open local file\n      if (messageType === 'open-file') {\n        apexutils.openLocalFile(e.message);\n      }\n    });\n  \u003c/script\u003e\n\u003c/body\u003e\n\n\u003c/html\u003e\n```\n\n#### apexutil.js\n**not required if you decide to download this repository!**\n\nThis file contains functions for APEX triggered events in electron opponent for electronapex.js functions inside APEX.\nGenerally spoken: All functions that are required by electron for the APEX js functions. Wrapped into a own file...\n\n```javascript\n// Functions for APEX triggered events in electron\n// opponent for electronapex.js functions inside APEX\n//\n\n// electron functions\nelectron = window.require('electron');\n\nvar stringDevider = \"::\";\n\nmodule.exports = {\n  // opens a local file from consol.log text from APEX\n  openLocalFile: function(consoleString) {\n    var position = consoleString.indexOf(stringDevider) + 2;\n    path = consoleString.substr(position);\n    var shell = electron.shell;\n    shell.openItem(path);\n  }\n};\n```\n\n#### electronapex.js\n**not required if you decide to download this repository!**\n\nThis file contains js functions used in the APEX app itself. For example notifications or opening files.\nFile open uses console.log to trigger the event on electron side (not nice but works...hopefully someone was here more successful than I!).\n\n**Upload this file to your APEX application or workspace!**\n\n```javascript\n// Functions for electron used in APEX\n// functions often uses wrapper around console.log (webview.addEventListener('console-message' gets these events))\n// upload to APEX App!\n\nvar stringDevider = \"::\";\n// global namespace\nvar electronapex = {\n  // open file\n  openFile: function(path) {\n    var type = 'open-file';\n    console.log(type + stringDevider + path);\n  },\n  // check if notifications are enabled\n  notifyCheck: function() {\n    // Let's check if the browser supports notifications\n    if (!(\"Notification\" in window)) {\n      alert(\"This browser does not support system notifications\");\n    }\n    // Let's check whether notification permissions have already been granted\n    else if (Notification.permission === \"granted\") {\n      // If it's okay let's create a notification\n      var notification = new Notification(\"You already have granted Notification permissions, great!:)\");\n    }\n    // Otherwise, we need to ask the user for permission\n    else if (Notification.permission !== 'denied') {\n      Notification.requestPermission(function(permission) {\n        // If the user accepts, let's create a notification\n        if (permission === \"granted\") {\n          var notification = new Notification(\"Now Notifications should work!\");\n        }\n      });\n    }\n  },\n  // send a notification\n  doNotify: function(text) {\n    var notification = new Notification(text);\n  }\n};\n```\n\n#### Starting the App\n- If you cloned this repository than you have to do this:\n```\nnpm install #installs all dependencies from package.json\nnpm start\n```\n\n- If you did all by yourself:\n```\nnpm start\n```\n\n#### Bundle the app into a real Application\nTo create a real application from your development app folder you need the \"electron-packager\" package installed globally:\n```\nnpm install electron-packager -g\n```\n\nNow you can create the application on commandline with:\n```\ncd apex-app-desktop\nelectron-packager . \"APEX Plugins\" --platform=darwin --arch=x64 --version=0.36.4 --app-version=1.0.0 --icon img/app.icns\n```\nThis command creates a \"APEX Plugins.app\" for Mac OS X (darwin) in 64bit, optionally takes the image from img folder as application icon. \"--version\" is the version string for electron, can be found in your package.json file!\n\nfor Linux:\n```\ncd apex-app-desktop\nelectron-packager . \"APEX Plugins\" --platform=linux --arch=x64 --version=0.36.4 --app-version=1.0.0\n```\n\nand Windows:\n```\ncd apex-app-desktop\nelectron-packager . \"APEX Plugins\" --platform=win32 --arch=x64 --version=0.36.4 --app-version=1.0.0 --icon img/app.ico\n```\n\nIf you build a Windows Binary on other platforms be sure to install \"wine\" before!\n\n## Sample functions\n### Desktop notifications\nFor this functionality I created a dynamic action (on button click) with this code:\n```javascript\nvar text = $v('P15_NOTIFY_TEXT'); //read text from APEX item\nelectronapex.doNotify(text); //use function from electronapex.js\n```\n\nUses the Browser Notification API [MDN](https://developer.mozilla.org/de/docs/Web/API/notification)\n\n### File open\nHere I wrapped the file path + type \"open-file\" into console.log. On electron side I react on this with \"webview.addEventListener('console-message')\".\nI created a dynamic action (on button click) with this code:\n```javascript\nvar path = $v('P15_FILE_PATH'); //read path from APEX item\nelectronapex.openFile(path); //use function from electronapex.js\n```\n\n### APEX Authorization Scheme\nThus the webview is created with the option (useragent=\"APEXDESKTOP\" - index.html),\nwe can use this on APEX side to create a authorization scheme which is true or false based on the user agent of the \"browser\".\n\nName: Is Electron User Agent\n\nMessage: This page is only visible inside a electron desktop app\n\n```language-sql\nDECLARE\n  l_user_agent VARCHAR2(500);\nBEGIN\n  l_user_agent := owa_util.get_cgi_env('HTTP_USER_AGENT');\n  IF l_user_agent = 'APEXDESKTOP' THEN\n    RETURN TRUE;\n  ELSE\n    RETURN FALSE;\n  END IF;\nEND;\n```\n\n\n## License\nThis software is under **MIT License**.\n\n[LICENSE](https://github.com/Dani3lSun/apex-app-desktop/blob/master/LICENSE)\n\n## Preview\n![](https://github.com/Dani3lSun/apex-app-desktop/blob/master/preview.gif)\n---\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdani3lsun%2Fapex-app-desktop","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdani3lsun%2Fapex-app-desktop","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdani3lsun%2Fapex-app-desktop/lists"}