I ran into this problem. but instead i figured out a way to dynamically load modules in the user interface. Which you are able to interact within 1 or more files.
fxmanifest.lua
-
minimum contents to follow
fx_version 'cerulean' game 'gta5' author 'Sm1Ly ~ 5 Pixel' description 'dynamically load modules' version '1.0.0' ui_page 'view/index.html' files { 'view/index.html', 'view/modules/**/*.html', 'view/modules/**/*.css', 'view/modules/**/*.js' } server_scripts { 'server/config.lua', -- (optional to store paths) 'server/main.lua' } client_script 'client/main.lua'
Loading js/css content dynamically
-
scandir function (in lua) that is run serverside
function scandir(directory) -- search a givin path local i, t, popen = 0, {}, io.popen -- on a windows server local pfile = popen('dir "'..directory..'" /b /ad') -- on a linux server local pfile = popen('ls -a "'..directory..'"') -- loop trough the returned files for filename in pfile:lines() do -- the if statement isn't nessasery but usefull if you got a template module like me. if filename ~= 'template' then i = i + 1 t[i] = filename end end pfile:close() -- return the table with the names of the modules return t end
-
Trigger this function underneath and asing to a local variable
- specify a path with escaped backslashed otherwise it wont work, so do not use normal slashes!!!
local path = 'C:\\*server_folder*\\resources\\*resource_name*\\view\\modules' local modules = scandir(path)
-
Trigger this function whitin an event that is called by the client
- return the table with module names to the client
RegisterServerEvent('module-loader:server:getModules') AddEventHandler('module-loader:server:getModules', function() local src = source TriggerClientEvent('module-loader:client:sendModules', src, modules) end)
-
send te information form the client to the user interface
RegisterNetEvent('module-loader:client:sendModules') AddEventHandler('module-loader:client:sendModules', function(modules) -- setup an 'SendNUIMessage' function to, send the names to the client their webpage SendNUIMessage{ action = 'setModules', modules = modules } end)
-
Place a script tag the head tag after your prefferd librarys are loaded do not load any other script you want to use
- if you wonder why maybe you should read this first → How the browser renders a web page
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- jQuery --> <script src="nui://game/ui/jquery.js" type="text/javascript"></script> <script> // write the upcomming code here </script> <title>Dynamic loading</title> </head> <body> </body> </html>
-
Add a function to the script tag which loads the contents of the modules
let loadModule = ({ js, css }) => { // create script element for the js file let script = document.createElement("script"); // set script type for older browsers (not required) script.type = "text/javascript"; // add the given path to the src attribute script.src = js; // add element to head document.getElementsByTagName("head")[0].append(script); // do the same for the css file but with a link element // within conditional statement in case you just want to load a js file if (css != null) { let link = document.createElement("link"); // rel is required within a link element when you load css files, // unlike type within a script element link.rel = "stylesheet"; // add the given path to the href attribute link.href = css; // add element to head document.getElementsByTagName("head")[0].append(link); } };
-
Add a event listener to catch the message
- i’m using a switch case statement for keybinding
window.addEventListener('message', (ev) => { switch (ev.data.action) { case 'setModules'; // switch on the action send const modules = ev.data.modules; // loop through the recieved names for (let i = 0; i < modules.length; i++) { const name = modules[i]; /* we can send the paths within any table because, the fuction uses the table names as parameters */ const path = { js: `./modules/${name}/js/app.js`, css: `./modules/${name}/css/app.css` } // call the loadModule function and pass the path table loadModule(path); } break; } });
Loading HTML5 dynamically
if you want switch content and do this in a smooth way its recommended to animate te moduleContainer div out and unload the content, then load the new content and animate it back this can eigther be acomplished by a fade or slide animation.
It would be better to use 2 containers to load modules, if there isnt any form main content for the moduleContainer to overlap.
-
Add the following events to the server file
local tokens = tokens or {} RegisterServerEvent('module-loader:server:generateToken') AddEventHandler('module-loader:server:generateToken', function(callback) local src = source local newToken = 'T'..tostring(math.random(1111111, 9999999)) tokens[src] = newToken callback(newToken) end)
-
Add the following thread to the client file
local sleep = 0 Citizen.CreateThread(function() repeat if IsControlJustReleased(0, * control key number) then sleep = (1000 * 60) * 0.5 -- 30 seconds TriggerServerEvent('module-loader:server:generateToken', function(token) SendNUIMessage { action = 'loadModuleContent', moduleName = '*your_module_name*', token = token } end) end Citizen.Wait(sleep) until false end)
-
Create a container to load the content
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- jQuery --> <script src="nui://game/ui/jquery.js" type="text/javascript"></script> <script> // code written in 'Loading js/css content dynamically' </script> <title>Dynamic loading</title> </head> <body> <div id="moduleContainer"></div> </body> </html>
-
Add the following variable above te event listener
let container = document.querySelector('.module'); let token;
-
Add the followning case in message event listener
case 'loadModuleContent'; token = ev.data.token; const name = ev.data.name; const path = `./modules/${name}/css/app.css`; container.classList.add(`module-${name}`); fetch(path) .then(data => data.text()) .then(html => container.innerHTML = html); break;
-
Add a key down, up or press event listener to unload the content
$(document).on('keydown', (e) => { switch (e.keyCode) { case 27: // esc container.innerHTML = ''; container.classList.remove(`module-${name}`); $.post('https://module-loader/close', token); break; } });
-
Add a NUI callback to the client file under neath the thread which sets the sleeper back to 0
RegisterNUICallback('close', function(token) TriggerServerEvent('module-loader:server:checkToken', token) sleep = 0 end)
-
Add event to the server file to check the token given back by the client
RegisterServerEvent('module-loader:server:checkToken') AddEventHandler('module-loader:server:checkToken', function(token) local src = source local newToken = 'T'..tostring(math.random(1111111, 9999999)) if token ~= tokens[src] then -- yea 99.9999% sure that hes fucking with the devtools tokens[src] = newToken else tokens[src] = newToken end end)