Skip to content

Instantly share code, notes, and snippets.

@samuelgoto
Last active August 9, 2017 23:38
Show Gist options
  • Save samuelgoto/d8d510032e93e773454eb2ad24fdb9a2 to your computer and use it in GitHub Desktop.
Save samuelgoto/d8d510032e93e773454eb2ad24fdb9a2 to your computer and use it in GitHub Desktop.

Introduction

This is an exploration of a DST to be embedded in javascript designed specifically to build and manipulate the HTML DOM.

Largely inspired by a {Kotlin, JFX, Protobuf, JSON-ish}-like syntax (to intermingle well with javascript code) and a JSX-like DOM building algorithm.

Basic idea

Introduce syntax to javascript to intermingle tree-like docs and code and vice versa. Something like this:

let doc = div {
  // comments inside document

  a         // child
  "order"   // child
  b         // child
  "matters" // child
  c         // child
  
  d(attribute: value) { // child
    e { // child's child
      f(g: false) // child's child's child with attribute
    }
  }
  
  // if-expressions
  if (cond) {
    a
  } else {
    b
  } 
  
  // for-expressions
  for (u in g) {
    a {
      b { `u.b` }
    }
  }
  
  // function calls and expressions
  var a = g();
  
  // inline callbacks
  div(onclick : function() { alert("hi"); }) {
    
  }
  
  
  // async document building
  await fetch("data.pb").map(u => {
    div {
      span {
        `u.b`
      }
    }
  })
};

Example

hello world

var doc = div {
  // New syntax introduced to build documents.
  div {
    // This is a text node and a child
    "hello world"
    
    // Also, comments allowed!!!
  }
};

attributes

var doc = div {
  form {
    "Enter your name:"
    input(enabled: false) {
    }
  }
};

callbacks

var doc = div {
  div {
     "hello world", 
     // inline callbacks
     onclick = function() {
        alert("hi");
      }
  }
};

expressions

var people = ["goto", "bnutter"];

var doc = div {
  // for-expressions enable you to iterate and add multiple nodes to the parent node.
  for (person in people) {
    div {
      p { a(href: `/users/{{person.id}}`) { `{person.name}` } },
    }
  },
  
  // arrow functions
  people.map(person => { div { p { `{{person.name}}`} })
  
  // try-catch expressions
  try { avatar(user); } catch {  { div { "invalid user id" }} },
  
  // TODO(goto): if-then-else expressions
  // TODO(goto): select operator, switch-like expressions

};

async-await

var doc = div {
  // async-await
  var users = await fetch("people.xml");
  users.map(user => div { `user.name` });
};

CSS Typed OM

var doc = div {
  div(
    // Uses CSS's TypedOM, 
    style: {
      width: "100%",
      position: "absolute"
    }) {
    "hello world"
  }
};

custom elements

// Doc-expressions can also be represented as classes that implement an Element interface.
// For example:

var doc = div {
  head {
   // CSS in JS!!!
   await fetch(["main.css-in-js", "hello.css-in-js"]).map(style => new Style(style));
  },
  body {
    bind ["goto", "bnutter"].map(user => new User(user)),

    // web-components like syntax, creating new node types!
    User {
      name: "Sam"
    }
  },
}

class Style implements Element {
  doc() {
    return div { "hello world" };
  }
}

class User implements Element {
  doc() {
    return div { "hello world" };
  }
}

Related Work

  • JSX
  • Kotlin typed builders
  • Elm
  • Hyperscript
  • json-ish
  • Om
  • Flutter
  • Anko layouts
  • Curl
  • JFX Script
  • JXON
  • E4X
@samuelgoto
Copy link
Author

samuelgoto commented Aug 7, 2017

Some options for syntax for configuring a builder:

// Cast-like expression
let a = (HtmlElement) div {
  span {
  }
}

// Cast-like expression, no parens
let a = html div {
  span {
  }
}

"pragma"-like statement
let a = "html" div {
  span {
  }
}

// Root-level element
let a = html {
   div {
    span {
    }
  }
}

// decorator
let a = @html div {
  span {
  }
}

// #hash
let a = #html div {
  span {
  }
}

let a = div(@doc = html) {
  span {
  }
}

// Extra syntax
let a = doc(html) {
   div {
    span {
    }
  }
}

let a = html#div() {
  span {
  }
}

let a = div#html() {
  span {
  }
}

let a = div@html() {
  span {
  }
}

let a = div as html {
  span {
  }
}

let a = div[html] {
  span {
  }
}

let a = /** HtmlElement */ div {
  span {
  }
}

let a = new HtmlElement() {
  div {
    span {
    }
  }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment