Blog-Archiv

Samstag, 19. September 2015

JS Poor Developer's IDE

For learning JavaScript I sometimes wanted to have a text field directly in the web-browser where I can write and try out scripts. You can do this any time at JSFiddle, or use the console input of the debugger in your browser (press F12), but let's see whether we can write such a "Poor Developer's JavaScript IDE" as web-page.

JS Evaluation by the Browser

Normally any JavaScript is loaded via a <script type="text/javascript"> HTML element, either in the <head> or in the <body> of the page. There could be either an URL or the script text itself inside. In case URL this must be "relative" to the page, that means it must at least reside on the same server. In any other case the browser will deny and report a cross-site scripting attempt.

Consequently this means that any script the browser loads and executes comes from the server where the page resides. But I want to write a script directly in the browser, and execute it, without having access to the HTTP server where the page resides!

JS has a facility for that, it exposes the JS interpreter of the browser via the global and infamous eval(script) function. Infamous because it is a bad practices to write scripts which generate and execute other scripts. This could easily break out of control and result in endless loops. So do not use eval()! I will use it here exceptionally to achieve my scripting IDE, because it gives me the opportunity to catch interpretation errors.

You could also put the script text into a <script> tag, and add this to the HTML document's head. This would make the browser execute the script. JS source for that is on bottom of this page.

Just Write and Run

Here is a very short HTML page that lets you write and run JS code.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!DOCTYPE html>
<html>
<head>
  <title>Run JavaScripts from Textarea</title>

  <script type="text/javascript">
    var run = function(script)  {
      eval(script);
    };
  </script>

</head>

<body>

  <div style="text-align: center;">
 
    <textarea id='script-text-area' style="width: 100%;">alert("Hello World!")</textarea>
    <br>
    <input type="button" value="Run" onclick="run(document.getElementById('script-text-area').value);"/>
    
  </div>
    
</body>

Select this text and copy it, then create a source file like e.g. /home/me/js-ide.js, open it with some text editor, paste the text and save the file. Then load it into the browser by writing file:///home/me/js-ide.js into the address line on top, press ENTER.

Here is what you should see:


Change the text to "Goodbye World" and press "Run" to get the feeling.

Write, Run Encapsulated, see Errors

There are two major flaws in this very basic IDE.

  1. The scripts you write will "aggregate", that means any variable you define will be globally in the window object, and might affect subsequent scripts. All scripts that are started should run encapsulated, so that the global context (which is implicitly the window object) is not used, or at least not unintended.

  2. We can not see error messages of the JS interpreter when we have mistakes in the script.

Encapsulation

JS has no access modifiers, thus just basic encapsulation mechanisms like scope-visibility can be applied. This is about avoiding global variables, one of the biggest threats against source reliability.

In any script, be it embedded or in its own file, if you write

foobar = "My name is foo-bar";

you will have created a global variable named foobar (which you want to avoid).
If you write

var foobar = "My name is foo-bar";

you also have created a global variable.
But if you declare variables within a function body, it is different.
If you write

var foo = function() {
  var foobar = "My name is foo-bar";
};

you avoided the global variable! But mind that if you write

var foo = function() {
  foobar = "My name is foo-bar";
};

you again created a global variable, because you forgot to prepend var.

So we can not prevent the script writer from "polluting the global namespace", but we can when (s)he uses var to declare variables. So please use var any time you declare a variable in JS, this is a generally good practice!

What we can do is enclose the script from the textarea into an anonymous function and call that function after declaration. This looks like the following.

(function() {
  var foobar = "My name is foo-bar";
})();

And to do even better we also declare the strict mode at head of the script. This will prevent variables without leading var.


"use strict";
(function() {
  // here goes the script text
})();

In such a capsule we will pack any script that we fetch from the text-area.

Read about the revealing module pattern for more information about this kind of encapsulation.

Error Reporting

The JS interpreter does not give good information about errors in scripts (is browser-specific). Nevertheless I want to make them visible. When the eval() function throws an exception, I can catch that and render it in some logging area.

  try {
    eval(script);
  }
  catch (error) {
    log(error);
  }

For that to work we need (1) a log(message) function and (2) a logging area.

The Enhanced IDE

Here is the source code for the enhanced "Poor Developer's JavaScript IDE", including the log() function and a logging area.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<!DOCTYPE html>
<html>
<head>
  <title>Run JavaScripts from Textarea</title>

  <script type="text/javascript">
    var run = function(script)  {
      try {
        eval(
          "'use strict';\n"+
          "(function() {\n"+
            script+
          "\n})();"
        );
      }
      catch (error) {
        log(error);
      }
    };
    
    var log = function(text) {
      var logText = document.createTextNode(text);
      var logLine = document.createElement("p");
      logLine.appendChild(logText);
      
      var logArea = document.getElementById("log-area");
      logArea.appendChild(logLine);
      logLine.scrollIntoView();
    };
  </script>

</head>

<body>

  <div style="text-align: center;">
    <p>Enter some JavaScript text, then press "Run".</p>
    
    <textarea id='script-text-area' rows="25" style="width: 100%;"></textarea>
    <br>
    <input type="button" value="Run" onclick="run(document.getElementById('script-text-area').value);"/>
    
    <p>Use <code>log("my log")</code> to write to the log area below.</p>
    
    <div id='log-area' style="text-align: initial; border: 1px solid gray; height: 20em; overflow: auto;"></div>
    <input type="button" value="Clear Log" onclick="document.getElementById('log-area').innerHTML = '';"/>
    
  </div>
    
</body>
</html>

When your browser denies eval(), you could also use the following JS function to execute the script text. But mind that here you won't receive an exception when the script is erroneous. This creates a script tag and adds it to the HTML document's head, which makes the browser execute it.

    var run = function(script)  {
      var encapsulatedScript =
          "'use strict';\n"+
          "(function() {\n"+
            script+
          "\n})();";
      var scriptElement = document.createElement("script");
      scriptElement.type = "text/javascript";
      scriptElement.appendChild(document.createTextNode(encapsulatedScript));
      document.head.appendChild(scriptElement);
    };

Here is the page for trying out. I added an erroneous script to show whether error messages are displayed.

Enter some JavaScript text, then press "Run".


Use log("my log") to write to the log area below.

Such a mini-IDE is useful when you want to present JavaScript example source and provide the possibility to immediately start it.

Have fun!





Keine Kommentare: