Arc Forumnew | comments | leaders | submitlogin
1 point by Pauan 4696 days ago | link | parent

And here is a fantastic example of why macros are incredibly awesome, and why JS could really use macros. Let's suppose you wanted to generate the following DOM structure:

  <div test="yes">
    <div foo="bar">
      <div qux="corge"></div>
      <div corge="yesno"></div>
    </div>
  </div>
Now, you could of course write that out as a string and use innerHTML, etc. But what about event listeners? What if you want one of the node's attributes to vary depending on another node? Now you get into the verbosity that is the DOM. Let's start with the traditional way:

  var div1 = document.createElement("div");
  div1.setAttribute("test", "yes");

  var div2 = document.createElement("div");
  div2.setAttribute("foo", "bar");

  var div3 = document.createElement("div");
  div3.setAttribute("qux", "corge");

  var div4 = document.createElement("div");
  div4.setAttribute("corge", div1.getAttribute("test") + "no");

  div2.appendChild(div3);
  div2.appendChild(div4);
  div1.appendChild(div2);
Okay, so we need to give each element a unique name... and we need to deal with accurate-but-verbose names like setAttribute... and this is all flat: it doesn't show the nested structure of the DOM. So when working on my Chrome extensions, I use a little library that provides a UI.create function:

  UI.create("div", function (x) {
      x.setAttribute("test", "yes");

      x.appendChild(UI.create("div", function (self) {
          self.setAttribute("foo", "bar");

          self.appendChild(UI.create("div", function (self) {
              self.setAttribute("qux", "corge");
          }));

          self.appendChild(UI.create("div", function (self) {
              self.setAttribute("corge", x.getAttribute("test") + "no");
          }));
      }));
  });
This shows the structure of the DOM, and due to lexical scope, I can give every element the same name if I want to. I consider this an improvement over the standard DOM, but it's still far too verbose...

But with my compiler, I wrote a "div" and "w/div" macro, so now I can write this:

  (w/div x test "yes"
           (div foo "bar"
             (div qux "corge")
             (div corge (+ (x.getAttribute "test") "no"))))
Well, gosh. Not only is this drastically shorter and easier to read... but it shows the DOM's nested structure. w/div lets you create a new div with a specific name (in this case, "x"), and div is just like w/div, except it prechooses the name "self" for you. You can think of them like rfn and afn, but for DOM elements.

And since this macro expands into the verbose DOM methods, I can do things like add event listeners. Thus, this method is by far the best: you get all the flexibility of the DOM, but with minimal verbosity.

For instance, I just added in this:

  (div on (click (e)
            (+ this.id "bar")))
Which then expands into this:

  (function (self) {
      self.addEventListener("click", (function (e) {
          return (this.id + "bar");
      }), true);
      return self;
  })(document.createElement("div"));


1 point by Pauan 4696 days ago | link

And now styles work, so this:

  (append document.body
    (div style (height          50px
                width           50px
                backgroundColor black)

         on    (click (e)
                 (alert "foo")))

    (div style (height          50px
                width           50px
                backgroundColor green)

         on    (click (e)
                 (alert "bar"))))
Is compiled into this:

  document.body.appendChild((function (self) {
      self.style.height = "50px";
      self.style.width = "50px";
      self.style.backgroundColor = "black";
      self.addEventListener("click", (function (e) {
          return alert("foo");
      }), true);
      return self;
  })(document.createElement("div")));
  
  document.body.appendChild((function (self) {
      self.style.height = "50px";
      self.style.width = "50px";
      self.style.backgroundColor = "green";
      self.addEventListener("click", (function (e) {
          return alert("bar");
      }), true);
      return self;
  })(document.createElement("div")));

-----