-1

I'm trying to create a html/css/javascript online editor for people to try small code and stuff like that. it was actually pretty simple.. you create 2 div's one is a textarea id="input" and the other is a div id="output" and you create a js function to push the content of the input into the output either by pressing a button or make it live by running the function every 2 seconds or something I ran the code and it worked fine for html and css but once i tried to create a function inside <script> tags inside that textarea it didn't actually run.. like it doesn't exist.. I can run small functions using a button if i wrote the actions inside the onclick but to define a function and then run it by that button is not working

window.setInterval(function(){
    var y = document.getElementById("automatic").checked
    if (y==1){
        editorFunction()
    }
}, 1000);

function editorFunction(){
    var x = document.getElementById("input").value;
    document.getElementById("output").innerHTML = x;
}

My question is.. is there a way around this ? i really want the page to stay static.. i don't want to have to send the content to a database and make a new form and render it on the page.. i wanna keep it as simple as possible Someone asked for the html.. I don't think it's important but here it is:

<body id="body">
<section class="container">
    <div class="row">
        <div class="col-sm-6">
            <textarea class="col-sm-12" id="input"></textarea>
        </div>
        <div class="col-sm-6">
            <div class="col-sm-12" id="output"></div>
        </div>
    </div>
</section>
<section>
    <div class="row">
        <div class="col-sm-3"></div>
        <div class="col-sm-6">
            <button onclick="editorFunction()">RUN</button>
            <input type="checkbox" id="automatic" checked>Submit the changes LIVE</input>
        </div>
    </div>
</section>
</body>

eval() will only work when i want to execute an action.. i can't really define a function that will stay for later use

  • 1
    Are you wrapping your function in ` – Rob M. Sep 01 '16 at 23:09
  • 1
    Can you please post your HTML code as well? – Neobugu Sep 01 '16 at 23:10
  • Of course i am, Rob – Mahmoud Moustafa Sep 01 '16 at 23:12
  • I assume the value that you are getting is a string. To make it run, you could use `eval()`, but I would be reeeeeally careful with this and use it as a last resort! https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval – Denis Ivanov Sep 01 '16 at 23:13
  • This could solve the issue if i had a textarea for javascript only.. in my situation.. i'm trying to use the text area for html code.. containing – Mahmoud Moustafa Sep 01 '16 at 23:25
  • Possible duplicate of [Can scripts be inserted with innerHTML?](http://stackoverflow.com/questions/1197575/can-scripts-be-inserted-with-innerhtml) – Makyen Sep 01 '16 at 23:33
  • You will need to specially handle the ` – Makyen Sep 01 '16 at 23:37
  • 2
    I would not use an interval timer to copy what the user is entering. Just the button which the user can click when they are ready to try their code. Using an interval timer could result in some screwy output which might distract the user from editing. On the other hand, you intend to let the user be in charge of using auto updates. So, I guess that is not too bad. – Makyen Sep 01 '16 at 23:41
  • 1
    I agree with @Makyen. If the automatic mode is a must, there should be at least another timing mechanism: Do not perform changes by regular time intervals, but only when the user stops entering text for 3 seconds, for example. – Little Santi Sep 01 '16 at 23:44
  • I'd +1 @LittleSanti's comment, but I'm out of votes for today. Using a timer to only perform updates after the user stops making changes is a good way to implement auto-updates. For instance, I created a Firefox extension to force Stack Overflow to do exactly that when I am editing answers & questions. Without that extension, Stack Overflow can, from time to time, be a *real pain* to edit because they update on *every* key (actually, SO's event handler code gets called 3 times for every key pressed) which can get slow in some situations, particularly with one, or more, snippets. – Makyen Sep 01 '16 at 23:53
  • I'm willing to forget about the automatic mode if i found a solution for this.. but to be honest.. if the user is only using html and css.. the automatic mode actually feels amazing – Mahmoud Moustafa Sep 01 '16 at 23:58
  • @MahmoudMoustafa, Having tried the live update, I would say it depends on how large of a project you expect people to write with this. If you were just implementing HTML, I would say it might be reasonable. But, with scripts, I would have the default be to use it. – Makyen Sep 02 '16 at 01:04

2 Answers2

1

Based on Denis Ivanov's suggestion, I propose this solution:

function editorFunction(){
    var x = document.getElementById("input").value;
    var output=document.getElementById("output");
    output.innerHTML = x;
    var scripts=output.getElementsByTagName("SCRIPT");
    for (var i=0;i<scripts.length;i++)
    {
        var script=scripts[i];
        eval(script.textContent);
    }
}

But I have my doubts about the automatic mode: Obviously, while the user is coding a script function it shouldn't be tested yet.

Update

Support for defining functions (based on Makyen's suggestion):

function editorFunction(){
    var x = document.getElementById("input").value;
    var output=document.getElementById("output");
    output.innerHTML = x;
    var scripts=output.getElementsByTagName("SCRIPT");
    for (var i=0;i<scripts.length;i++)
    {
        var script=scripts[i];
        var x=eval(script.textContent);

        var scriptNew=document.createElement("SCRIPT");
        scriptNew.type=script.type;
        scriptNew.textContent=script.textContent;
        script.parentNode.insertBefore(scriptNew, script);
        script.parentNode.removeChild(script);
    }
}
Little Santi
  • 8,563
  • 2
  • 18
  • 46
  • this works if i'm running some action (like `alert("hello")`) but it won't work if i'm defining a new function that i'm going to use later – Mahmoud Moustafa Sep 01 '16 at 23:53
  • Hum... I see. I've tried @Makyen's suggestion and posted it as an update. – Little Santi Sep 02 '16 at 00:11
  • Ack.. I did not see that you implemented creating the ` – Makyen Sep 02 '16 at 00:51
  • @Mayken Never mind: It's OK you post your answer and get the points for it. And if you want to include the delay mechanism for user input, go ahead. – Little Santi Sep 02 '16 at 06:49
  • +1 (it's a new day and I have votes again.) Reminded me that I needed to copy the attributes of the ` – Makyen Sep 02 '16 at 06:52
  • @Makyen Hehe. Any reason is good to receive points :-) But it's true: Your script is more complete for that reason. I've upvoted it. – Little Santi Sep 02 '16 at 07:17
