machty / emblem.js

Emblem.js - Ember-friendly, indented syntax alternative for Handlebars.js
http://emblemjs.com
MIT License
1.04k stars 81 forks source link

Slim-esque inline tags #41

Closed heartsentwined closed 11 years ago

heartsentwined commented 11 years ago

Will there be support for a compact inline tag shortcut denoted by the colon (:)?

In slim,

ul
  li.first
    a href="/a" A link
  li
    a href="/b" B link

can be written more succintly as

ul
  li.first: a href="/a" A link
  li: a href="/b" B link

A lot of common patterns fit into this syntax: li: a, nav: ul, a: img, etc. They all belong to the family of items having multiple semantics: a link that is a list item, a list that is a navigation, an image that is a link, etc.

machty commented 11 years ago

This seems good, would also be useful for the various scaffolding/grid frameworks, such as this bootstrap looking thing:

.container: .row: p Here is some text

I would just want to make sure it'd fit in nicely with work being done here https://github.com/machty/emblem.js/issues/39 , which, unlike slim, allows for a single line chain of nested HTML elements and block mustache helpers. Take a look at that and lemme know if you think this might play nicely with what's being done in that issue.

heartsentwined commented 11 years ago

Not comprehensive perhaps, but a couple of tests and observations:

ul %li

doesn't work

Taking these two examples:

: and % serve slightly different grammatical purposes.

The first case, the : serves to mark the end of the first tag-chain, so it can appear in these statements:

The second case, the % serves to mark a particular literal as a tag, as opposed to being a block argument of the handlebars block. It also, coincidentally, marks the end of the handlebars block. It would disambiguate:

Either could be replaced by another, i.e.

But I would argue that the : should be adopted, following the convention from slim.

However, I would think that the example in #39

ul = each items %li = linkTo "item" this = item.text

while being possible, is drifting towards Perl-golf-ish. It took me a few full seconds to read and parse the line.

The original intention of the : is, I think, to be reserved for the class of multiple-semantics content I cited, i.e. at most two or three tags used together in a meaningful chain. A quick scan can still make out this compact syntax, especially in common idioms that one is familiar with, such as the example you have given:

.container: .row: p Here is some text

This example also shows an advantage of using the : - the ability to continue using the implicit div. A % line would have to be written thus:

.container %div.row %p Here is some text

Well, not really actually, you could still allow

.container %.row %p Here is some text

But the human brain processes letters and symbols as two different groups. A couple of symbols jammed together tend to get read (at first sight) as one group, requiring additional mental effort to separate them. Thus

Would be hard to process, at least as compared to the : syntax. The %# in particular, resembles "consonant cluster" in natural languages, using a rather bad metaphor. Or put it simply it looks like a jumble of trash to me.

Continuing on the issue of human readibility, if one wants to compress block helper contents into one line, as in the above example, then neither of these would be sufficient:

Neither line is easily readible (as least as compared with the rest of the slim/emblem syntax). Readibility comments are, of course, subjective, but I'd say that this points out either of the following:

machty commented 11 years ago

Wow, awesome response. Busy at the moment but I'll be parsing this shortly. Thank you for the writeup!

heartsentwined commented 11 years ago

Any update?

machty commented 11 years ago

So, I think I understand, but lemme just rephrase it a bit.

In Emblem, at the beginning of a non-empty, non-comment line, the rules are:

It sounds like what you're saying as that when a : is encountered, we should start over with this logic, and subject whatever follows the : to the same rules. Some examples:

ul: li: a href="http://www.google.com" class=something: span Hello!
<ul><li><a href="http://www.google.com" {{bindAttr class="something"}}><span>Hello!</span></a></li></ul>

These were proposed in https://github.com/machty/emblem.js/issues/39, here's how they've be revised, presented in order of 1) original suggested syntax, 2) what it compiled to, and 3) how it'd look using a : approach. Afterwards I'll revise/simplify the syntax.

