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

is there a multiline helper syntax? (for ex, for contextual components and yield) #292

Closed kjhangiani closed 7 years ago

kjhangiani commented 7 years ago

With the addition of contextual components, and the component and hash helpers in ember 2.3+, we've started to move some older code to use the new syntax.

However, it's somewhat tedious to yield large blocks of components using the hash helper without the ability to do multiline components.

Is there a supported way, or plan to allow this in emblem?

For ex, currently we use this syntax a lot:

= wijmo/toolbar/buttons/text-strikethrough [
 _firstSelectedCellStylingObservable=(readonly _firstSelectedCellStylingObservable)
 isReadonly=(readonly isReadonly)
 buttonAction=(action applyStyles)
]

however, when changing to yield with contextual components, we end up with lines like:

yield (hash (textStrikethrough=(component "wijmo/toolbar/buttons/text-strikethrough" isReadonly=isReadonly buttonAction=(action applyStyles)) textBold=(component "wijmo/toolbar/buttons/text-bold" isReadonly=isReadonly buttonAction=(action applyStyles)))

etc etc which is far less readable. Is there any way to make this more palatable in emblem?

kjhangiani commented 7 years ago

So - discovered that this syntax successfully compiles:

yield (hash buttons=(hash saveSheet=(component "wijmo/toolbar/buttons/save-sheet" [
 isReadonly=isReadonly
 buttonAction=(action saveComponent)
]) fontFamily=(component "wijmo/toolbar/buttons/font-family" [
 _firstSelectedCellStylingObservable=(readonly _firstSelectedCellStylingObservable)
 _closeMenuRequestedObservable=(readonly _closeMenuRequestedObservable)
 _closeMenuException=(readonly _closeMenuException)
 closeAllMenus=(action closeAllMenus)
 isReadonly=(readonly isReadonly)
 buttonAction=(action applyStyles)
])))

and actually yields the components - however, the assignments inside the bracket do not work (they remain undefined) - so it's not usable. Still trying various combinations to see if there's any way to get linebreaks into the syntax

kjhangiani commented 7 years ago

Not the most elegant solution, but this does work:

= yield-hash [
 class="spreadsheet-toolbar"
 saveSheet=(component "wijmo/toolbar/buttons/save-sheet" isReadonly=(readonly isReadonly) buttonAction=(action saveComponent))
 fontFamily=(component "wijmo/toolbar/buttons/font-family" isReadonly=(readonly isReadonly) buttonAction=(action applyStyles))
] as |toolbar|
    yield (hash saveSheet=toolbar.saveSheet fontFamily=toolbar.fontFamily)

where the yield-hash component just does yield this in its template. This works, and probably has an extremely negligible performance impact, but does make the code far more readable.

thec0keman commented 7 years ago

very good point! we have some components that are also starting to get a bit messy like this.

what do you think an ideal syntax in Emblem would look like?

kjhangiani commented 7 years ago

honestly - I'm open to anything - as long as there is a way to do it! I do like the [ ... ] syntax for components, perhaps it can just be the de-facto way to do multiline in emblem? Though the indenting might get tricky. some initial thoughts...

yield (hash buttons=(hash [
 saveSheet=(component 'save-sheet' [
  isReadonly=isReadonly
  buttonAction=(action saveComponent)
 ])
 fontFamily=(component 'font-family' [
  isReadonly=isReadonly
  buttonAction=(action applyStyles)
 ])
]))

Another possibility is to use something like double brackets [[ and ]] and just replace everything between [[ ]] onto the same line - so that the indenting can be done as a style preference but actually wouldn't affect parsing (this might make it easier to manage deeply nested helpers - which does happen on occasion)

ie:

yield (hash buttons=(hash [[
 saveSheet=(component 'save-sheet' [[
  isReadonly=isReadonly
  buttonAction=(action saveComponent)
 ]])
 fontFamily=(component 'font-family' [[
  isReadonly=isReadonly
  buttonAction=(action applyStyles)
 ]])
]]))

Or a prefix on each line to indicate it should be brought up to the previous line kind of like the ' operator (not as much a fan of this one)

I've also only thought of the yield case here - but helpers can be used inline in htmlbars, in component assignments, etc, so there are considerations there too.

thec0keman commented 7 years ago

I like your first case, it seems in line with the current bracket syntax and would only require a small expansion of the current bracket rules (e.g they can now apply to subexpressions). So: (<keyword [ .. ])

Would it be worth limiting keyword to only component or hash, or any subexpression? Would this cover the use cases you are currently facing?

kjhangiani commented 7 years ago

This would definitely cover the use case we are currently experiencing - would it work in the nested form as well? We sometimes use a hash with several sub hashes to further organize the contextual components. Though honestly those are easy refactors, so even in the non-nested case its useful.

Initially was thinking it should work for all helpers, but on second thought, it should probably be limited to component and hash. Those are the only helpers that use the attr=value syntax inside the helper, I believe? other helpers are (helper-name arg1 arg2) which doesn't fit within the current [...] model.

As far as I know there's no way to create a custom helper that supports arg=value.

for the component case, would it be:

(component '/path/to/component' [
 arg1=value1
 arg2=value2
])

or

(component [
 '/path/to/component'
 arg1=value1
 arg2=value2
])

(component has the unique first parameter before the arg=value params that dictates the component path). I would say if I had the choice, the former should be the only supported way.

thec0keman commented 7 years ago

Yeah, I think it would be ideal to allow in nested subexpressions.

I think queryParams is another subexpression helper that takes key / value pairs, though probably isn't one that would benefit as much as hash or component. At the same time, if you had something like (my-helper a b c d e f g) I don't know if it would be very much different for Emblem to allow brackets there as well:

(my-helper [
  a
  b
  c
  d
  e
])
Leooo commented 7 years ago

@thec0keman awesome!