Thursday, March 23, 2023
HomeJavascriptUtilizing net dev abilities to check net dev abilities

Utilizing net dev abilities to check net dev abilities


The editor

If you’re in my place and also you need the customers to sort code you could have two choices:

  • You employ one thing like CodeMirror. Which is a full-blown code editor.
  • You assemble one thing small through the use of both contenteditable or the nice outdated <textarea>.

For daskalo.dev I used the primary choice. It is as a result of I had more room and needed to offer a close-to-real programming expertise to the scholars. For iloveweb.dev I first tried to make use of contenteditable however had some CSS issues, so I ended up with <textarea>.

The underside line is that we’d like a strategy to get enter from the person. Code that we validate towards the given process.

Validating solutions on HTML questions

To get a greater concept of what we’re speaking about, right here is an instance of a query:

To illustrate that we have now a web page with header and navigation containing 3 <a> tags. Write a semantically appropriate HTML.

The thought right here is to check the information about semantics in HTML. The proper reply for this process is one thing like:

<header>
  <nav>
    <a href="">A</a>
    <a href="">B</a>
    <a href="">C</a>
  </nav>
</header>

(By the way in which, you’ll be able to see the precise query right here and check out fixing it your self.)

So, the person begins typing code, and we have now to see if the code is legitimate HTML, containing <header> ingredient that has nested <nav> that has three <a> hyperlinks. The very first thing is to parse the textual content and convert it to some type of AST (summary syntax tree), so we are able to traverse it. We should depend on one thing aside from common expressions as a result of will probably be a nightmare for extra complicated duties. I landed on this moderately small parser right here written by Sam Blowes. It is ~4Kb minified. Its enter is HTML and outputs a tree of objects we are able to loop over. For instance:

<p><span>Textual content</span></p>

is processed to:

{
  "node": "root",
  "baby": [
    {
      "node": "element",
      "tag": "p",
      "child": [
        {
          "node": "element",
          "tag": "span",
          "child": [
            {
              "node": "text",
              "text": "Text"
            }
          ]
        }
      ]
    }
  ]
}

I wrote a easy operate that traverses the construction and provides me entry to every node.

operate walkHTML(merchandise, cb) {
  cb(merchandise);
  if (merchandise.baby) {
    merchandise.baby.forEach(i => {
      walkHTML(i, cb);
    });
  }
}

The HTML parser and walkHTML are sufficient to put in writing a validator for the duty above. It is a matter of recognizing a selected construction of tags and counting what number of <a> components we have now.

operate validator(tree) {
  let numOfLinks = 0;
  walkHTML(tree, (el) => {
    if (el.tag === 'header') {
      walkHTML(el, (headerChild) => {
        if (headerChild.tag === 'nav') {
          walkHTML(headerChild, (navChild) => {
            if (navChild.tag === 'a') {
              numOfLinks += 1;
            }
          });
        }
      });
    }
  });
  return numOfLinks === 3;
}
validator(html2json(code));

Validating solutions on CSS questions

To validate CSS, we are able to use the identical strategy. We are able to get the code, rework it right into a tree and analyze it. Equally to HTML, there are lots of parsers on the market. I made a decision to make use of cssparser. It is just about the next:

const parser = new cssparser.Parser();
const tree = parser.parse(code).toJSON('easy');

For physique { font-size: 20px; } we’ll get again:

{
  "sort": "stylesheet",
  "degree": "easy",
  "worth": [
    {
      "type": "rule",
      "selectors": [
        "body"
      ],
      "declarations": {
        "font-size": "20px"
      }
    }
  ]
}

As a substitute of “easy,” we are able to use “deep” or “atomic,” which leads to much more detailed tree. For me, “easy” was ok.

That was good, however the CSS questions might transcend syntax and construction. Two issues require a bit extra work – selector specificity and selector matching. These we won’t validate through the use of a tree.

For the specificity, I seemed right here. A small utility that accepts a selector and returns the calculated specificity. For instance:

let worth = SPECIFICITY.calculate('#foo .bar .moo p')
// worth ->
/*
[
  {
    "selector": "#foo .bar .moo p",
    "specificity": "0,1,2,1",
    "specificityArray": [0, 1, 2, 1],
    "elements": [ ...]
  }
]
*/

Then with a bit of transformation we get a quantity like 121.

Quantity(SPECIFICITY.calculate('#foo .bar .moo p')[0].specificity.change(/,/g, '')); // 121

Then we are able to ask a query like

Write a sound CSS rule which’s selector has a specificity equal to 130.