bullshit = bullshit = bullshit | bullshit
{{#bullshit}}{{#bullshit}}{{#bullshit}}bullshit{{/bullshit}}{{/bullshit}}{{/bullshit}}
bullshit: bullshit: bullshit: | bullshit

ul = each items = linkTo destination = text
<ul>{{#each items}}{{#linkTo destination}}{{text}}{{/each}}</ul>
ul: each items: linkTo destination: text

ul = each items %li = name
<ul>{{#each items}}<li>{{name}}</li>{{/each}}</ul>
ul: each items: li: name

ul = each items %li = linkTo "item" this = item.text
<ul>{{#each items}}<li>{{#linkTo "item" this}}{{item.text}}{{/linkTo}}</li>{{/each}}</ul>
ul: each items: li: linkTo "item" this: item.text

This looks great to me. I prefer how this looks to the already-established syntax of

p = foo
<p>{{foo}}</p>

I'd keep this syntax around, but also support all of these colon-based ones.

p: foo
<p>{{foo}}</p>

%p: foo
<p>{{foo}}</p>

%each: =span
<each>{{span}}</p>

=span: %each Hello
{{#span}}<each>Hello</each>{{/span}}

These last two bring up an important case; when you want to override the default convention to use a known HTML5 tag name as a mustache invocation, you have to use =. Same if you want to render an HTML element with non-HTML5 tag by using the % prefix. Without a further syntax adjustment, you'd have to weirdly clutter : together with % or =. These corner cases are important, but there's also the case of text nodes. The following is already valid Emblem syntax:

linkTo foo | Hello
{{#linkTo foo}}Hello{{/linkTo}}

This would also technically be valid with the new colon syntax:

linkTo foo: | Hello

But that clearly sucks compared to what already works. But I think I have the solution.

In a chain of elements/helpers/text, the following will be nesting delimiters:

So now here are a few examples of how to generate handlebars HTML:

{{#each controller}}<div class="fun" data-foo="5"><p>Hello</p></div>{{/each}}
each controller: .fun data-foo="5": p Hello

/ less ideal (IMO), but working alternatives
= each controller: .fun data-foo="5" %p Hello
= each controller: div.fun data-foo="5": p Hello

Anyway, there's a million permutations to this that I don't need to bother with, but I think you get the point.

I'm really happy with this approach; seems clean and sexy.

machty commented 11 years ago

There's some existing sugar for using % in a mustache invocation that sets the tagName hash value that I need to kill of to make this work. Good riddance.

heartsentwined commented 11 years ago

Wow, excellent "summary"! You sure think of it thoroughly, with the bunch of edge cases that you have given. I have not used neither PEG nor JISON/BISON before, so you're the expert here in language grammar. Just one observation:

This sentence

when a : is encountered, we should start over with this logic, and subject whatever follows the : to the same rules.

if taken literally, would break existing syntax, e.g. when used in conjunction with ember's alternate true/false class bindings div class={isActive::hidden :content} (example from issue 40 ). (Need exceptions for :s used inside expressions.)

I'm 99% sure you have thought of that and the sentence is just an quick summary in natural language, but well, no harm pointing out I guess.

machty commented 11 years ago

Yes, I'd definitely handle that case. It'll work the way you expect.

heartsentwined commented 11 years ago

Thanks in advance @machty!

P.S. updated previous comment, wrong copy-and-paste issue link :-)

machty commented 11 years ago

Closed by https://github.com/machty/emblem.js/commit/b5e44284c3e9ae030e8e36558d05e039af315cc0

Pretty happy about this one. The final syntax ended up having some slight differences from my long ass earlier post, but it should all seem pretty intuitive. Gonna spend some time shifting the docs to this style, but in the mean time, you can get an idea of the syntax by looking at https://github.com/machty/emblem.js/blob/master/spec/qunit_spec.coffee#L1553

Thank you so much for suggesting this and helping to work through it all!

heartsentwined commented 11 years ago

The test suite use cases look impressive indeed. Great work @machty! I'm going to pull the latest version and test it on my emblem templates.

Once again many thanks for this (rather big) enhancement!

heartsentwined commented 11 years ago

Hi @machty.

The new colon syntax is mostly working well, but I have found an edge case:

I'll continue applying the new syntax to my emblem templates, and I'll let you know if I find any more edge case.

machty commented 11 years ago

Looking into it.

Note that li data-foo=bar %a is not valid syntax. You need to have the colon. The only time you don't need the colon is if you're initiating a text block within a block mustache, such as

helperName | Text nested inside block helper. 

Also, I don't know what you're saying here: first you say li data-foo=bar %a doesn't compile, but then you say it compiles but the data-foo is missing... which one is it? Also, if Ember/Handlebars can't find the non-falsy value for bar, it may very well not render data-foo attribute even if it's properly bound, so it might not be an emblem thing.

machty commented 11 years ago

This all seems to be compiling correctly. Aside from the one with invalid syntax, I think you might just be running into a case where Handlebars isn't finding the property with the bar path.

heartsentwined commented 11 years ago

Sorry, I phrased poorly. Here is a proper one:

With bar = somevalue set (somewhere in model / controller / etc):

li data-foo=bar
  a
    baz

compiles (via handlebars) to

<li data-bind-attr-1="1" data-foo="somevalue"><a>baz</a></li>

From what I understand, the data-bind-attr-1="1" is there for ember's own use; it is a hook so that ember knows where to update bar. The data-foo="somewhere" is the actual attribute that userland code wants to be there.

Using the colon syntax:

li data-foo=bar: a
  baz

(I did not compact to a: baz to isolate the problem more properly.) This compiles (via handlebars) to

<li data-bind-attr-1="1">a baz</a>

Two issues:

One potential issue:

I can help with debugging. I understand that there are two steps here that can go wrong (emblem -> handlebars or handlebars -> html). This can be a handlebars fault, but I'm not sure which part went wrong, because the handlebars template is already in an evaluated form when I see it in the console. Perhaps you can tell me how to obtain the handlebars template output from emblem, and I'll post the handlebars templates?

heartsentwined commented 11 years ago

The reference "working" case:

Both these emblem templates:

li data-foo='bar'
  a
    baz
li data-foo='bar': a
  baz

compiles (via handlebars) to

<li data-foo="bar"><a>baz</a></li>

But here, bar is a literal, not a binding reference.

machty commented 11 years ago

Could you clone the repo and add a breaking test case to spec/qunit_spec.coffee? You can build emblem and run the tests just by doing rake or bundle exec rake. I've tried copying your test cases myself but they all still seem to pass, and this would be a more direct way of demonstrating something that's broken for you. Once you have something that's breaking, I'll accept a pull request and then fix whatever doesn't seem to be working.

heartsentwined commented 11 years ago

Thanks, I'll do that

machty commented 11 years ago

Note that the Emblem tests presently just test vanilla Handlebars compilation (and stub out Ember helpers when testing Ember functionality), so there's not going to be any concept of bindAttr in your output if you just do something like

a data-foo=bar Hello

This would just set data-foo="value_of_bar" and not installing bindAttrs, but that's fine, since the problems you're describing seem like syntactical issues that would affect both vanilla and Ember Handlebars.

Thanks for taking the time to do this!

heartsentwined commented 11 years ago

Allow me some time, I'm digesting your qunit test suite and your fixtures.

heartsentwined commented 11 years ago

I'm confused, what exactly is the relationship between emblem, handlebars, and html? I originally thought emblem would compile to handlebars, which would then compile to html. But looking at your test suite, it seems that emblem compiles directly to html, or that it is a part of handlebars, or the other way round, etc.

In short, what should I expect from emblem template tests? handlebars? Or html?

machty commented 11 years ago

Well, you don't actually need to know the answer to those questions to copy the pattern of the test cases set up for the colon syntax that I linked to before, but all that Emblem does is compile to a Handlebars AST that Handlebars uses to compile the rest of the way, but for testing purposes, we need to check the final output to HTML, so we render the template to HTML because it's the easiest way to confirm that it does what we think it does.

But just copy one of the colon syntax cases; they're all in the format of 1) Here is Emblem code, 2) Here is the object passed to the template when it's rendering, 3) Here is the output HTML that I expect.

heartsentwined commented 11 years ago

Should be a dependency problem. My guess is that the tests run on npm modules, but emblem-rails depends on gem packages, and that some differences somewhere among these sets of modules caused the observed behavior. Here is what I did:

  1. I forked the repo, added a test case exactly as how I described it above, and indeed it passes, just like you said.
  2. I created a fresh rails project, added ember-rails and emblem-rails to it, and made a minimal "test case" template. :banghead: Now it fails as I described. The project is at heartsentwined/emblem-test, heroku demo here, key code and output extracted in this gist.
  3. There are three test cases in the repo:
    • li data-foo=bar: a quux, the failing case
    • li data-foo='bar': a quux, the control case
    • li data-foo=bar href='#': a quux, curiously, I have found a workaround: by ensuring that the line-section terminates in a literal attribute (terminating in a quotation mark), like a href (albeit invalid in a li), everything works fine. Suggestive to a ": without an immediately preceding ["']" bug?
  4. I traced upwards from the passing qunit tests to the failing rails case. There is at least one rather important difference here: the qunit test suite uses npm modules, but emblem-rails uses gem packages.
  5. One such difference is handlebars. npm's handlebars is at 1.0.10, but the corresponding gem handlebars-source is at 1.0.9 or 1.0.0.rc{1,2,3}. emblem uses the 1.0.10 handlebars module from npm, but there is no 1.0.10 gem.
  6. The rails handlebars source comes from a #= require handlebars sprockets line. I traced it, and I think it should come from (handlebars-source)/dist/handlebars.js. I tried removing handlebars.js from the gem, and symlinking the one from emblem's (under node_module). The error is still there. (I'm not confident on this step though, I might have done something wrong here.)
  7. I tried different combinations of ember-rails, handlebars-source, emblem-rails, and coffee-script-source, as far as dependencies allow. This is not fruitful though, because the latest emblem-rails already restricts ember-rails to the 0.11.* branch, which in turn restricts handlebars-source to 1.0.0.rc3. I tried several combinations of older versions, and they, expectedly, fail.
  8. I'm out of ideas now, I know this is probably not an issue of emblem itself, but I think you may have more knowledge in this dependency tree (hell?) than I do.
machty commented 11 years ago

THANK YOU for going this in-depth, I've finally found a way to reproduce this error. It doesn't really have anything to do with dependency hell (but yes, stuff is really complicated, and handlebars versioning really went off the deep end in that their package versions don't match the versions listed in the code). I should have it fixed shortly.

heartsentwined commented 11 years ago

I have no idea how you got it, but good luck with the (hopefully) solution!

machty commented 11 years ago

Try now, emblem-source 0.2.1.

machty commented 11 years ago

The problem stems from testing only in a raw Handlebars context. There's syntax patterns that only get detected when Ember is enabled, and I thought they were similar enough that I could get away with not strictly testing it. Silly me. I really need to find a way to properly test Emberized-Emblem, but it's a really tricky thing to set up, since Emberized handlebars template expect a lot more information provided to them in order to render, stuff which might depend on there being a DOM, which kinda sucks. But anyway, I found your exact problem, and that's certainly totally fixed at this point. Lemme know if you find anything else, and thanks so much

heartsentwined commented 11 years ago

Testing now. I think you mean emblem-source 0.2.1. :-)

heartsentwined commented 11 years ago

Working perfectly :+1: Brilliant!

machty commented 11 years ago

wooo!