1

You will need to specially handle the <script> tags. Content that is inserted as text (e.g. innerHTML) is specifically excluded from having the scripts run. This is for security reasons.

You can insert the text with innerHTML, then go through deleting the inserted <script> tags while creating new ones using document.createElement('script') in which you add the textContent of the script tag you are deleting. Then insert the newly created <script> tag into the document

window.setInterval(function(){
    var y = document.getElementById("automatic").checked
    if (y==1){
        editorFunction()
    }
}, 1000);

function editorFunction(){
    var x = document.getElementById("input").value;
    var outputEl = document.getElementById("output");
    outputEl.innerHTML = x;
    //Get a NodeList of the <script> elements
    var scripts = outputEl.querySelectorAll('script');
    for(var i=0;i<scripts.length;i++){
        var oldScript = scripts[i];
        var newScript = document.createElement('script');
        //Copy the actual script
        newScript.textContent = oldScript.textContent;
        //Copy the oldScripts attributes
        var attrs = oldScript.attributes;
        for(var j=0;j<attrs.length;j++){
            newScript.setAttribute(attrs[j],oldScript.getAttribute(attrs[j]));
        }
        //Replace the non-functional oldScript with the newScript
        oldScript.parentNode.replaceChild(newScript,oldScript);
    }
}
<body id="body">
<section class="container">
    <div class="row">
        <div class="col-sm-6">
            <textarea class="col-sm-12" id="input"></textarea>
        </div>
        <div class="col-sm-6">
            <div class="col-sm-12" id="output"></div>
        </div>
    </div>
</section>
<section>
    <div class="row">
        <div class="col-sm-3"></div>
        <div class="col-sm-6">
            <button onclick="editorFunction()">RUN</button>
            <input type="checkbox" id="automatic" checked>Submit the changes LIVE</input>
        </div>
    </div>
</section>
</body>

Suggested Changes:

  1. Use an event handler, not an interval timer:
    • I would implement a different method of having the output automatically updated. If you are going to have the output follow the input, then you should use an event handler, not an interval timer.
  2. Automatically disable the live/auto updates when a script exists.
    • Try the first snippet with "LIVE" enabled and <script>console.log('This is a test');</script> It will repeatedly output to the console.

var detectScriptRegExp = /<script/ig;
document.getElementById("input").addEventListener('input',function(){
    var autoCheckboxEl = document.getElementById("automatic");
    if (autoCheckboxEl.checked==true){
        editorFunction();
    }else if (autoCheckboxEl.disabled==true){
        detectScriptRegExp.lastIndex=0; //clear the RegExp
        if(!detectScriptRegExp.test(document.getElementById("input").value)){
            //Don't lock out LIVE if there is no <script>
            autoCheckboxEl.disabled=false;
        }
    }
}, false);

function editorFunction(){
    var autoCheckboxEl = document.getElementById("automatic");
    autoCheckboxEl.disabled = false;
    var x = document.getElementById("input").value;
    var outputEl = document.getElementById("output");
    outputEl.innerHTML = x;
    //Get a NodeList of the <script> elements
    var scripts = outputEl.querySelectorAll('script');
    for(var i=0;i<scripts.length;i++){
        var oldScript = scripts[i];
        var newScript = document.createElement('script');
        //Copy the actual script
        newScript.textContent = oldScript.textContent;
        //Copy the oldScripts attributes
        var attrs = oldScript.attributes;
        for(var j=0;j<attrs.length;j++){
            newScript.setAttribute(attrs[j],oldScript.getAttribute(attrs[j]));
        }
        //Replace the non-functional oldScript with the newScript
        oldScript.parentNode.replaceChild(newScript,oldScript);
        //Disable auto/live updates when there are scripts.
        //  This is needed, at least in a snippet. If you use console.log()
        //  the checkbox will rapidly be covered.
        autoCheckboxEl.checked = false;
        autoCheckboxEl.disabled = true;
    }
}
<body id="body">
<section class="container">
    <div class="row">
        <div class="col-sm-6">
            <textarea class="col-sm-12" id="input"></textarea>
        </div>
        <div class="col-sm-6">
            <div class="col-sm-12" id="output"></div>
        </div>
    </div>
</section>
<section>
    <div class="row">
        <div class="col-sm-3"></div>
        <div class="col-sm-6">
            <button onclick="editorFunction()">RUN</button>
            <input type="checkbox" id="automatic" checked>Submit the changes LIVE</input>
        </div>
    </div>
</section>
<!-- Extra lines so we can scroll the snippet in case console output covers controls-->
<br/><br/><br/><br/><br/><br/><br/><br/><br/><br/>
</body>
Makyen
  • 31,849
  • 12
  • 86
  • 121
  • one problem though.. the function stays even if you deleted the function and ran the code :) .. i guess i could find a way around it.. thanks a lot bro – Mahmoud Moustafa Sep 02 '16 at 01:25
  • @MahmoudMoustafa, I updated the second snippet (and explanation text) with another suggestion (disable LIVE when a ` – Makyen Sep 02 '16 at 03:22