[how-to] Loading nui html/css/js modules dynamically

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


  1. 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
    

  2. 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)
    

  3. 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)
    

  4. 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)
    

  5. Place a script tag the head tag after your prefferd librarys are loaded do not load any other script you want to use

    <!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>
    

  6. 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);
    
       }
    
    };
    

  7. 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.


  1. 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)
    

  2. 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)
    

    * control key number


  3. 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>
    

  4. Add the following variable above te event listener

    let container = document.querySelector('.module');
    let token;
    

  5. 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;
    

  6. 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;
       }
    });
    

  7. 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)
    

  8. 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)
    

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.