and validate it with

operate validator(ast) {
  const tree = ast.toJSON('easy');
  let success = false;
  tree.worth.forEach(({ sort, selectors, declarations }) => {
    selectors.forEach((selector) => {
      const specificity = Quantity(SPECIFICITY.calculate(selector)[0].specificity.change(/,/g, ''));
      if (specificity === 130) {
        success = true;
      }
    });
  });
  return success;
}
const parser = new cssparser.Parser();
const ast = parser.parse(code);
validator(ast);

Play with the query right here.

The extra fascinating downside is to validate a selector matching. Or in different phrases, to see if a selected selector matches a selected HTML ingredient. After a few iterations, I discovered that there IS an API within the browser achieved particularly for this objective – ingredient.matches(). If we have now a sound DOM someplace, we are able to choose a component and execute .matches(selector). Nonetheless, at that time, I’ve already applied one thing that works. It is a bit hacky, however I like that I discovered it myself, so I made a decision to go along with it. Let’s use the next query for instance the answer:

Set a coloration of the second <span> ingredient within the following HTML snippet:
<part>
  <p>
    <span class=”xxx”>A</span>
    <span>B</span>
  </p>
</part>

The very first thing we have now to do is add that HTML to a web page. We want an actual DOM ingredient. We need to maintain this out of the present web page as a result of this may occasionally have an effect on the prevailing content material. The best way to try this is to can an iframe dynamically. In that newly created iframe, we are able to use doc.write to position within the <part> and its nested components. From there, we are able to additionally add a <script> tag that runs some logic. And in our case, that logic will likely be doc.querySelector (or ingredient.matches) to confirm if the given by the person selector works. In the long run, if the selector matches, we wish entry to the matched ingredient to see whether it is what we needed. Right here is the ultimate model of my operate:

// Word: on the present web page we have now  <div id="exerciseFrame"></div>
operate matchSelectorToHTML(selector, html) {
  const iframe = doc.querySelector('#exerciseFrame');
  let end result = false;
  const iframeDoc = iframe.contentDocument || iframeWin.doc;
  const funcName = `iframeMatching${new Date().getTime()}`;
  window[funcName] = operate(r) {
    end result = r;
  }
  iframeDoc.open();
  iframeDoc.write(html);
  iframeDoc.write(`
    <script>
      el = doc.querySelector("${selector}");
      dad or mum.${funcName}(el);
    </script>
  `);
  iframeDoc.shut();
  delete window[funcName];
  return end result;
}

Discover how we create a operate within the international scope (funcName); then, from contained in the iframe, we use dad or mum.<func identify> to name it with the matched DOM ingredient (if any). I do know it is difficult, nevertheless it works. You may see the query stay right here.

Validating solutions on JavaScript questions

Initially, I assumed validating JavaScript duties can be probably the most difficult half. It occurred that it was the simplest. All I had to make use of was the Perform constructor. Let us take a look at the next query:

Write a JavaScript operate with identify “take a look at” that returns “iloveweb”.

And the validator that I am utilizing:

operate validator(code) {
  const f = new Perform(`
    ${code};
    return take a look at();
  `);
  return f() === 'iloveweb';
}

In JavaScript, we are able to use the Perform constructor to create a operate out of a string. So, all we have now to do is to imagine that the person will sort the proper JavaScript. Then we plug within the code in some context created by us. On this case, we count on to have a operate with the identify take a look at. We name that operate and return its end result. It must be “iloveweb”.

The extra complicated query, after all, requires extra context from our aspect. For instance:

Write a JavaScript operate “render” that updates the HTML content material of the next tag:<div id="app"></div>

We need to see if the developer is aware of how one can choose a DOM ingredient and replace its content material. We won’t enable entry to the precise DOM APIs. So, we have now to mock them like so:

operate validator(code) {
  const f = new Perform(`
    let domEl = {};
    const doc = {
      querySelector(sel) {
        return sel === '#app' ? domEl : null;
      },
      querySelectorAll(sel) {
        return sel === '#app' ? [domEl] : [];
      },
      getElementById(sel) {
        return sel === 'app' ? domEl : null;
      }
    }
    ${code};
    render();
    return domEl;
  `);
  return typeof f().innerHTML !== 'undefined';
}

(Take note of that the Perform constructor may very well be harmful. You might be giving lots of energy to the person. Use it correctly.)

Ultimate phrases

All of the methods above are bundled in iloveweb.dev. Go test it out, and if you wish to contribute with extra questions, go to the mission’s GitHub repo right here.

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular

Recent Comments