1

I'm making an online Python editor. And when I'm trying to print the console's output to the HTML body using document.write(), it is showing undefined

Code :

var py
async function ld() { 
        py = await loadPyodide({
        indexURL: "https://cdn.jsdelivr.net/pyodide/v0.19.1/full/"
    })
}

ld()

function run() { 
document.write(py.runPython(document.getElementById("c").value))
    
}
/*no styles*/
<!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">
    <title>WIDLE - Web IDLE</title>
    <script src="https://cdn.jsdelivr.net/pyodide/v0.19.1/full/pyodide.js"></script>
</head>
<body>
    <textarea id="c" ></textarea>
    <button onclick="run()">run</button>
</body>
</html>

By the way, what I want is, to show the console output in HTML as the StackSnippets do.

lunix
  • 151
  • 1
  • 14
  • You should not be using `document.write`. It's very, very obsolete and breaks easily. To add text to a page you should append to an element's `textContent` property instead. – Dai Mar 05 '22 at 09:36
  • @Dai _"Nowhere in your code are you actually using the console API though"_ - OP want's the output of `py.runPython(...)` not `console.log()` – Andreas Mar 05 '22 at 09:40
  • Yes that is it. – lunix Mar 05 '22 at 09:40
  • Search for Pyodide here on SO and you will find, e.g., [this question](https://stackoverflow.com/questions/56583696/how-to-redirect-render-pyodide-output-in-browser). Not a 100% match (because OP asks multiple questions in there) but part of it will also work for you. – Andreas Mar 05 '22 at 09:44
  • Or their [documentation](https://pyodide.org/en/stable/usage/api/js-api.html): _"`config.stdout` (`undefined|function`) – Override the standard output callback. Default: `undefined`"_ – Andreas Mar 05 '22 at 09:46
  • BTW, I get HTTP 403 error when I try to load `https://cdn.jsdelivr.net/pyodide/v0.19.1/full/` - [other people are have technical issues with jsDelivr-hosted Pyodide too](https://github.com/pyodide/pyodide/issues/952) (which is an organization that I just can't trust with hosting scripts: their business model isn't sustainable and they could XSS the entire web if they wanted to at this point). – Dai Mar 05 '22 at 11:46

2 Answers2

1

As per @Andreas' comment-reply, you'll need to use the config parameter of loadPyodide to set a callback function for config.stdout (and also config.stderr).

Pyodide's online documentation doesn't show the exact signature of the stdout callback, but fortunately they do have a TypeScript-annotated file in their github repo which shows that both stdout and stderr are (string) => void.

So you can do something like this:

Disclaimers:

  • I don't have any prior experience with Pyodide.
  • My personal style preference is to use named functions, including global-scoped functions, though I know some people are now quite religious about not doing that, eh, whatever floats yer boat...
  • I added defer to the <script>.
  • The jsDelivr-hosted pyodide/v0.19.1 script does not load for me and I get a HTTP 403 error. Other people are reporting issues too.
    • I cannot recommend that anyone use jsDelivr or any other JS hosting service that doesn't have a plausible business model and/or actual relationship with the scripts they're hosting or the sites that are using them. And more reasons.
      • Even if there's nothing dodgy going on at jsDelivr, and if also even if you're using <script integrity=""> correctly, no CDN website will be around forever, after which your site will just break. Just host scripts yourself or use your own CDN account that you are responsible for.

// Using `var` promotes these variables to window properties which makes it easier to access them in certain situations.
var py             = null;
var pythonOutputUL = null;

// The pyodide.js <script> is marked with `defer` so it won't block the page load, which also means it can't be used until the DOMContentLoaded event.

function pythonConsoleStdOut( text ) {
    console.log( "Python stdout: %o", text );
    appendPythonOutputMessage( 'stdout', text );
}

function pythonConsoleStdErr( text ) {
    console.error( "Python stderr: %o", text );
    appendPythonOutputMessage( 'stderr', text );
}

function appendPythonOutputMessage( className, text ) {
    const messageLI = document.createElement('li');
    messageLI.classList.add( className );
    messageLI.dataset['dt'] = new Date().toString();
    messageLI.textContent = text;
    window.pythonOutputUL.appendChild( messageLI );
}

window.addEventListener( 'DOMContentLoaded', loadPython );

const pyodideConfig = {
    indexURL: "https://cdn.jsdelivr.net/pyodide/v0.19.1/full/",
    stdout  : pythonConsoleStdOut,
    stderr  : pythonConsoleStdErr
};

async function loadPython() { 
    window.pythonOutputUL = document.getElementById( 'pythonOutputUL' );
    try {
        window.py = await loadPyodide( pyodideConfig );
    }
    catch( err ) {
        debugger;
        console.error( "Pyodide's loadPyodide threw an error: %o", err );
        appendPythonOutputMessage( "Failed to load Pyodide: " + ( err || '' ).toString() );
        return;
    }

    document.getElementById( 'loadingMessage' ).hidden   = true;
    document.getElementById( 'codeTextArea'   ).disabled = false;
    document.getElementById( 'runButton'      ).disabled = false;
}

async function run( e ) { 
    if( !window.py ) throw new Error( "Pyodide isn't loaded yet." );

    const pythonScript = document.getElementById( "codeTextArea" ).value;

    const button = e.currentTarget;
    button.disabled = true;
    button.textContent = "Running...";
    try {
        await window.py.runPythonAsync( /*code:*/ pythonScript, /*globals:*/ { } );
        button.textContent = "Run";
    }
    catch( err ) {
//          debugger; // Uncomment at your discretion.
        console.error( err );
        button.textContent = "Error. See browser console.";
    }
    finally {
        button.disabled = false;
    }
}

function onPyodideScriptLoadError( event ) {
    debugger;
    console.error( "Failed to load Pyodide.js: %o", event );
    appendPythonOutputMessage( "Failed to load Pyodide.js. See browser console." );
}
#pythonOutputUL {
    font-family: monospace;
}
#pythonOutputUL > li[data-dt]::before {
    content: "[" attr(data-dt) "] ";
    display: inline;
}

#pythonOutputUL > li.stdout {
    border-top: 1px solid #ccc;
}

#pythonOutputUL > li.stderr {
    border-top: 1px solid #red;
}
<!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">
    <title>WIDLE - Web IDLE</title>
    <script src="https://cdn.jsdelivr.net/pyodide/v0.19.1/full/pyodide.js" defer onerror="onPyodideScriptLoadError(event)"></script>
</head>
<body>
    <noscript>Please enable JS to run</noscript>

    <p id="loadingMessage">Loading, please wait...</p>
    <textarea id="codeTextArea" disabled></textarea>
    <button id="runButton" onclick="run(event)" disabled>Run</button>
 
    <ul id="pythonOutputUL">
    </ul>

</body>
</html>
Dai
  • 141,631
  • 28
  • 261
  • 374
0

The simplest way to achieve this is by remaking console.log

console.log = function(message){
document.getElementById("div").innerHTML += message
}

And anything like py.runPython() will produce proper output

lunix
  • 151
  • 1
  • 14